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.
1719 lines
53 KiB
1719 lines
53 KiB
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . |
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· |
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ |
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ |
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ |
|
// Magicbane Emulator Project © 2013 - 2022 |
|
// www.magicbane.com |
|
|
|
|
|
package engine.objects; |
|
|
|
import engine.Enum; |
|
import engine.Enum.*; |
|
import engine.InterestManagement.HeightMap; |
|
import engine.InterestManagement.RealmMap; |
|
import engine.InterestManagement.WorldGrid; |
|
import engine.db.archive.CityRecord; |
|
import engine.db.archive.DataWarehouse; |
|
import engine.db.archive.MineRecord; |
|
import engine.gameManager.BuildingManager; |
|
import engine.gameManager.DbManager; |
|
import engine.gameManager.ZoneManager; |
|
import engine.job.JobContainer; |
|
import engine.job.JobScheduler; |
|
import engine.jobs.DoorCloseJob; |
|
import engine.jobs.SiegeSpireWithdrawlJob; |
|
import engine.math.Bounds; |
|
import engine.math.Vector3f; |
|
import engine.math.Vector3fImmutable; |
|
import engine.net.ByteBufferWriter; |
|
import engine.net.DispatchMessage; |
|
import engine.net.client.msg.ApplyBuildingEffectMsg; |
|
import engine.net.client.msg.UpdateObjectMsg; |
|
import engine.server.MBServerStatics; |
|
import org.pmw.tinylog.Logger; |
|
|
|
import java.sql.ResultSet; |
|
import java.sql.SQLException; |
|
import java.time.LocalDateTime; |
|
import java.time.ZoneId; |
|
import java.util.ArrayList; |
|
import java.util.HashMap; |
|
import java.util.concurrent.ConcurrentHashMap; |
|
import java.util.concurrent.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 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> patrolPoints = new ArrayList<>(); |
|
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; |
|
protected Resists resists; |
|
/* The Blueprint class has methods able to derive |
|
* all defining characteristics of this building, |
|
*/ |
|
private int blueprintUUID = 0; |
|
private float w = 1.0f; |
|
private Vector3f meshScale = new Vector3f(1.0f, 1.0f, 1.0f); |
|
private int doorState = 0; |
|
private int ownerUUID = 0; //NPC or Character--check ownerIsNPC flag |
|
private int _strongboxValue = 0; |
|
private int maxGold; |
|
private int effectFlags = 0; |
|
private String name = ""; |
|
private int rank; |
|
private boolean ownerIsNPC = true; |
|
private boolean spireIsActive = false; |
|
private ConcurrentHashMap<String, JobContainer> timers = null; |
|
private ConcurrentHashMap<String, Long> timestamps = null; |
|
private ConcurrentHashMap<Integer, BuildingFriends> friends = new ConcurrentHashMap<>(); |
|
private ConcurrentHashMap<Integer, Condemned> condemned = new ConcurrentHashMap<>(); |
|
private ProtectionState protectionState = ProtectionState.NONE; |
|
private ArrayList<Building> children = null; |
|
|
|
/** |
|
* ResultSet Constructor |
|
*/ |
|
|
|
public Building(ResultSet rs) throws SQLException { |
|
super(rs); |
|
|
|
float scale; |
|
Blueprint blueprint = null; |
|
|
|
try { |
|
this.meshUUID = rs.getInt("meshUUID"); |
|
this.setObjectTypeMask(MBServerStatics.MASK_BUILDING); |
|
this.blueprintUUID = rs.getInt("blueprintUUID"); |
|
this.gridObjectType = GridObjectType.STATIC; |
|
this.parentZone = DbManager.ZoneQueries.GET_BY_UID(rs.getLong("parent")); |
|
this.name = rs.getString("name"); |
|
this.ownerUUID = rs.getInt("ownerUUID"); |
|
|
|
// Orphaned Object Sanity Check |
|
//This was causing ABANDONED Tols. |
|
// if (objectType == DbObjectType.INVALID) |
|
// this.ownerUUID = 0; |
|
|
|
this.doorState = rs.getInt("doorState"); |
|
this.setHealth(rs.getInt("currentHP")); |
|
this.w = rs.getFloat("w"); |
|
this.setRot(new Vector3f(0f, rs.getFloat("rotY"), 0f)); |
|
this.reverseKOS = rs.getByte("reverseKOS") == 1 ? true : false; |
|
|
|
scale = rs.getFloat("scale"); |
|
this.meshScale = new Vector3f(scale, scale, scale); |
|
|
|
this.rank = rs.getInt("rank"); |
|
this.parentBuildingID = rs.getInt("parentBuildingID"); |
|
|
|
//create a new list if the building is a parent and not a child. |
|
|
|
if (this.parentBuildingID == 0) |
|
this.children = new ArrayList<>(); |
|
|
|
this.floor = rs.getInt("floor"); |
|
this.level = rs.getInt("level"); |
|
this.isFurniture = (rs.getBoolean("isFurniture")); |
|
|
|
// Lookup building blueprint |
|
|
|
if (this.blueprintUUID == 0) |
|
blueprint = Blueprint._meshLookup.get(meshUUID); |
|
else |
|
blueprint = this.getBlueprint(); |
|
|
|
// Log error if something went horrible wrong |
|
|
|
if ((this.blueprintUUID != 0) && (blueprint == null)) |
|
Logger.error("Invalid blueprint for object: " + this.getObjectUUID()); |
|
|
|
// Note: We handle R8 tree edge case for mesh and health |
|
// after city is loaded to avoid recursive result set call |
|
// in City resulting in a stack ovreflow. |
|
|
|
if (blueprint != null) { |
|
|
|
// Only switch mesh for player dropped structures |
|
|
|
if (this.blueprintUUID != 0) |
|
this.meshUUID = blueprint.getMeshForRank(rank); |
|
|
|
this.healthMax = blueprint.getMaxHealth(this.rank); |
|
|
|
// If this object has no blueprint but is a blueprint |
|
// mesh then set it's current health to max health |
|
|
|
if (this.blueprintUUID == 0) |
|
this.setHealth(healthMax); |
|
|
|
if (blueprint.getBuildingGroup().equals(BuildingGroup.BARRACK)) |
|
this.patrolPoints = DbManager.BuildingQueries.LOAD_PATROL_POINTS(this); |
|
|
|
} else { |
|
this.healthMax = 100000; // Structures with no blueprint mesh |
|
this.setHealth(healthMax); |
|
} |
|
|
|
// Null out blueprint if not needed (npc building) |
|
|
|
if (blueprintUUID == 0) |
|
blueprint = null; |
|
|
|
resists = new Resists("Building"); |
|
this.statLat = rs.getFloat("locationX"); |
|
this.statAlt = rs.getFloat("locationY"); |
|
this.statLon = rs.getFloat("locationZ"); |
|
|
|
if (this.parentZone != null) { |
|
if (this.parentBuildingID != 0) { |
|
Building parentBuilding = BuildingManager.getBuilding(this.parentBuildingID); |
|
if (parentBuilding != null) { |
|
this.setLoc(new Vector3fImmutable(this.statLat + this.parentZone.absX + parentBuilding.statLat, this.statAlt + this.parentZone.absY + parentBuilding.statAlt, this.statLon + this.parentZone.absZ + parentBuilding.statLon)); |
|
} else { |
|
this.setLoc(new Vector3fImmutable(this.statLat + this.parentZone.absX, this.statAlt + this.parentZone.absY, this.statLon + this.parentZone.absZ)); |
|
|
|
} |
|
} else { |
|
|
|
// Altitude of this building is derived from the heightmap engine. |
|
|
|
Vector3fImmutable tempLoc = new Vector3fImmutable(this.statLat + this.parentZone.absX, 0, this.statLon + this.parentZone.absZ); |
|
tempLoc = new Vector3fImmutable(tempLoc.x, HeightMap.getWorldHeight(tempLoc), tempLoc.z); |
|
this.setLoc(tempLoc); |
|
} |
|
} |
|
|
|
this._strongboxValue = rs.getInt("currentGold"); |
|
this.maxGold = 15000000; // *** Refactor to blueprint method |
|
this.reserve = rs.getInt("reserve"); |
|
|
|
// Does building have a protection contract? |
|
this.taxType = TaxType.valueOf(rs.getString("taxType")); |
|
this.taxAmount = rs.getInt("taxAmount"); |
|
this.protectionState = ProtectionState.valueOf(rs.getString("protectionState")); |
|
|
|
java.sql.Timestamp maintTimeStamp = rs.getTimestamp("maintDate"); |
|
|
|
if (maintTimeStamp != null) |
|
this.maintDateTime = LocalDateTime.ofInstant(maintTimeStamp.toInstant(), ZoneId.systemDefault()); |
|
|
|
java.sql.Timestamp taxTimeStamp = rs.getTimestamp("taxDate"); |
|
|
|
if (taxTimeStamp != null) |
|
this.taxDateTime = LocalDateTime.ofInstant(taxTimeStamp.toInstant(), ZoneId.systemDefault()); |
|
|
|
java.sql.Timestamp upgradeTimeStamp = rs.getTimestamp("upgradeDate"); |
|
|
|
if (upgradeTimeStamp != null) |
|
this.upgradeDateTime = LocalDateTime.ofInstant(upgradeTimeStamp.toInstant(), ZoneId.systemDefault()); |
|
|
|
} catch (Exception e) { |
|
|
|
Logger.error("Failed for object " + this.blueprintUUID + ' ' + this.getObjectUUID() + e.toString()); |
|
} |
|
} |
|
|
|
/* |
|
* Getters |
|
*/ |
|
|
|
public 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 void setRank(int newRank) { |
|
|
|
int newMeshUUID; |
|
boolean success; |
|
|
|
|
|
// If this building has no blueprint then set rank and exit immediatly. |
|
|
|
if (this.blueprintUUID == 0 || this.getBlueprint() != null && this.getBlueprint().getBuildingGroup().equals(BuildingGroup.MINE)) { |
|
this.rank = newRank; |
|
DbManager.BuildingQueries.CHANGE_RANK(this.getObjectUUID(), newRank); |
|
return; |
|
} |
|
|
|
// Delete any upgrade jobs before doing anything else. It won't quite work |
|
// if in a few lines we happen to delete this building. |
|
|
|
JobContainer jc = this.getTimers().get("UPGRADE"); |
|
|
|
if (jc != null) { |
|
if (!JobScheduler.getInstance().cancelScheduledJob(jc)) |
|
Logger.error("failed to cancel existing upgrade job."); |
|
} |
|
|
|
// Attempt write to database, or delete the building |
|
// if we are destroying it. |
|
|
|
if (newRank == -1) |
|
success = DbManager.BuildingQueries.DELETE_FROM_DATABASE(this); |
|
else |
|
success = DbManager.BuildingQueries.updateBuildingRank(this, newRank); |
|
|
|
if (success == false) { |
|
Logger.error("Error writing to database UUID: " + this.getObjectUUID()); |
|
return; |
|
} |
|
|
|
this.isDeranking.compareAndSet(false, true); |
|
|
|
// Change the building's rank |
|
|
|
this.rank = newRank; |
|
|
|
// New rank means new mesh |
|
|
|
newMeshUUID = this.getBlueprint().getMeshForRank(this.rank); |
|
this.meshUUID = newMeshUUID; |
|
|
|
// New rank mean new max hitpoints. |
|
|
|
this.healthMax = this.getBlueprint().getMaxHealth(this.rank); |
|
this.setCurrentHitPoints(this.healthMax); |
|
|
|
if (this.getUpgradeDateTime() != null) |
|
BuildingManager.setUpgradeDateTime(this, null, 0); |
|
|
|
// If we destroyed this building make sure to turn off |
|
// protection |
|
|
|
if (this.rank == -1) |
|
this.protectionState = ProtectionState.NONE; |
|
|
|
if ((this.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) |
|
&& (this.rank == 8)) |
|
this.meshUUID = Realm.getRealmMesh(this.getCity()); |
|
; |
|
|
|
// update object to clients |
|
|
|
this.refresh(true); |
|
if (this.getBounds() != null) |
|
this.getBounds().setBounds(this); |
|
|
|
// Cleanup hirelings resulting from rank change |
|
|
|
BuildingManager.cleanupHirelings(this); |
|
|
|
this.isDeranking.compareAndSet(true, false); |
|
} |
|
|
|
public final 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.extents)) |
|
return this.getGuild().getOwnedCity(); |
|
} else { |
|
Bane bane = Bane.getBaneByAttackerGuild(this.getGuild()); |
|
|
|
if (bane != null) { |
|
if (bane.getCity() != null) { |
|
if (this.getLoc().isInsideCircle(bane.getCity().getLoc(), CityBoundsType.ZONE.extents)) |
|
return bane.getCity(); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
if (this.parentZone.isPlayerCity() == false) |
|
return null; |
|
|
|
return City.getCity(this.parentZone.getPlayerCityUUID()); |
|
|
|
} |
|
|
|
public final String getCityName() { |
|
|
|
City city = getCity(); |
|
|
|
if (city != null) |
|
return city.getName(); |
|
|
|
return ""; |
|
} |
|
|
|
public final Blueprint getBlueprint() { |
|
|
|
if (this.blueprintUUID == 0) |
|
return null; |
|
|
|
return Blueprint.getBlueprint(this.blueprintUUID); |
|
|
|
} |
|
|
|
public final int getBlueprintUUID() { |
|
|
|
return this.blueprintUUID; |
|
} |
|
|
|
public final void setCurrentHitPoints(Float CurrentHitPoints) { |
|
this.addDatabaseJob("health", MBServerStatics.ONE_MINUTE); |
|
this.setHealth(CurrentHitPoints); |
|
} |
|
|
|
//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); |
|
|
|
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) |
|
if (city != null) |
|
city.setWarehouseBuildingID(0); |
|
|
|
// Special handling of destroyed Spires |
|
|
|
if ((blueprint.getBuildingGroup() == BuildingGroup.SPIRE) && this.rank == 1) |
|
this.disableSpire(true); |
|
|
|
// Special handling of destroyed Mines |
|
|
|
if (blueprint.getBuildingGroup() == BuildingGroup.MINE |
|
&& this.rank == 1) { |
|
|
|
Mine mine = Mine.getMineFromTower(this.getObjectUUID()); |
|
|
|
if (mine != null) { |
|
|
|
// Warehouse mine destruction event |
|
|
|
MineRecord mineRecord = MineRecord.borrow(mine, attacker, RecordEventType.DESTROY); |
|
DataWarehouse.pushToWarehouse(mineRecord); |
|
|
|
this.setRank(-1); |
|
this.setCurrentHitPoints((float) 1); |
|
this.healthMax = (float) 1; |
|
this.meshUUID = this.getBlueprint().getMeshForRank(this.rank); |
|
mine.handleDestroyMine(); |
|
this.getBounds().setBounds(this); |
|
this.refresh(true); |
|
return; |
|
} |
|
} |
|
|
|
// Special handling of deranking Trees |
|
|
|
if (blueprint.getBuildingGroup() == BuildingGroup.TOL) { |
|
derankTreeOfLife(); |
|
return; |
|
} |
|
|
|
// If codepath reaches here then it's a regular |
|
// structure not requiring special handling. |
|
// Time to either derank or destroy the building. |
|
|
|
if ((this.rank - 1) < 1) |
|
this.setRank(-1); |
|
else |
|
this.setRank(this.rank - 1); |
|
|
|
} |
|
|
|
// 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); |
|
spireBuilding.setRank(-1); |
|
} |
|
} |
|
|
|
if (shrineBuildings.size() > Blueprint.getMaxShrines(this.rank - 1)) { |
|
|
|
shrineBuilding = shrineBuildings.get(0); |
|
|
|
// Delete a random shrine |
|
|
|
if (shrineBuilding != null) |
|
shrineBuilding.setRank(-1); |
|
} |
|
|
|
if (barracksBuildings.size() > this.rank - 1) { |
|
|
|
Building barracksBuilding = barracksBuildings.get(0); |
|
|
|
// Delete a random barrack |
|
|
|
if (barracksBuilding != null) |
|
barracksBuilding.setRank(-1); |
|
} |
|
|
|
// If the tree is R8 and deranking, we need to update it's |
|
// mesh along with buildings losing their health bonus |
|
|
|
if (this.rank == 8) { |
|
|
|
cityRealm = city.getRealm(); |
|
|
|
if (cityRealm != null) |
|
cityRealm.abandonRealm(); |
|
|
|
for (Building cityBuilding : this.parentZone.zoneBuildingSet) { |
|
|
|
if ((cityBuilding.getBlueprint() != null && cityBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.TOL) |
|
&& (cityBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.BANESTONE)) { |
|
cityBuilding.healthMax = cityBuilding.getBlueprint().getMaxHealth(cityBuilding.rank); |
|
} |
|
|
|
if (cityBuilding.health.get() > cityBuilding.healthMax) |
|
cityBuilding.setHealth(cityBuilding.healthMax); |
|
} |
|
} |
|
|
|
// Tree is simply deranking. |
|
// Let's do so and early exit |
|
|
|
if (this.rank > 1) { |
|
this.setRank(rank - 1); |
|
City.lastCityUpdate = System.currentTimeMillis(); |
|
return; |
|
} |
|
|
|
// Must remove a bane before considering destruction of a TOL |
|
|
|
if (bane != null) { |
|
|
|
// Cache the new owner |
|
|
|
newOwner = Guild.GetGL(bane.getOwner().getGuild()); |
|
|
|
this.isDeranking.compareAndSet(false, true); |
|
|
|
if ((bane.getOwner().getGuild().getGuildState() == GuildState.Sovereign) || |
|
(bane.getOwner().getGuild().getGuildState() == GuildState.Protectorate) || |
|
(bane.getOwner().getGuild().getGuildState() == GuildState.Province) || |
|
(bane.getOwner().getGuild().getGuildState() == GuildState.Nation)) |
|
siegeResult = SiegeResult.DESTROY; |
|
else |
|
siegeResult = SiegeResult.CAPTURE; |
|
|
|
// Remove realm if city had one |
|
|
|
Realm realm = RealmMap.getRealmAtLocation(city.getLoc()); |
|
|
|
if (realm != null) |
|
if (realm.isRealmFullAfterBane()) |
|
siegeResult = SiegeResult.DESTROY; |
|
|
|
city.getBane().endBane(siegeResult); |
|
|
|
// If it's a capture bane transfer the tree and exit |
|
|
|
if (siegeResult.equals(SiegeResult.CAPTURE)) { |
|
city.transfer(newOwner); |
|
CityRecord cityRecord = CityRecord.borrow(city, RecordEventType.CAPTURE); |
|
DataWarehouse.pushToWarehouse(cityRecord); |
|
return; |
|
} |
|
} // end removal of bane |
|
|
|
// if codepath reaches here then we can now destroy the tree and the city |
|
|
|
CityRecord cityRecord = CityRecord.borrow(city, RecordEventType.DESTROY); |
|
DataWarehouse.pushToWarehouse(cityRecord); |
|
|
|
city.destroy(); |
|
|
|
} |
|
|
|
public float getCurrentHitpoints() { |
|
return this.health.get(); |
|
} |
|
|
|
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 void setMeshUUID(int value) { |
|
this.meshUUID = value; |
|
} |
|
|
|
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); |
|
DispatchMessage.sendToAllInRange(this, applyBuildingEffectMsg); |
|
} |
|
|
|
/* |
|
* Utils |
|
*/ |
|
|
|
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(); |
|
} |
|
|
|
|
|
/* |
|
* Serializing |
|
*/ |
|
|
|
public final AbstractCharacter getOwner() { |
|
|
|
if (this.ownerUUID == 0) |
|
return null; |
|
if (this.ownerIsNPC) |
|
return NPC.getFromCache(this.ownerUUID); |
|
|
|
return PlayerCharacter.getFromCache(this.ownerUUID); |
|
|
|
} |
|
|
|
/* |
|
* Database |
|
*/ |
|
|
|
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; |
|
if(this.isOwnerIsNPC()) { |
|
newValue = 0; |
|
this.setStrongboxValue(0); |
|
} |
|
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); |
|
DispatchMessage.sendToAllInRange(this, uom); |
|
} |
|
} |
|
|
|
public final void updateName() { |
|
|
|
UpdateObjectMsg uom = new UpdateObjectMsg(this, 2); |
|
DispatchMessage.sendToAllInRange(this, uom); |
|
|
|
} |
|
|
|
// *** Refactor: Can't we just use setRank() for this? |
|
|
|
public final void rebuildMine(int maxHP) { |
|
this.setRank(1); |
|
this.meshUUID = this.getBlueprint().getMeshForRank(this.rank); |
|
|
|
// New rank mean new max hitpoints. |
|
|
|
//this.healthMax = this.getBlueprint().getMaxHealth(this.rank); |
|
this.healthMax = maxHP; |
|
this.setCurrentHitPoints(this.healthMax); |
|
this.getBounds().setBounds(this); |
|
} |
|
|
|
public final void refreshGuild() { |
|
|
|
UpdateObjectMsg uom = new UpdateObjectMsg(this, 5); |
|
DispatchMessage.sendToAllInRange(this, uom); |
|
|
|
} |
|
|
|
public int getMaxGold() { |
|
return maxGold; |
|
} |
|
|
|
//This returns if a player is allowed access to control the building |
|
|
|
@Override |
|
public void runAfterLoad() { |
|
|
|
try { |
|
|
|
this.parentZone.zoneBuildingSet.add(this); |
|
|
|
// Submit upgrade job if building is currently set to rank. |
|
|
|
try { |
|
DbObjectType objectType = DbManager.BuildingQueries.GET_UID_ENUM(this.ownerUUID); |
|
this.ownerIsNPC = (objectType == DbObjectType.NPC); |
|
} catch (Exception e) { |
|
this.ownerIsNPC = false; |
|
Logger.error("Failed to find Object Type for owner " + this.ownerUUID + " Location " + this.getLoc().toString()); |
|
} |
|
|
|
try { |
|
DbManager.BuildingQueries.LOAD_ALL_FRIENDS_FOR_BUILDING(this); |
|
DbManager.BuildingQueries.LOAD_ALL_CONDEMNED_FOR_BUILDING(this); |
|
} catch (Exception e) { |
|
Logger.error(this.getObjectUUID() + " failed to load friends/condemned." + e.getMessage()); |
|
} |
|
|
|
//LOad Owners in Cache so we do not have to continuely look in the db for owner. |
|
|
|
if (this.ownerIsNPC) { |
|
if (NPC.getNPC(this.ownerUUID) == null) |
|
Logger.info("Building UID " + this.getObjectUUID() + " Failed to Load NPC Owner with ID " + this.ownerUUID + " Location " + this.getLoc().toString()); |
|
|
|
} else if (this.ownerUUID != 0) { |
|
if (PlayerCharacter.getPlayerCharacter(this.ownerUUID) == null) { |
|
Logger.info("Building UID " + this.getObjectUUID() + " Failed to Load Player Owner with ID " + this.ownerUUID + " Location " + this.getLoc().toString()); |
|
} |
|
} |
|
|
|
// Apply health bonus and special mesh for realm if applicable |
|
if ((this.getCity() != null) && this.getCity().getTOL() != null && (this.getCity().getTOL().rank == 8)) { |
|
|
|
// Update mesh accordingly |
|
if (this.getBlueprint() != null && this.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) |
|
this.meshUUID = Realm.getRealmMesh(this.getCity()); |
|
|
|
// Apply realm capital health bonus. |
|
// Do not apply bonus to banestones or TOL's. *** Refactor: |
|
// Possibly only protected buildings? Needs some thought. |
|
|
|
float missingHealth = 0; |
|
|
|
if (this.health.get() != 0) |
|
missingHealth = this.healthMax - this.health.get(); |
|
|
|
if ((this.getBlueprint() != null && this.getBlueprint().getBuildingGroup() != BuildingGroup.TOL) |
|
&& (this.getBlueprint().getBuildingGroup() != BuildingGroup.BANESTONE)) { |
|
this.healthMax += (this.healthMax * Realm.getRealmHealthMod(this.getCity())); |
|
|
|
if (this.health.get() != 0) |
|
this.health.set(this.healthMax - missingHealth); |
|
|
|
if (this.health.get() > this.healthMax) |
|
this.health.set(this.healthMax); |
|
} |
|
} |
|
|
|
// Set bounds for this building |
|
|
|
Bounds buildingBounds = Bounds.borrow(); |
|
buildingBounds.setBounds(this); |
|
this.setBounds(buildingBounds); |
|
|
|
//create a new list for children if the building is not a child. children list default is null. |
|
//TODO Remove Furniture/Child buildings from building class and move them into a seperate class. |
|
|
|
if (this.parentBuildingID == 0) |
|
this.children = new ArrayList<>(); |
|
|
|
if (this.parentBuildingID != 0) { |
|
Building parent = BuildingManager.getBuildingFromCache(this.parentBuildingID); |
|
|
|
if (parent != null) { |
|
parent.children.add(this); |
|
|
|
//add furniture to region cache. floor and level are reversed in database, //TODO Fix |
|
|
|
Regions region = BuildingManager.GetRegion(parent, this.level, this.floor, this.getLoc().x, this.getLoc().z); |
|
if (region != null) |
|
Regions.FurnitureRegionMap.put(this.getObjectUUID(), region); |
|
} |
|
|
|
} |
|
|
|
if (this.upgradeDateTime != null) |
|
BuildingManager.submitUpgradeJob(this); |
|
|
|
} catch (Exception e) { |
|
e.printStackTrace(); |
|
} |
|
} |
|
|
|
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 == true) |
|
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); |
|
DispatchMessage.sendToAllInRange(this, applyBuildingEffectMsg); |
|
|
|
} |
|
|
|
public final void enableSpire() { |
|
|
|
SpireType spireType; |
|
|
|
if (this.getCity() == null) |
|
return; |
|
|
|
// Blueprint sanity check |
|
|
|
if (this.blueprintUUID == 0) |
|
return; |
|
|
|
spireType = SpireType.getByBlueprintUUID(this.blueprintUUID); |
|
|
|
SiegeSpireWithdrawlJob spireJob = new SiegeSpireWithdrawlJob(this); |
|
JobContainer jc = JobScheduler.getInstance().scheduleJob(spireJob, 300000); |
|
this.getTimers().put("SpireWithdrawl", jc); |
|
|
|
this.getCity().addCityEffect(spireType.getEffectBase(), rank); |
|
addEffectBit(spireType.getEffectFlag()); |
|
this.spireIsActive = true; |
|
this.updateEffects(); |
|
|
|
|
|
} |
|
|
|
public final void disableSpire(boolean refreshEffect) { |
|
|
|
SpireType spireType; |
|
|
|
if (this.getCity() == null) |
|
return; |
|
|
|
// Blueprint sanity check |
|
|
|
if (this.blueprintUUID == 0) |
|
return; |
|
|
|
spireType = SpireType.getByBlueprintUUID(this.blueprintUUID); |
|
|
|
this.getCity().removeCityEffect(spireType.getEffectBase(), rank, refreshEffect); |
|
|
|
JobContainer toRemove = this.getTimers().get("SpireWithdrawl"); |
|
|
|
if (toRemove != null) { |
|
toRemove.cancelJob(); |
|
this.getTimers().remove("SpireWithdrawl"); |
|
} |
|
|
|
this.spireIsActive = false; |
|
this.removeEffectBit(spireType.getEffectFlag()); |
|
this.updateEffects(); |
|
} |
|
|
|
public ConcurrentHashMap<AbstractCharacter, Integer> getHirelings() { |
|
return hirelings; |
|
} |
|
|
|
public final boolean isSpireIsActive() { |
|
return spireIsActive; |
|
} |
|
|
|
public final 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 = false; |
|
|
|
if (protectionState.equals(ProtectionState.PROTECTED)) |
|
outValue = true; |
|
|
|
if (protectionState.equals(ProtectionState.CONTRACT)) |
|
outValue = true; |
|
|
|
return outValue; |
|
} |
|
|
|
public synchronized boolean transferGold(int amount, boolean tax) { |
|
|
|
if (amount < 0) |
|
if (!this.hasFunds(-amount)) |
|
return false; |
|
|
|
if (_strongboxValue + amount < 0) |
|
return false; |
|
|
|
if (_strongboxValue + amount > maxGold) |
|
return false; |
|
|
|
//Deduct Profit taxes. |
|
if (tax) |
|
if (taxType == TaxType.PROFIT && protectionState == ProtectionState.CONTRACT && amount > 0) |
|
amount = this.payProfitTaxes(amount); |
|
|
|
|
|
if (amount != 0) |
|
return this.setStrongboxValue(_strongboxValue + amount); |
|
return true; |
|
} |
|
|
|
public synchronized int payProfitTaxes(int amount) { |
|
|
|
if (this.getCity() == null) |
|
return amount; |
|
if (this.getCity().getWarehouse() == null) |
|
return amount; |
|
|
|
if (this.getCity().getWarehouse().getResources().get(ItemBase.getGoldItemBase()) >= Warehouse.getMaxResources().get(ItemBase.getGoldItemBase().getUUID())) |
|
return amount; |
|
|
|
int profitAmount = (int) (amount * (taxAmount * .01f)); |
|
|
|
if (this.getCity().getWarehouse().getResources().get(ItemBase.getGoldItemBase()) + profitAmount <= Warehouse.getMaxResources().get(ItemBase.getGoldItemBase().getUUID())) { |
|
this.getCity().getWarehouse().depositProfitTax(ItemBase.getGoldItemBase(), profitAmount, this); |
|
return amount - profitAmount; |
|
} |
|
//overDrafting |
|
int warehouseDeposit = Warehouse.getMaxResources().get(ItemBase.getGoldItemBase().getUUID()) - this.getCity().getWarehouse().getResources().get(ItemBase.getGoldItemBase()); |
|
this.getCity().getWarehouse().depositProfitTax(ItemBase.getGoldItemBase(), warehouseDeposit, this); |
|
return amount - warehouseDeposit; |
|
} |
|
|
|
public synchronized boolean setReserve(int amount, PlayerCharacter player) { |
|
|
|
if (!BuildingManager.playerCanManageNotFriends(player, this)) |
|
return false; |
|
|
|
if (amount < 0) |
|
return false; |
|
|
|
if (!DbManager.BuildingQueries.SET_RESERVE(this, amount)) |
|
return false; |
|
|
|
this.reserve = amount; |
|
|
|
return true; |
|
} |
|
|
|
public synchronized boolean hasFunds(int amount) { |
|
return amount <= (this._strongboxValue - reserve); |
|
} |
|
|
|
public ArrayList<Vector3fImmutable> getPatrolPoints() { |
|
return patrolPoints; |
|
} |
|
|
|
public void setPatrolPoints(ArrayList<Vector3fImmutable> patrolPoints) { |
|
this.patrolPoints = patrolPoints; |
|
} |
|
|
|
public ArrayList<Vector3fImmutable> getSentryPoints() { |
|
return sentryPoints; |
|
} |
|
|
|
public void setSentryPoints(ArrayList<Vector3fImmutable> sentryPoints) { |
|
this.sentryPoints = sentryPoints; |
|
} |
|
|
|
public synchronized boolean addProtectionTax(Building building, PlayerCharacter pc, final TaxType taxType, int amount, boolean enforceKOS) { |
|
if (building == null) |
|
return false; |
|
|
|
if (this.getBlueprint() == null) |
|
return false; |
|
|
|
if (this.getBlueprint().getBuildingGroup() != BuildingGroup.TOL) |
|
return false; |
|
|
|
if (building.assetIsProtected()) |
|
return false; |
|
|
|
if (!DbManager.BuildingQueries.addTaxes(building, taxType, amount, enforceKOS)) |
|
return false; |
|
|
|
building.taxType = taxType; |
|
building.taxAmount = amount; |
|
building.enforceKOS = enforceKOS; |
|
|
|
return true; |
|
|
|
} |
|
|
|
public synchronized boolean declineTaxOffer() { |
|
return true; |
|
} |
|
|
|
public synchronized boolean acceptTaxOffer() { |
|
return true; |
|
} |
|
|
|
public synchronized boolean acceptTaxes() { |
|
|
|
if (!DbManager.BuildingQueries.acceptTaxes(this)) |
|
return false; |
|
|
|
this.setProtectionState(Enum.ProtectionState.CONTRACT); |
|
this.taxDateTime = LocalDateTime.now().plusDays(7); |
|
|
|
return true; |
|
} |
|
|
|
public synchronized boolean removeTaxes() { |
|
|
|
if (!DbManager.BuildingQueries.removeTaxes(this)) |
|
return false; |
|
|
|
this.taxType = TaxType.NONE; |
|
this.taxAmount = 0; |
|
this.taxDateTime = null; |
|
this.enforceKOS = false; |
|
|
|
return true; |
|
} |
|
|
|
public boolean isTaxed() { |
|
if (this.taxType == TaxType.NONE) |
|
return false; |
|
if (this.taxAmount == 0) |
|
return false; |
|
return this.taxDateTime != null; |
|
} |
|
|
|
public void AddToBarracksList() { |
|
City playerCity = ZoneManager.getCityAtLocation(this.loc); |
|
if (playerCity != null) { |
|
playerCity.cityBarracks.add(this); |
|
} |
|
} |
|
|
|
public void RemoveFromBarracksList() { |
|
|
|
} |
|
}
|
|
|