// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.objects; import engine.Enum; import engine.Enum.ProtectionState; import engine.Enum.SiegePhase; import engine.Enum.SiegeResult; import engine.InterestManagement.WorldGrid; import engine.db.archive.BaneRecord; import engine.db.archive.DataWarehouse; import engine.gameManager.BuildingManager; import engine.gameManager.ChatManager; import engine.gameManager.DbManager; import engine.gameManager.ZoneManager; import engine.job.JobScheduler; import engine.jobs.ActivateBaneJob; import engine.jobs.BaneDefaultTimeJob; import engine.math.Vector3fImmutable; import engine.net.DispatchMessage; import engine.net.client.ClientConnection; import engine.net.client.msg.PlaceAssetMsg; import engine.net.client.msg.chat.ChatSystemMsg; import engine.server.MBServerStatics; import org.joda.time.DateTime; import org.pmw.tinylog.Logger; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.concurrent.ConcurrentHashMap; public final class Bane { public static ConcurrentHashMap banes = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD, MBServerStatics.CHM_THREAD_LOW); private final int cityUUID; private final int stoneUUID; private int ownerUUID; private DateTime placementDate = null; private DateTime liveDate = null; private BaneDefaultTimeJob defaultTimeJob; // Internal cache for banes private ActivateBaneJob activateBaneJob; /** * ResultSet Constructor */ public Bane(ResultSet rs) throws SQLException { Date sqlDateTime; ActivateBaneJob abtj; this.cityUUID = rs.getInt("cityUUID"); this.ownerUUID = rs.getInt("ownerUUID"); this.stoneUUID = rs.getInt("stoneUUID"); sqlDateTime = rs.getTimestamp("placementDate"); if (sqlDateTime != null) this.placementDate = new DateTime(sqlDateTime); sqlDateTime = rs.getTimestamp("liveDate"); if (sqlDateTime != null) this.liveDate = new DateTime(sqlDateTime); switch (this.getSiegePhase()) { case CHALLENGE: break; case WAR: // cancel old job if exists if (this.activateBaneJob != null) this.activateBaneJob.cancelJob(); abtj = new ActivateBaneJob(cityUUID); JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis()); this.activateBaneJob = abtj; break; case STANDOFF: // cancel old job if exists if (this.activateBaneJob != null) this.activateBaneJob.cancelJob(); abtj = new ActivateBaneJob(cityUUID); JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis()); this.activateBaneJob = abtj; break; } if (this.liveDate == null) setDefaultTime(); } public static boolean summonBanestone(PlayerCharacter player, ClientConnection origin, int rank) { Guild baningGuild; Zone cityZone; City targetCity; ArrayList nationBanes; baningGuild = player.getGuild(); if (baningGuild.getNation().isEmptyGuild()) { PlaceAssetMsg.sendPlaceAssetError(origin, 55, ""); // You must be in a Nation return false; } if (baningGuild.getNation().isNPCGuild()) { PlaceAssetMsg.sendPlaceAssetError(origin, 72, ""); // Cannot be in an NPC nation return false; } if (GuildStatusController.isInnerCouncil(player.getGuildStatus()) == false) { PlaceAssetMsg.sendPlaceAssetError(origin, 10, ""); // You must be a guild leader return false; } // Cannot place banestone underwater; if (ZoneManager.isLocUnderwater(player.getLoc())) { PlaceAssetMsg.sendPlaceAssetError(origin, 6, ""); // Cannot place underwater return false; } // figure out which city zone we are standing on. targetCity = ZoneManager.getCityAtLocation(player.getLoc()); if (targetCity == null) { PlaceAssetMsg.sendPlaceAssetError(origin, 59, ""); // No city to siege at this location! return false; } if (targetCity.isSafeHold()) { PlaceAssetMsg.sendPlaceAssetError(origin, 15, ""); // Cannot place assets in peace zone return false; } if (targetCity.getRank() > rank) { PlaceAssetMsg.sendPlaceAssetError(origin, 60, ""); // Bane rank is too low return false; } cityZone = targetCity.getParent(); // Cannot place assets on a dead tree if ((cityZone.isPlayerCity()) && (City.getCity(cityZone.getPlayerCityUUID()).getTOL().getRank() == -1)) { PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Cannot bane a dead tree!"); return false; } if (baningGuild.getNation() == targetCity.getGuild().getNation()) { PlaceAssetMsg.sendPlaceAssetError(origin, 20, ""); //Cannot bane yourself! return false; } if (targetCity.getTOL() == null) { PlaceAssetMsg.sendPlaceAssetError(origin, 65, ""); // Cannot find tree to target return false; } if (targetCity.getBane() != null) { PlaceAssetMsg.sendPlaceAssetError(origin, 23, ""); // Tree is already baned. return false; } if (getBaneByAttackerGuild(baningGuild) != null) { PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Your guild has already placed a bane!"); return false; } nationBanes = getBanesByNation(baningGuild.getNation()); // A nation can only have 3 concurrent banes if (nationBanes.size() == 3) { PlaceAssetMsg.sendPlaceAssetError(origin, 64, ""); // Your nation is already at war and your limit has been reached return false; } if (targetCity.isLocationOnCityGrid(player.getLoc()) == true) { PlaceAssetMsg.sendPlaceAssetError(origin, 1, "Cannot place banestone on city grid."); return false; } Blueprint blueprint = Blueprint.getBlueprint(24300); // Banestone //Let's drop a banestone! Vector3fImmutable localLocation = ZoneManager.worldToLocal(player.getLoc(), cityZone); if (localLocation == null) { PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); Logger.info("Failed to Convert World coordinates to local zone coordinates"); return false; } Building stone = DbManager.BuildingQueries.CREATE_BUILDING( cityZone.getObjectUUID(), player.getObjectUUID(), blueprint.getName(), blueprint.getBlueprintUUID(), localLocation, 1.0f, blueprint.getMaxHealth(rank), ProtectionState.PROTECTED, 0, rank, null, blueprint.getBlueprintUUID(), 1, 0.0f); if (stone == null) { PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); return false; } stone.addEffectBit((1 << 19)); stone.setMaxHitPoints(stone.getBlueprint().getMaxHealth(stone.getRank())); stone.setCurrentHitPoints(stone.getMaxHitPoints()); BuildingManager.setUpgradeDateTime(stone, null, 0); //Make the bane Bane bane = makeBane(player, targetCity, stone); if (bane == null) { //delete bane stone, failed to make bane object DbManager.BuildingQueries.DELETE_FROM_DATABASE(stone); PlaceAssetMsg.sendPlaceAssetError(player.getClientConnection(), 1, "A Serious error has occurred. Please post details for to ensure transaction integrity"); return false; } WorldGrid.addObject(stone, player); //Add bane effect to TOL targetCity.getTOL().addEffectBit((1 << 16)); targetCity.getTOL().updateEffects(); Vector3fImmutable movePlayerOutsideStone = player.getLoc(); movePlayerOutsideStone = movePlayerOutsideStone.setX(movePlayerOutsideStone.x + 10); movePlayerOutsideStone = movePlayerOutsideStone.setZ(movePlayerOutsideStone.z + 10); player.teleport(movePlayerOutsideStone); // Notify players ChatSystemMsg chatMsg = new ChatSystemMsg(null, "[Bane Channel] " + targetCity.getCityName() + " has been baned by " + baningGuild.getName() + ". Standoff phase has begun!"); chatMsg.setMessageType(4); chatMsg.setChannel(Enum.ChatChannelType.SYSTEM.getChannelID()); DispatchMessage.dispatchMsgToAll(chatMsg); // Push this event to the DataWarehouse BaneRecord baneRecord = BaneRecord.borrow(bane, Enum.RecordEventType.PENDING); DataWarehouse.pushToWarehouse(baneRecord); return true; } public static Bane getBane(int cityUUID) { Bane outBane; // Check cache first outBane = banes.get(cityUUID); // Last resort attempt to load from database if (outBane == null) outBane = DbManager.BaneQueries.LOAD_BANE(cityUUID); else return outBane; // As we loaded from the db, store it in the internal cache if (outBane != null) banes.put(cityUUID, outBane); return outBane; } public static Bane getBaneByAttackerGuild(Guild guild) { if (guild == null || guild.isEmptyGuild()) return null; ArrayList baneList; baneList = new ArrayList<>(banes.values()); for (Bane bane : baneList) { if (bane.getOwner().getGuild().equals(guild)) return bane; } return null; } public static ArrayList getBanesByNation(Guild guild) { ArrayList baneList; ArrayList returnList; baneList = new ArrayList<>(banes.values()); returnList = new ArrayList<>(); for (Bane bane : baneList) { if (bane.getOwner().getGuild().getNation().equals(guild)) returnList.add(bane); } return returnList; } public static void addBane(Bane bane) { Bane.banes.put(bane.cityUUID, bane); } public static Bane makeBane(PlayerCharacter owner, City city, Building stone) { Bane newBane; if (DbManager.BaneQueries.CREATE_BANE(city, owner, stone) == false) { Logger.error("Error writing to database"); return null; } newBane = DbManager.BaneQueries.LOAD_BANE(city.getObjectUUID()); return newBane; } //Call this to prematurely end a bane public SiegePhase getSiegePhase() { SiegePhase phase; if (this.liveDate == null) { phase = SiegePhase.CHALLENGE; //challenge return phase; } if (DateTime.now().isAfter(this.liveDate)) { phase = SiegePhase.WAR; //war return phase; } // If code reaches this point we are in standoff mode. phase = SiegePhase.STANDOFF; //standoff return phase; } // Cache access private void setDefaultTime() { DateTime timeToSetDefault = new DateTime(this.placementDate); timeToSetDefault = timeToSetDefault.plusDays(1); DateTime currentTime = DateTime.now(); DateTime defaultTime = new DateTime(this.placementDate); defaultTime = defaultTime.plusDays(2); defaultTime = defaultTime.hourOfDay().setCopy(22); defaultTime = defaultTime.minuteOfHour().setCopy(0); defaultTime = defaultTime.secondOfMinute().setCopy(0); if (currentTime.isAfter(timeToSetDefault)) this.setLiveDate(defaultTime); else { if (this.defaultTimeJob != null) this.defaultTimeJob.cancelJob(); BaneDefaultTimeJob bdtj = new BaneDefaultTimeJob(this); JobScheduler.getInstance().scheduleJob(bdtj, timeToSetDefault.getMillis()); this.defaultTimeJob = bdtj; } } //Returns a guild's bane /* * Getters */ public City getCity() { return City.getCity(this.cityUUID); } public PlayerCharacter getOwner() { return (PlayerCharacter) DbManager.getObject(Enum.GameObjectType.PlayerCharacter, ownerUUID); } public boolean remove() { Building baneStone; baneStone = this.getStone(); if (baneStone == null) { Logger.debug("Removing bane without a stone."); return false; } // Reassert protection contracts this.getCity().protectionEnforced = true; // Remove visual effects on Bane and TOL. this.getStone().removeAllVisualEffects(); this.getStone().updateEffects(); this.getCity().getTOL().removeAllVisualEffects(); this.getCity().getTOL().updateEffects(); // Remove bane from the database if (DbManager.BaneQueries.REMOVE_BANE(this) == false) { Logger.error("Database call failed for city UUID: " + this.getCity().getObjectUUID()); return false; } // Remove bane from ingame cache Bane.banes.remove(cityUUID); // Delete stone from database if (DbManager.BuildingQueries.DELETE_FROM_DATABASE(baneStone) == false) { Logger.error("Database error when deleting stone object"); return false; } // Remove object from simulation baneStone.removeFromCache(); WorldGrid.RemoveWorldObject(baneStone); WorldGrid.removeObject(baneStone); return true; } public final DateTime getPlacementDate() { return placementDate; } public final boolean isAccepted() { return (this.getSiegePhase() != SiegePhase.CHALLENGE); } public final DateTime getLiveDate() { return liveDate; } public void setLiveDate(DateTime baneTime) { if (DbManager.BaneQueries.SET_BANE_TIME(baneTime, this.getCity().getObjectUUID())) { this.liveDate = new DateTime(baneTime); // Push event to warehouse BaneRecord.updateLiveDate(this, baneTime); ChatManager.chatGuildInfo(this.getOwner().getGuild(), "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!"); Guild attackerNation = this.getOwner().getGuild().getNation(); if (attackerNation != null) for (Guild subGuild : attackerNation.getSubGuildList()) { //Don't send the message twice. if (subGuild.equals(attackerNation)) continue; ChatManager.chatGuildInfo(subGuild, "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!"); } ChatManager.chatGuildInfo(this.getCity().getGuild(), "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!"); Guild defenderNation = this.getCity().getGuild().getNation(); if (defenderNation != null) { if (defenderNation != this.getCity().getGuild()) ChatManager.chatGuildInfo(defenderNation, "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!"); for (Guild subGuild : defenderNation.getSubGuildList()) { //Don't send the message twice. if (subGuild == this.getCity().getGuild()) continue; ChatManager.chatGuildInfo(subGuild, "The bane on " + this.getCity().getGuild().getName() + " has been set! Standoff phase has begun!"); } } if (activateBaneJob != null) this.activateBaneJob.cancelJob(); ActivateBaneJob abtj = new ActivateBaneJob(cityUUID); JobScheduler.getInstance().scheduleJob(abtj, this.liveDate.getMillis()); this.activateBaneJob = abtj; } else { Logger.debug("error with city " + this.getCity().getName()); ChatManager.chatGuildInfo(this.getOwner().getGuild(), "A Serious error has occurred. Please post details for to ensure transaction integrity"); ChatManager.chatGuildInfo(this.getCity().getGuild(), "A Serious error has occurred. Please post details for to ensure transaction integrity"); } } public final void endBane(SiegeResult siegeResult) { boolean baneRemoved; // No matter what the outcome of a bane, we re-asset // protection contracts at this time. They don't quite // matter if the city falls, as they are invalidated. this.getCity().protectionEnforced = true; switch (siegeResult) { case DEFEND: // Push event to warehouse BaneRecord.updateResolution(this, Enum.RecordEventType.DEFEND); baneRemoved = remove(); if (baneRemoved) { // Update seieges withstood this.getCity().setSiegesWithstood(this.getCity().getSiegesWithstood() + 1); // Notify players ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel]" + this.getCity().getGuild().getName() + " has rallied against " + this.getOwner().getGuild().getName() + ". The siege on " + this.getCity().getCityName() + " has been broken!"); msg.setMessageType(4); msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID()); DispatchMessage.dispatchMsgToAll(msg); } break; case CAPTURE: // Push event to warehouse BaneRecord.updateResolution(this, Enum.RecordEventType.CAPTURE); baneRemoved = this.remove(); if (baneRemoved) { ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel]" + this.getOwner().getGuild().getName() + " have defeated " + this.getCity().getGuild().getName() + " and captured " + this.getCity().getCityName() + '!'); msg.setMessageType(4); msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID()); DispatchMessage.dispatchMsgToAll(msg); } break; case DESTROY: // Push event to warehouse BaneRecord.updateResolution(this, Enum.RecordEventType.DESTROY); baneRemoved = this.remove(); if (baneRemoved) { ChatSystemMsg msg = new ChatSystemMsg(null, "[Bane Channel]" + this.getOwner().getGuild().getName() + " have defeated " + this.getCity().getGuild().getName() + " and razed " + this.getCity().getCityName() + '!'); msg.setMessageType(4); msg.setChannel(engine.Enum.ChatChannelType.SYSTEM.getChannelID()); DispatchMessage.dispatchMsgToAll(msg); } break; } Zone cityZone = this.getCity().getParent(); if (cityZone == null) return; //UNPROTECT ALL SIEGE EQUIPMENT AFTER A BANE for (Building toUnprotect : cityZone.zoneBuildingSet) { if (toUnprotect.getBlueprint() != null && toUnprotect.getBlueprint().isSiegeEquip() && toUnprotect.assetIsProtected() == true) toUnprotect.setProtectionState(ProtectionState.NONE); } } public boolean isErrant() { boolean isErrant = true; if (this.getOwner() == null) return isErrant; if (this.getOwner().getGuild().isEmptyGuild() == true) return isErrant; if (this.getOwner().getGuild().getNation().isEmptyGuild() == true) return isErrant; // Bane passes validation isErrant = false; return isErrant; } /** * @return the stone */ public Building getStone() { return BuildingManager.getBuilding(this.stoneUUID); } /** * @return the cityUUID */ public int getCityUUID() { return cityUUID; } }