Files
Server/src/engine/objects/Building.java
T
MagicBotandClaude Opus 5.5 e64d64bf18 Keep destroyed warehouse contents in memory so the rubble can be looted
destroyOrDerank nulled city.warehouse on destruction, so the loot click
found no warehouse and returned silently. The destroyed Warehouse is now
held in Warehouse.destroyedWarehouses keyed by the rubble building UUID
until it is looted empty, the rubble is destroyed, or the city places a
new warehouse. Contents are not persisted across a reboot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 09:19:40 -04:00

1540 lines
47 KiB
Java

// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
// Magicbane Emulator Project © 2013 - 2026
// https://www.magicbane.com
package engine.objects;
import engine.InterestManagement.RealmMap;
import engine.InterestManagement.Terrain;
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.DispatchManager;
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.mbEnums;
import engine.mbEnums.*;
import engine.net.ByteBufferWriter;
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.ThreadLocalRandom;
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();
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<>();
public int meshUUID;
public Zone parentZone;
public int parentZoneUUID;
public boolean reverseKOS;
public int reserve = 0;
public float statLat;
public float statLon;
public float statAlt;
public LocalDateTime upgradeDateTime = null;
public LocalDateTime taxDateTime = null;
public ArrayList<Vector3fImmutable> sentryPoints = new ArrayList<>();
public TaxType taxType = TaxType.NONE;
public int taxAmount;
public boolean enforceKOS = false;
// Variables NOT to be stored in db
public int parentBuildingID;
public boolean isFurniture = false;
public int floor;
public int level;
public AtomicBoolean isDeranking = new AtomicBoolean(false);
public LocalDateTime maintDateTime;
/* The Blueprint class has methods able to derive
* all defining characteristics of this building,
*/
public int blueprintUUID = 0;
public int rank;
public ArrayList<Vector3fImmutable> patrolPoints;
public ProtectionState protectionState = ProtectionState.NONE;
protected Resists resists;
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 boolean ownerIsNPC = true;
private boolean spireIsActive = false;
private ConcurrentHashMap<String, JobContainer> timers = null;
private ConcurrentHashMap<String, Long> timestamps = null;
private ConcurrentHashMap<Integer, BuildingFriends> friends;
private ConcurrentHashMap<Integer, Condemned> condemned;
private ArrayList<Building> children = null;
/**
* ResultSet Constructor
*/
public Building(ResultSet rs) throws SQLException {
super(rs);
float scale;
try {
this.meshUUID = rs.getInt("meshUUID");
this.setObjectTypeMask(MBServerStatics.MASK_BUILDING);
this.blueprintUUID = rs.getInt("blueprintUUID");
this.gridObjectType = GridObjectType.STATIC;
this.parentZoneUUID = rs.getInt("parent");
this.name = rs.getString("name");
this.ownerUUID = rs.getInt("ownerUUID");
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;
this.statLat = rs.getFloat("locationX");
this.statAlt = rs.getFloat("locationY");
this.statLon = rs.getFloat("locationZ");
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"));
this._strongboxValue = rs.getInt("currentGold");
this.maxGold = 15000000; // *** Refactor to blueprint method
this.reserve = rs.getInt("reserve");
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());
if(rs.getInt("enforceKOS") == 0)
this.enforceKOS = false;
else
this.enforceKOS = true;
} catch (Exception e) {
Logger.error("Failed for object " + this.blueprintUUID + ' ' + this.getObjectUUID() + e);
}
}
/*
* Getters
*/
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
}
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(), CityBoundsType.ZONE.halfExtents))
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(), CityBoundsType.ZONE.halfExtents))
return bane.getCity();
}
}
}
}
}
if (this.parentZone.guild_zone == false)
return null;
return City.getCity(this.parentZone.playerCityUUID);
}
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);
}
//This method is to handle when a building is damaged below 0 health.
//Either destroy or derank it.
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);
City playerCity = ZoneManager.getCityAtLocation(this.loc);
if (playerCity != null) {
if (this.getGuild().getNation().equals(playerCity.getTOL().getGuild().getNation())) {
//friendly building has been attacked, add attacker to city outlaw list
if (!playerCity.cityOutlaws.contains(attacker.getObjectUUID()) && attacker.getObjectType().equals(GameObjectType.PlayerCharacter))
playerCity.cityOutlaws.add(attacker.getObjectUUID());
//only city guards respond; siege engines and pets in the zone are left alone
for (Mob guard : playerCity.getParent().zoneMobSet)
if (guard.isPlayerGuard() && guard.combatTarget == null)
guard.setCombatTarget(attacker);
}
}
return newHealth - oldHealth;
}
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) {
Warehouse.destroyedWarehouses.put(this.getObjectUUID(), city.warehouse);
DbManager.WarehouseQueries.DELETE_WAREHOUSE(city.warehouse);
city.warehouse = null;
}
// 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);
BuildingManager.setRank(this, -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)
BuildingManager.setRank(this, -1);
else
BuildingManager.setRank(this, this.rank - 1);
}
// Return the maint cost in gold associated with this structure
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) {
//don't 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);
BuildingManager.setRank(spireBuilding, -1);
}
}
if (shrineBuildings.size() > Blueprint.getMaxShrines(this.rank - 1)) {
shrineBuilding = shrineBuildings.get(0);
// Delete a random shrine
if (shrineBuilding != null)
BuildingManager.setRank(shrineBuilding, -1);
}
if (barracksBuildings.size() > this.rank - 1) {
Building barracksBuilding = barracksBuildings.get(0);
// Delete a random barrack
if (barracksBuilding != null)
BuildingManager.setRank(barracksBuilding, -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.realm;
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) {
BuildingManager.setRank(this, rank - 1);
City.lastCityUpdate = System.currentTimeMillis();
return;
}
// 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().guildState == GuildState.Sworn)
siegeResult = SiegeResult.CAPTURE;
else
siegeResult = SiegeResult.DESTROY;
// 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();
}
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 setw(float value) {
this.w = value;
}
public final float getw() {
return this.w;
}
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;
}
//Sets the relative position to a parent zone
public final void setParentZone(Zone zone) {
//update ZoneManager's zone building list
if (zone != null)
if (this.parentZone != null) {
this.parentZone.zoneBuildingSet.remove(this);
if (this.getBlueprint() != null && this.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)) {
this.RemoveFromBarracksList();
}
zone.zoneBuildingSet.add(this);
} else
zone.zoneBuildingSet.add(this);
else if (this.parentZone != null) {
this.parentZone.zoneBuildingSet.remove(this);
if (this.getBlueprint() != null && this.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)) {
this.RemoveFromBarracksList();
}
}
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;
if (this.getBlueprint() != null && this.getBlueprint().getBuildingGroup().equals(BuildingGroup.BARRACK)) {
AddToBarracksList();
}
}
public final int getParentZoneID() {
if (this.parentZone == null)
return 0;
return this.parentZone.getObjectUUID();
}
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);
DispatchManager.sendToAllInRange(this, applyBuildingEffectMsg);
}
public void removeEffectBit(int bit) {
this.effectFlags &= (~bit);
}
@Override
public String getName() {
return this.name;
}
public final void setName(String value) {
if (DbManager.BuildingQueries.CHANGE_NAME(this, value) == false)
return;
this.name = value;
this.updateName();
}
public final AbstractCharacter getOwner() {
if (this.ownerUUID == 0)
return null;
if (this.ownerIsNPC)
return NPC.getNPC(this.ownerUUID);
return PlayerCharacter.getPlayerCharacter(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";
}
@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 refresh(boolean newMesh) {
if (newMesh)
WorldGrid.updateObject(this);
else {
UpdateObjectMsg uom = new UpdateObjectMsg(this, 3);
DispatchManager.sendToAllInRange(this, uom);
}
}
public final void updateName() {
UpdateObjectMsg uom = new UpdateObjectMsg(this, 2);
DispatchManager.sendToAllInRange(this, uom);
}
public final void refreshGuild() {
UpdateObjectMsg uom = new UpdateObjectMsg(this, 5);
DispatchManager.sendToAllInRange(this, uom);
}
public int getMaxGold() {
return maxGold;
}
@Override
public void runAfterLoad() {
// Set Parent Zone
this.parentZone = ZoneManager.getZoneByUUID(this.parentZoneUUID);
this.parentZone.zoneBuildingSet.add(this);
// Lookup building blueprint
Blueprint 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);
this.patrolPoints = BuildingManager._buildingPatrolPoints.computeIfAbsent(this.getObjectUUID(), k -> new ArrayList<>());
if (this.patrolPoints == null)
Logger.error("Null patrol points");
} else {
this.healthMax = 100000; // Structures with no blueprint mesh
this.setHealth(healthMax);
}
resists = new Resists("Building");
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, Terrain.getWorldHeight(tempLoc) + this.statAlt, tempLoc.z);
this.setLoc(tempLoc);
}
}
// 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());
}
// Reference friend and condemn lists from BuildingManager
this.friends = BuildingManager._buildingFriends.computeIfAbsent(this.getObjectUUID(), k -> new ConcurrentHashMap<>());
this.condemned = BuildingManager._buildingCondemned.computeIfAbsent(this.getObjectUUID(), k -> new ConcurrentHashMap<>());
// 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.getBuilding(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);
}
public synchronized boolean setOwner(AbstractCharacter newOwner) {
int newOwnerID;
if (newOwner == null)
newOwnerID = 0;
else
newOwnerID = 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() {
Vector3fImmutable stuckLocation;
ArrayList<BuildingLocation> stuckLocations;
stuckLocations = BuildingManager._stuckLocations.get(this.meshUUID);
// Sanity check
if (stuckLocations == null ||
stuckLocations.isEmpty())
return this.getLoc();
stuckLocation = stuckLocations.get(ThreadLocalRandom.current().nextInt(stuckLocations.size())).getLocation();
stuckLocation = this.getLoc().add(stuckLocation);
// Rotate stuck position by the building rotation
stuckLocation = Vector3fImmutable.rotateAroundPoint(this.getLoc(), stuckLocation, this.getBounds().getQuaternion().angleY);
return stuckLocation;
}
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)
return DbManager.BuildingQueries.UPDATE_DOOR_LOCK(this.getObjectUUID(), this.doorState);
else
return true;
}
public void updateEffects() {
ApplyBuildingEffectMsg applyBuildingEffectMsg = new ApplyBuildingEffectMsg(0x00720063, 1, this.getObjectType().ordinal(), this.getObjectUUID(), this.effectFlags);
DispatchManager.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 void setSpireIsActive(boolean spireIsActive) {
this.spireIsActive = 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 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 = protectionState.equals(ProtectionState.PROTECTED);
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().warehouse == null)
return amount;
if (this.getCity().warehouse.resources.get(ResourceType.GOLD) >= ResourceType.GOLD.deposit_limit)
return amount;
int profitAmount = (int) (amount * (taxAmount * .01f));
if (this.getCity().warehouse.resources.get(ResourceType.GOLD) + profitAmount <= ResourceType.GOLD.deposit_limit) {
Warehouse.depositProfitTax(ResourceType.GOLD, profitAmount, this, this.getCity().warehouse);
return amount - profitAmount;
}
//overDrafting
int warehouseDeposit = ResourceType.GOLD.deposit_limit - this.getCity().warehouse.resources.get(ResourceType.GOLD);
Warehouse.depositProfitTax(ResourceType.GOLD, warehouseDeposit, this, this.getCity().warehouse);
return amount - warehouseDeposit;
}
public synchronized boolean setReserve(int amount, PlayerCharacter player) {
if (!BuildingManager.playerCanManage(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 ArrayList<Vector3fImmutable> getSentryPoints() {
return 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 acceptTaxes() {
if (!DbManager.BuildingQueries.acceptTaxes(this))
return false;
this.setProtectionState(mbEnums.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 void AddToBarracksList() {
City playerCity = ZoneManager.getCityAtLocation(this.loc);
if (playerCity != null) {
playerCity.cityBarracks.add(this);
}
}
public void RemoveFromBarracksList() {
}
}