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>
1540 lines
47 KiB
Java
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() {
|
|
|
|
}
|
|
}
|