|
|
|
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ .
|
|
|
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
|
|
|
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
|
|
|
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
|
|
|
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀
|
|
|
|
// Magicbane Emulator Project © 2013 - 2022
|
|
|
|
// www.magicbane.com
|
|
|
|
|
|
|
|
|
|
|
|
package engine.objects;
|
|
|
|
|
|
|
|
import engine.Enum;
|
|
|
|
import engine.Enum.*;
|
|
|
|
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.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 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> 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,
|
|
|
|
*/
|
|
|
|
public 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 = "";
|
|
|
|
public 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;
|
|
|
|
private ConcurrentHashMap<Integer, Condemned> condemned;
|
|
|
|
public ProtectionState protectionState = ProtectionState.NONE;
|
|
|
|
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());
|
|
|
|
|
|
|
|
} 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 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());
|
|
|
|
for(Mob guard : playerCity.getParent().zoneMobSet)
|
|
|
|
if(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)
|
|
|
|
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);
|
|
|
|
|
|
|
|
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.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) {
|
|
|
|
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().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 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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.getFromCache(this.ownerUUID);
|
|
|
|
|
|
|
|
return PlayerCharacter.getFromCache(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);
|
|
|
|
DispatchMessage.sendToAllInRange(this, uom);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public final void updateName() {
|
|
|
|
|
|
|
|
UpdateObjectMsg uom = new UpdateObjectMsg(this, 2);
|
|
|
|
DispatchMessage.sendToAllInRange(this, uom);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public final void refreshGuild() {
|
|
|
|
|
|
|
|
UpdateObjectMsg uom = new UpdateObjectMsg(this, 5);
|
|
|
|
DispatchMessage.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);
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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), 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.get(this.getObjectUUID());
|
|
|
|
|
|
|
|
if (this.friends == null) {
|
|
|
|
this.friends = new ConcurrentHashMap<>();
|
|
|
|
BuildingManager._buildingFriends.put(this.getObjectUUID(), this.friends);
|
|
|
|
}
|
|
|
|
|
|
|
|
this.condemned = BuildingManager._buildingCondemned.get(this.getObjectUUID());
|
|
|
|
|
|
|
|
if (this.condemned == null) {
|
|
|
|
this.condemned = new ConcurrentHashMap<>();
|
|
|
|
BuildingManager._buildingCondemned.put(this.getObjectUUID(), this.condemned);
|
|
|
|
}
|
|
|
|
|
|
|
|
//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());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
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 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(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 void AddToBarracksList() {
|
|
|
|
City playerCity = ZoneManager.getCityAtLocation(this.loc);
|
|
|
|
if (playerCity != null) {
|
|
|
|
playerCity.cityBarracks.add(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public void RemoveFromBarracksList() {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|