// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // 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.gameManager.*; import engine.math.Bounds; import engine.math.FastMath; import engine.math.Vector2f; import engine.math.Vector3fImmutable; import engine.net.ByteBufferWriter; import engine.net.Dispatch; import engine.net.DispatchMessage; import engine.net.client.msg.ErrorPopupMsg; import engine.net.client.msg.TaxResourcesMsg; import engine.net.client.msg.ViewResourcesMessage; import engine.powers.EffectsBase; import engine.server.MBServerStatics; import engine.workthreads.DestroyCityThread; import engine.workthreads.TransferCityThread; import org.pmw.tinylog.Logger; import java.net.UnknownHostException; import java.sql.ResultSet; import java.sql.SQLException; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.locks.ReentrantReadWriteLock; public class City extends AbstractWorldObject { private String cityName; private String motto; private String description; public java.time.LocalDateTime established; private int isNoobIsle; //1: noob, 0: not noob: -1: not noob, no teleport private int population = 0; private int siegesWithstood = 0; private int realmID; private int radiusType; private float bindRadius; private float statLat; private float statAlt; private float statLon; private float bindX; private float bindZ; private byte isNpc; //aka Safehold private byte isCapital = 0; private byte isSafeHold; private boolean forceRename = false; public boolean hasBeenTransfered = false; private boolean noTeleport = false; //used by npc cities private boolean noRepledge = false; //used by npc cities private boolean isOpen = false; private int treeOfLifeID; private Vector3fImmutable location = Vector3fImmutable.ZERO; private Vector3fImmutable bindLoc; protected Zone parentZone; private int warehouseBuildingID = 0; private boolean open = false; private boolean reverseKOS = false; public static long lastCityUpdate = 0; public LocalDateTime realmTaxDate; public ReentrantReadWriteLock transactionLock = new ReentrantReadWriteLock(); // Players who have entered the city (used for adding and removing affects) private final HashSet _playerMemory = new HashSet<>(); public volatile boolean protectionEnforced = true; private String hash; public ArrayListcityBarracks; public ArrayList cityOutlaws; /** * ResultSet Constructor */ public City(ResultSet rs) throws SQLException, UnknownHostException { super(rs); try{ this.cityName = rs.getString("name"); this.motto = rs.getString("motto"); this.isNpc = rs.getByte("isNpc"); this.isSafeHold = (byte) ((this.isNpc == 1) ? 1 : 0); this.description = ""; // TODO Implement this! this.isNoobIsle = rs.getByte("isNoobIsle"); // Noob this.gridObjectType = GridObjectType.STATIC; // Island // City(00000001), // Otherwise(FFFFFFFF) this.population = rs.getInt("population"); this.siegesWithstood = rs.getInt("siegesWithstood"); java.sql.Timestamp establishedTimeStamp = rs.getTimestamp("established"); if (establishedTimeStamp != null) this.established = java.time.LocalDateTime.ofInstant(establishedTimeStamp.toInstant(), ZoneId.systemDefault()); this.location = new Vector3fImmutable(rs.getFloat("xCoord"), rs.getFloat("yCoord"), rs.getFloat("zCoord")); this.statLat = rs.getFloat("xCoord"); this.statAlt = rs.getFloat("yCoord"); this.statLon = rs.getFloat("zCoord"); java.sql.Timestamp realmTaxTimeStamp = rs.getTimestamp("realmTaxDate"); if (realmTaxTimeStamp != null) this.realmTaxDate = realmTaxTimeStamp.toLocalDateTime(); if (this.realmTaxDate == null) this.realmTaxDate = LocalDateTime.now(); this.treeOfLifeID = rs.getInt("treeOfLifeUUID"); this.bindX = rs.getFloat("bindX"); this.bindZ = rs.getFloat("bindZ"); this.bindLoc = new Vector3fImmutable(this.location.getX() + this.bindX, this.location.getY(), this.location.getZ() + this.bindZ); this.radiusType = rs.getInt("radiusType"); float bindradiustemp = rs.getFloat("bindRadius"); if (bindradiustemp > 2) bindradiustemp -=2; this.bindRadius = bindradiustemp; this.forceRename = rs.getInt("forceRename") == 1; this.open = rs.getInt("open") == 1; if (this.cityName.equals("Perdition") || this.cityName.equals("Bastion")) { this.noTeleport = true; this.noRepledge = true; } else { this.noTeleport = false; this.noRepledge = false; } this.hash = rs.getString("hash"); if (this.motto.isEmpty()){ Guild guild = this.getGuild(); if (guild != null && guild.isEmptyGuild() == false) this.motto = guild.getMotto(); } //Disabled till i finish. // this.reverseKOS = rs.getInt("kos") == 1; Zone zone = ZoneManager.getZoneByUUID(rs.getInt("parent")); if (zone != null) setParent(zone); //npc cities without heightmaps except swampstone are specials. this.realmID = rs.getInt("realmID"); }catch(Exception e){ Logger.error(e); } // *** Refactor: Is this working? Intended to supress // login server errors from attempting to // load cities/realms along with players } /* * Utils */ public boolean renameCity(String cityName){ if (!DbManager.CityQueries.renameCity(this, cityName)) return false; if (!DbManager.CityQueries.updateforceRename(this, false)) return false; this.cityName = cityName; this.forceRename = false; return true; } public boolean updateTOL(Building tol){ if (tol == null) return false; if (!DbManager.CityQueries.updateTOL(this, tol.getObjectUUID())) return false; this.treeOfLifeID = tol.getObjectUUID(); return true; } public boolean renameCityForNewPlant(String cityName){ if (!DbManager.CityQueries.renameCity(this, cityName)) return false; if (!DbManager.CityQueries.updateforceRename(this, true)) return false; this.cityName = cityName; this.forceRename = true; return true; } public void setForceRename(boolean forceRename) { if (!DbManager.CityQueries.updateforceRename(this, forceRename)) return; this.forceRename = forceRename; } public String getCityName() { return cityName; } public String getMotto() { return motto; } public String getDescription() { return description; } public Building getTOL() { if (this.treeOfLifeID == 0) return null; return BuildingManager.getBuildingFromCache(this.treeOfLifeID); } public int getIsNoobIsle() { return isNoobIsle; } public int getPopulation() { return population; } public int getSiegesWithstood() { return siegesWithstood; } public float getLatitude() { return this.location.x; } public float getLongitude() { return this.location.z; } public float getAltitude() { return this.location.y; } @Override public Vector3fImmutable getLoc() { return this.location; } public byte getIsNpcOwned() { return isNpc; } public byte getIsSafeHold() { return this.isSafeHold; } public boolean isSafeHold() { return (this.isSafeHold == (byte) 1); } public byte getIsCapital() { return isCapital; } public void setIsCapital(boolean state) { this.isCapital = (state) ? (byte) 1 : (byte) 0; } public int getRadiusType() { return this.radiusType; } public float getBindRadius() { return this.bindRadius; } public int getRank() { return (this.getTOL() == null) ? 0 : this.getTOL().getRank(); } public Bane getBane() { return Bane.getBane(this.getObjectUUID()); } public void setParent(Zone zone) { try { this.parentZone = zone; this.location = new Vector3fImmutable(zone.absX + statLat, zone.absY + statAlt, zone.absZ + statLon); this.bindLoc = new Vector3fImmutable(this.location.x + this.bindX, this.location.y, this.location.z + this.bindZ); // set city bounds Bounds cityBounds = Bounds.borrow(); cityBounds.setBounds(new Vector2f(this.location.x + 64, this.location.z + 64), // location x and z are offset by 64 from the center of the city. new Vector2f(Enum.CityBoundsType.GRID.extents, Enum.CityBoundsType.GRID.extents), 0.0f); this.setBounds(cityBounds); if (zone.getHeightMap() == null && this.isNpc == 1 && this.getObjectUUID() != 1213 ){ HeightMap.GenerateCustomHeightMap(zone); Logger.info(zone.getName() + " created custom heightmap"); } }catch(Exception e){ Logger.error(e); } } public Zone getParent() { return this.parentZone; } public boolean isCityZone(Zone zone) { if (zone == null || this.parentZone == null) return false; return zone.getObjectUUID() == this.parentZone.getObjectUUID(); } public AbstractCharacter getOwner() { if (this.getTOL() == null) return null; int ownerID = this.getTOL().getOwnerUUID(); if (ownerID == 0) return null; if (this.isNpc == 1) return NPC.getNPC(ownerID); else return PlayerCharacter.getPlayerCharacter(ownerID); } public Guild getGuild() { if (this.getTOL() == null) return null; if (this.isNpc == 1) { if (this.getTOL().getOwner() == null) return null; return this.getTOL().getOwner().getGuild(); } else { if (this.getTOL().getOwner() == null) return null; return this.getTOL().getOwner().getGuild(); } } public boolean openCity(boolean open){ if (!DbManager.CityQueries.updateOpenCity(this, open)) return false; this.open = open; return true; } public static void _serializeForClientMsg(City city, ByteBufferWriter writer) { City.serializeForClientMsg(city,writer); } /* * Serializing */ public static void serializeForClientMsg(City city, ByteBufferWriter writer) { AbstractCharacter guildRuler; Guild rulingGuild; Guild rulingNation; java.time.LocalDateTime dateTime1900; // Cities aren't a city without a TOL. Time to early exit. // No need to spam the log here as non-existant TOL's are indicated // during bootstrap routines. if (city.getTOL() == null){ Logger.error( "NULL TOL FOR " + city.cityName); } // Assign city owner if (city.getTOL() != null) guildRuler = city.getTOL().getOwner(); else guildRuler = null; // If is an errant tree, use errant guild for serialization. // otherwise we serialize the soverign guild if (guildRuler == null) rulingGuild = Guild.getErrantGuild(); else rulingGuild = guildRuler.getGuild(); rulingNation = rulingGuild.getNation(); // Begin Serialzing soverign guild data writer.putInt(city.getObjectType().ordinal()); writer.putInt(city.getObjectUUID()); writer.putString(city.cityName); writer.putInt(rulingGuild.getObjectType().ordinal()); writer.putInt(rulingGuild.getObjectUUID()); writer.putString(rulingGuild.getName()); writer.putString(city.motto); writer.putString(rulingGuild.getLeadershipType()); // Serialize guild ruler's name // If tree is abandoned blank out the name // to allow them a rename. if (guildRuler == null) writer.putString(""); else writer.putString(guildRuler.getFirstName() + ' ' + guildRuler.getLastName()); writer.putInt(rulingGuild.getCharter()); writer.putInt(0); // always 00000000 writer.put(city.isSafeHold); writer.put((byte) 1); writer.put((byte) 1); // *** Refactor: What are these flags? writer.put((byte) 1); writer.put((byte) 1); writer.put((byte) 1); GuildTag._serializeForDisplay(rulingGuild.getGuildTag(),writer); GuildTag._serializeForDisplay(rulingNation.getGuildTag(),writer); writer.putInt(0);// TODO Implement description text writer.put((byte) 1); if (city.isCapital > 0) writer.put((byte) 1); else writer.put((byte) 0); writer.put((byte) 1); // Begin serializing nation guild info if (rulingNation.isEmptyGuild()){ writer.putInt(rulingGuild.getObjectType().ordinal()); writer.putInt(rulingGuild.getObjectUUID()); } else{ writer.putInt(rulingNation.getObjectType().ordinal()); writer.putInt(rulingNation.getObjectUUID()); } // Serialize nation name if (rulingNation.isEmptyGuild()) writer.putString("None"); else writer.putString(rulingNation.getName()); writer.putInt(city.getTOL().getRank()); if (city.isNoobIsle > 0) writer.putInt(1); else writer.putInt(0xFFFFFFFF); writer.putInt(city.population); if (rulingNation.isEmptyGuild()) writer.putString(" "); else writer.putString(Guild.GetGL(rulingNation).getFirstName() + ' ' + Guild.GetGL(rulingNation).getLastName()); writer.putLocalDateTime(city.established); // writer.put((byte) city.established.getDayOfMonth()); // writer.put((byte) city.established.minusMonths(1).getMonth().getValue()); // writer.putInt((int) years); // writer.put((byte) hours); // writer.put((byte) minutes); // writer.put((byte) seconds); writer.putFloat(city.location.x); writer.putFloat(city.location.y); writer.putFloat(city.location.z); writer.putInt(city.siegesWithstood); writer.put((byte) 1); writer.put((byte) 0); writer.putInt(0x64); writer.put((byte) 0); writer.put((byte) 0); writer.put((byte) 0); } public static Vector3fImmutable getBindLoc(int cityID) { City city; city = City.getCity(cityID); if (city == null) return Enum.Ruins.getRandomRuin().getLocation(); return city.getBindLoc(); } public Vector3fImmutable getBindLoc() { Vector3fImmutable treeLoc = null; if (this.getTOL() != null && this.getTOL().getRank() == 8) treeLoc = this.getTOL().getStuckLocation(); if (treeLoc != null) return treeLoc; if (this.radiusType == 1 && this.bindRadius > 0f) { //square radius float x = this.bindLoc.getX(); float z = this.bindLoc.getZ(); float offset = ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * this.bindRadius; int direction = ThreadLocalRandom.current().nextInt(4); switch (direction) { case 0: x += this.bindRadius; z += offset; break; case 1: x += offset; z -= this.bindRadius; break; case 2: x -= this.bindRadius; z += offset; break; case 3: x += offset; z += this.bindRadius; break; } return new Vector3fImmutable(x, this.bindLoc.getY(), z); } else if (this.radiusType == 2 && this.bindRadius > 0f) { //circle radius Vector3fImmutable dir = FastMath.randomVector2D(); return this.bindLoc.scaleAdd(this.bindRadius, dir); } else if (this.radiusType == 3 && this.bindRadius > 0f) { //random inside square float x = this.bindLoc.getX(); x += ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * this.bindRadius; float z = this.bindLoc.getZ(); z += ((ThreadLocalRandom.current().nextFloat() * 2) - 1) * this.bindRadius; return new Vector3fImmutable(x, this.bindLoc.getY(), z); } else if (this.radiusType == 4 && this.bindRadius > 0f) { //random inside circle Vector3fImmutable dir = FastMath.randomVector2D(); return this.bindLoc.scaleAdd(ThreadLocalRandom.current().nextFloat() * this.bindRadius, dir); } else //spawn at bindLoc //System.out.println("x: " + this.bindLoc.x + ", z: " + this.bindLoc.z); return this.bindLoc; } public static ArrayList getCitiesToTeleportTo(PlayerCharacter pc) { ArrayList cities = new ArrayList<>(); if (pc == null) return cities; Guild pcG = pc.getGuild(); ConcurrentHashMap worldCities = DbManager.getMap(Enum.GameObjectType.City); //add npc cities for (AbstractGameObject ago : worldCities.values()) { if (ago.getObjectType().equals(GameObjectType.City)) { City city = (City) ago; if (city.noTeleport) continue; if (city.parentZone != null && city.parentZone.isPlayerCity()) { if (pc.getAccount().status.equals(AccountStatus.ADMIN)) { cities.add(city); } else //list Player cities //open city, just list if (city.open && city.getTOL() != null && city.getTOL().getRank() > 4) { if (!BuildingManager.IsPlayerHostile(city.getTOL(), pc)) cities.add(city); //verify nation or guild is same } else if (Guild.sameNationExcludeErrant(city.getGuild(), pcG)) cities.add(city); } else if (city.isNpc == 1) { //list NPC cities Guild g = city.getGuild(); if (g == null) { if (city.isNpc == 1) if (city.isNoobIsle == 1) { if (pc.getLevel() < 21) cities.add(city); } else if (pc.getLevel() > 9) cities.add(city); } else if (pc.getLevel() >= g.getTeleportMin() && pc.getLevel() <= g.getTeleportMax()){ cities.add(city); } } } } return cities; } public NPC getRuneMaster() { NPC outNPC = null; if (this.getTOL() == null) return outNPC; for (AbstractCharacter npc : getTOL().getHirelings().keySet()) { if (npc.getObjectType() == GameObjectType.NPC) if (((NPC)npc).getContract().isRuneMaster() == true) outNPC = (NPC)npc; } return outNPC; } public static ArrayList getCitiesToRepledgeTo(PlayerCharacter pc) { ArrayList cities = new ArrayList<>(); if (pc == null) return cities; Guild pcG = pc.getGuild(); ConcurrentHashMap worldCities = DbManager.getMap(Enum.GameObjectType.City); //add npc cities for (AbstractGameObject ago : worldCities.values()) { if (ago.getObjectType().equals(GameObjectType.City)) { City city = (City) ago; if (city.noRepledge) continue; if (city.parentZone != null && city.parentZone.isPlayerCity()) { //list Player cities //open city, just list if (pc.getAccount().status.equals(AccountStatus.ADMIN)) { cities.add(city); } else if (city.open && city.getTOL() != null && city.getTOL().getRank() > 4) { if (!BuildingManager.IsPlayerHostile(city.getTOL(), pc)) cities.add(city); //verify nation or guild is same } else if (Guild.sameNationExcludeErrant(city.getGuild(), pcG)) cities.add(city); } else if (city.isNpc == 1) { //list NPC cities Guild g = city.getGuild(); if (g == null) { if (city.isNpc == 1) if (city.isNoobIsle == 1) { if (pc.getLevel() < 21) cities.add(city); } else if (pc.getLevel() > 9) cities.add(city); } else if (pc.getLevel() >= g.getRepledgeMin() && pc.getLevel() <= g.getRepledgeMax()){ cities.add(city); } } } } return cities; } public boolean isOpen() { return open; } public static void loadCities(Zone zone) { ArrayList cities = DbManager.CityQueries.GET_CITIES_BY_ZONE(zone.getObjectUUID()); for (City city : cities) { city.setParent(zone); city.setObjectTypeMask(MBServerStatics.MASK_CITY); city.setLoc(city.location); //not player city, must be npc city.. if (!zone.isPlayerCity()) zone.setNPCCity(true); if ((ConfigManager.serverType.equals(ServerType.WORLDSERVER)) && (city.hash == null)) { city.setHash(); if (DataWarehouse.recordExists(Enum.DataRecordType.CITY, city.getObjectUUID()) == false) { CityRecord cityRecord = CityRecord.borrow(city, Enum.RecordEventType.CREATE); DataWarehouse.pushToWarehouse(cityRecord); } } } } @Override public void updateDatabase() { // TODO Create update logic. } public static City getCity(int cityId) { if (cityId == 0) return null; City city = (City) DbManager.getFromCache(Enum.GameObjectType.City, cityId); if (city != null) return city; return DbManager.CityQueries.GET_CITY(cityId); } public static City GetCityFromCache(int cityId) { if (cityId == 0) return null; return (City) DbManager.getFromCache(Enum.GameObjectType.City, cityId); } @Override public void runAfterLoad() { // Set city bounds // *** Note: Moved to SetParent() // for some undocumented reason // Set city motto to current guild motto if (BuildingManager.getBuilding(this.treeOfLifeID) == null) Logger.info( "City UID " + this.getObjectUUID() + " Failed to Load Tree of Life with ID " + this.treeOfLifeID); if ((ConfigManager.serverType.equals(ServerType.WORLDSERVER)) && (this.isNpc == (byte) 0)) { Realm wsr = Realm.getRealm(this.realmID); if (wsr != null) wsr.addCity(this.getObjectUUID()); else Logger.error("Unable to find realm of ID " + realmID + " for city " + this.getObjectUUID()); } if (this.getGuild() != null) { this.motto = this.getGuild().getMotto(); // Determine if this city is a nation capitol if (this.getGuild().getGuildState() == GuildState.Nation) for (Guild sub : this.getGuild().getSubGuildList()) { if ( (sub.getGuildState() == GuildState.Protectorate) || (sub.getGuildState() == GuildState.Province)) this.isCapital = 1; } ArrayList guildList = Guild.GuildRoster(this.getGuild()); this.population = guildList.size(); } // Banes are loaded for this city from the database at this point if (this.getBane() == null) return; // if this city is baned, add the siege effect try { this.getTOL().addEffectBit((1 << 16)); this.getBane().getStone().addEffectBit((1 << 19));; }catch(Exception e){ Logger.info("Failed ao add bane effects on city." + e.getMessage()); } } public void addCityEffect(EffectsBase effectBase, int rank) { HashSet currentPlayers; PlayerCharacter player; // Add this new effect to the current city effect collection. // so any new player to the grid will have all effects applied this.addEffectNoTimer(Integer.toString(effectBase.getUUID()), effectBase, rank, false); // Any players currently in the zone will not be processed by the heartbeat // if it's not the first effect toggled so we do it here manually currentPlayers = WorldGrid.getObjectsInRangePartial(this.location, this.parentZone.getBounds().getHalfExtents().x * 1.2f, MBServerStatics.MASK_PLAYER); for (AbstractWorldObject playerObject : currentPlayers) { if (playerObject == null) continue; if (!this.isLocationWithinSiegeBounds(playerObject.getLoc())) continue; player = (PlayerCharacter) playerObject; player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, rank, MBServerStatics.FOURTYFIVE_SECONDS, true,this); } } public void removeCityEffect(EffectsBase effectBase, int rank, boolean refreshEffect) { PlayerCharacter player; // Remove the city effect from the ago's internal collection if (this.getEffects().containsKey(Integer.toString(effectBase.getUUID()))) this.getEffects().remove(Integer.toString(effectBase.getUUID())); // Any players currently in the zone will not be processed by the heartbeat // so we do it here manually for (Integer playerID : this._playerMemory) { player = PlayerCharacter.getFromCache(playerID); if (player == null) continue; player.endEffectNoPower(Integer.toString(effectBase.getUUID())); // Reapply effect with timeout? if (refreshEffect == true) player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, rank, MBServerStatics.FOURTYFIVE_SECONDS, false,this); } } public Warehouse getWarehouse() { if (this.warehouseBuildingID == 0) return null; return Warehouse.warehouseByBuildingUUID.get(this.warehouseBuildingID); } public Realm getRealm() { return Realm.getRealm(this.realmID); } public boolean isLocationOnCityGrid(Vector3fImmutable insideLoc) { Bounds newBounds = Bounds.borrow(); newBounds.setBounds(insideLoc); boolean collided = Bounds.collide(this.getBounds(), newBounds,0); newBounds.release(); return collided; } public boolean isLocationOnCityGrid(Bounds newBounds) { boolean collided = Bounds.collide(this.getBounds(), newBounds,0); return collided; } public boolean isLocationWithinSiegeBounds(Vector3fImmutable insideLoc) { return insideLoc.isInsideCircle(this.getLoc(), CityBoundsType.SIEGE.extents); } public boolean isLocationOnCityZone(Vector3fImmutable insideLoc) { return Bounds.collide(insideLoc, this.parentZone.getBounds()); } private void applyAllCityEffects(PlayerCharacter player) { Effect effect; EffectsBase effectBase; try { for (String cityEffect : this.getEffects().keySet()) { effect = this.getEffects().get(cityEffect); effectBase = effect.getEffectsBase(); if (effectBase == null) continue; player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, effect.getTrains(), MBServerStatics.FOURTYFIVE_SECONDS, true,this); } } catch (Exception e) { Logger.error( e.getMessage()); } } private void removeAllCityEffects(PlayerCharacter player,boolean force) { Effect effect; EffectsBase effectBase; try { for (String cityEffect : this.getEffects().keySet()) { effect = this.getEffects().get(cityEffect); effectBase = effect.getEffectsBase(); if (player.getEffects().get(cityEffect) == null) return; // player.endEffectNoPower(cityEffect); player.addCityEffect(Integer.toString(effectBase.getUUID()), effectBase, effect.getTrains(), MBServerStatics.FOURTYFIVE_SECONDS, false,this); } } catch (Exception e) { Logger.error(e.getMessage()); } } public void onEnter() { HashSet currentPlayers; HashSet currentMemory; PlayerCharacter player; // Gather current list of players within a distance defined by the seige bounds currentPlayers = WorldGrid.getObjectsInRangePartial(this.location, CityBoundsType.SIEGE.extents, MBServerStatics.MASK_PLAYER); currentMemory = new HashSet<>(); for (AbstractWorldObject playerObject : currentPlayers) { if (playerObject == null) continue; player = (PlayerCharacter) playerObject; currentMemory.add(player.getObjectUUID()); // Player is already in our memory if (_playerMemory.contains(player.getObjectUUID())) continue; if (!this.isLocationWithinSiegeBounds(player.getLoc())) continue; // Apply safehold affect to player if needed if ((this.isSafeHold == 1)) player.setSafeZone(true); //add spire effects. if (this.getEffects().size() > 0) this.applyAllCityEffects(player); // Add player to our city's memory _playerMemory.add(player.getObjectUUID()); // ***For debugging // Logger.info("PlayerMemory for ", this.getCityName() + ": " + _playerMemory.size()); } try { onExit(currentMemory); } catch (Exception e) { Logger.error( e.getMessage()); } } /* All characters in city player memory but * not the current memory have obviously * left the city. Remove their affects. */ private void onExit(HashSet currentMemory) { PlayerCharacter player; int playerUUID = 0; HashSet toRemove = new HashSet<>(); Iterator iter = _playerMemory.iterator(); while (iter.hasNext()) { playerUUID = iter.next(); player = PlayerCharacter.getFromCache(playerUUID); if (this.isLocationWithinSiegeBounds(player.getLoc())) continue; // Remove players safezone status if warranted // they can assumed to be not on the citygrid at // this point. player.setSafeZone(false); this.removeAllCityEffects(player,false); // We will remove this player after iteration is complete // so store it in a temporary collection toRemove.add(playerUUID); if(cityOutlaws.contains(playerUUID)) cityOutlaws.remove(playerUUID); // ***For debugging // Logger.info("PlayerMemory for ", this.getCityName() + ": " + _playerMemory.size()); } // Remove players from city memory _playerMemory.removeAll(toRemove); } public int getWarehouseBuildingID() { return warehouseBuildingID; } public void setWarehouseBuildingID(int warehouseBuildingID) { this.warehouseBuildingID = warehouseBuildingID; } public final void destroy() { Thread destroyCityThread = new Thread(new DestroyCityThread(this)); destroyCityThread.setName("deestroyCity:" + this.getName()); destroyCityThread.start(); } public final void transfer(AbstractCharacter newOwner) { Thread transferCityThread = new Thread(new TransferCityThread(this, newOwner)); transferCityThread.setName("TransferCity:" + this.getName()); transferCityThread.start(); } public final void claim(AbstractCharacter sourcePlayer) { Guild sourceNation; Guild sourceGuild; Zone cityZone; sourceGuild = sourcePlayer.getGuild(); if (sourceGuild == null) return; sourceNation = sourcePlayer.getGuild().getNation(); if (sourceGuild.isEmptyGuild()) return; //cant claim tree with owned tree. if (sourceGuild.getOwnedCity() != null) return; cityZone = this.parentZone; // Can't claim a tree not in a player city zone // Reset sieges withstood this.setSiegesWithstood(0); this.hasBeenTransfered = true; // If currently a sub of another guild, desub when // claiming your new tree and set as Landed if (!sourceNation.isEmptyGuild() && sourceNation != sourceGuild) { if (!DbManager.GuildQueries.UPDATE_PARENT(sourceGuild.getObjectUUID(), MBServerStatics.worldUUID)) { ChatManager.chatGuildError((PlayerCharacter) sourcePlayer, "A Serious error has occurred. Please post details for to ensure transaction integrity"); return; } sourceNation.getSubGuildList().remove(sourceGuild); if (sourceNation.getSubGuildList().isEmpty()) sourceNation.downgradeGuildState(); } // Link the mew guild with the tree if (!DbManager.GuildQueries.SET_GUILD_OWNED_CITY(sourceGuild.getObjectUUID(), this.getObjectUUID())) { ChatManager.chatGuildError((PlayerCharacter) sourcePlayer, "A Serious error has occurred. Please post details for to ensure transaction integrity"); return; } sourceGuild.setCityUUID(this.getObjectUUID()); sourceGuild.setNation(sourceGuild); sourceGuild.setGuildState(GuildState.Sovereign); GuildManager.updateAllGuildTags(sourceGuild); GuildManager.updateAllGuildBinds(sourceGuild, this); // Build list of buildings within this parent zone for (Building cityBuilding : cityZone.zoneBuildingSet) { // Buildings without blueprints are unclaimable if (cityBuilding.getBlueprintUUID() == 0) continue; // All protection contracts are void upon transfer of a city // All protection contracts are void upon transfer of a city //Dont forget to not Flip protection on Banestones and siege Equipment... Noob. if (cityBuilding.getBlueprint() != null && !cityBuilding.getBlueprint().isSiegeEquip() && cityBuilding.getBlueprint().getBuildingGroup() != BuildingGroup.BANESTONE) cityBuilding.setProtectionState(ProtectionState.NONE); // Transfer ownership of valid city assets // these assets are autoprotected. if ((cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE) || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK) || (cityBuilding.getBlueprint().isWallPiece()) || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE)) { cityBuilding.claim(sourcePlayer); cityBuilding.setProtectionState(ProtectionState.PROTECTED); } } this.setForceRename(true); // Reset city timer for map update City.lastCityUpdate = System.currentTimeMillis(); } public final boolean transferGuildLeader(PlayerCharacter sourcePlayer) { Guild sourceGuild; Zone cityZone; sourceGuild = sourcePlayer.getGuild(); if (sourceGuild == null) return false; if (sourceGuild.isEmptyGuild()) return false; cityZone = this.parentZone; for (Building cityBuilding : cityZone.zoneBuildingSet) { // Buildings without blueprints are unclaimable if (cityBuilding.getBlueprintUUID() == 0) continue; // All protection contracts are void upon transfer of a city //Dont forget to not Flip protection on Banestones and siege Equipment... Noob. // Transfer ownership of valid city assets // these assets are autoprotected. if ((cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.TOL) || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SPIRE) || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.BARRACK) || (cityBuilding.getBlueprint().isWallPiece()) || (cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.SHRINE) ) { cityBuilding.claim(sourcePlayer); cityBuilding.setProtectionState(ProtectionState.PROTECTED); } else if(cityBuilding.getBlueprint().getBuildingGroup() == BuildingGroup.WAREHOUSE) cityBuilding.claim(sourcePlayer); } this.setForceRename(true); CityRecord cityRecord = CityRecord.borrow(this, Enum.RecordEventType.TRANSFER); DataWarehouse.pushToWarehouse(cityRecord); return true; } /** * @return the forceRename */ public boolean isForceRename() { return forceRename; } /** * @param siegesWithstood the siegesWithstood to set */ public void setSiegesWithstood(int siegesWithstood) { // early exit if setting to current value if (this.siegesWithstood == siegesWithstood) return; if (DbManager.CityQueries.updateSiegesWithstood(this, siegesWithstood) == true) this.siegesWithstood = siegesWithstood; else Logger.error("Error when writing to database for cityUUID: " + this.getObjectUUID()); } /** * @param population the population to set */ public void setPopulation(int population) { this.population = population; } public boolean isReverseKOS() { return reverseKOS; } public void setReverseKOS(boolean reverseKOS) { this.reverseKOS = reverseKOS; } public String getHash() { return hash; } public void setHash(String hash) { this.hash = hash; } public void setHash() { this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID()); // Write hash to player character table DataWarehouse.writeHash(Enum.DataRecordType.CITY, this.getObjectUUID()); } public boolean setRealmTaxDate(LocalDateTime realmTaxDate) { if (!DbManager.CityQueries.updateRealmTaxDate(this, realmTaxDate)) return false; this.realmTaxDate = realmTaxDate; return true; } //TODO use this for taxing later. // public boolean isAfterTaxPeriod(LocalDateTime dateTime,PlayerCharacter player){ // if (dateTime.isBefore(realmTaxDate)){ // String wait = ""; // float hours = 1000*60*60; // float seconds = 1000; // float hoursUntil = realmTaxDate.minus(dateTime.get).getMillis() /hours; // int secondsUntil = (int) (realmTaxDate.minus(dateTime.getMillis()).getMillis() /seconds); // if (hoursUntil < 1) // wait = "You must wait " + secondsUntil + " seconds before taxing this city again!"; // else // wait = "You must wait " + hoursUntil + " hours before taxing this city again!"; // ErrorPopupMsg.sendErrorMsg(player, wait); // return false; // } // // return true; // } public synchronized boolean TaxWarehouse(TaxResourcesMsg msg,PlayerCharacter player) { // Member variable declaration Building building = BuildingManager.getBuildingFromCache(msg.getBuildingID()); Guild playerGuild = player.getGuild(); if (building == null){ ErrorPopupMsg.sendErrorMsg(player, "Not a valid Building!"); return true; } City city = building.getCity(); if (city == null){ ErrorPopupMsg.sendErrorMsg(player, "This building does not belong to a city."); return true; } if (playerGuild == null || playerGuild.isEmptyGuild()){ ErrorPopupMsg.sendErrorMsg(player, "You must belong to a guild to do that!"); return true; } if (playerGuild.getOwnedCity() == null){ ErrorPopupMsg.sendErrorMsg(player, "Your Guild needs to own a city!"); return true; } if (playerGuild.getOwnedCity().getTOL() == null){ ErrorPopupMsg.sendErrorMsg(player, "Cannot find Tree of Life for your city!"); return true; } if (playerGuild.getOwnedCity().getTOL().getRank() != 8){ ErrorPopupMsg.sendErrorMsg(player, "Your City needs to Own a realm!"); return true; } if (playerGuild.getOwnedCity().getRealm() == null){ ErrorPopupMsg.sendErrorMsg(player, "Cannot find realm for your city!"); return true; } Realm targetRealm = RealmMap.getRealmForCity(city); if (targetRealm == null){ ErrorPopupMsg.sendErrorMsg(player, "Cannot find realm for city you are attempting to tax!"); return true; } if (targetRealm.getRulingCity() == null){ ErrorPopupMsg.sendErrorMsg(player, "Realm Does not have a ruling city!"); return true; } if (targetRealm.getRulingCity().getObjectUUID() != playerGuild.getOwnedCity().getObjectUUID()){ ErrorPopupMsg.sendErrorMsg(player, "Your guild does not rule this realm!"); return true; } if (playerGuild.getOwnedCity().getObjectUUID() == city.getObjectUUID()){ ErrorPopupMsg.sendErrorMsg(player, "You cannot tax your own city!"); return true; } if (!GuildStatusController.isTaxCollector(player.getGuildStatus())){ ErrorPopupMsg.sendErrorMsg(player, "You Must be a tax Collector!"); return true; } if (this.realmTaxDate.isAfter(LocalDateTime.now())) return true; if (msg.getResources().size() == 0) return true; if (city.getWarehouse() == null) return true; Warehouse ruledWarehouse = playerGuild.getOwnedCity().getWarehouse(); if (ruledWarehouse == null) return true; ItemBase.getItemHashIDMap(); ArrayListresources = new ArrayList<>(); float taxPercent = msg.getTaxPercent(); if (taxPercent > 20) taxPercent = .20f; for (int resourceHash:msg.getResources().keySet()){ if (ItemBase.getItemHashIDMap().get(resourceHash) != null) resources.add(ItemBase.getItemHashIDMap().get(resourceHash)); } for (Integer itemBaseID:resources){ ItemBase ib = ItemBase.getItemBase(itemBaseID); if (ib == null) continue; if (ruledWarehouse.isAboveCap(ib, (int) (city.getWarehouse().getResources().get(ib) * taxPercent))){ ErrorPopupMsg.sendErrorMsg(player, "You're warehouse has enough " + ib.getName() + " already!"); return true; } } if(!city.setRealmTaxDate(LocalDateTime.now().plusDays(7))){ ErrorPopupMsg.sendErrorMsg(player, "Failed to Update next Tax Date due to internal Error. City was not charged taxes this time."); return false; } try{ city.getWarehouse().transferResources(player,msg,resources, taxPercent,ruledWarehouse); }catch(Exception e){ Logger.info( e.getMessage()); } // Member variable assignment ViewResourcesMessage vrm = new ViewResourcesMessage(player); vrm.setGuild(building.getGuild()); vrm.setWarehouseBuilding(BuildingManager.getBuildingFromCache(building.getCity().getWarehouse().getBuildingUID())); vrm.configure(); Dispatch dispatch = Dispatch.borrow(player, vrm); DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); dispatch = Dispatch.borrow(player, msg); DispatchMessage.dispatchMsgDispatch(dispatch, Enum.DispatchChannel.SECONDARY); return true; } }