// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.objects; import engine.Enum; import engine.InterestManagement.HeightMap; import engine.db.archive.DataWarehouse; import engine.gameManager.DbManager; import engine.gameManager.ZoneManager; import engine.math.Bounds; import engine.math.Vector2f; import engine.math.Vector3fImmutable; import engine.net.ByteBufferWriter; import engine.server.MBServerStatics; import org.pmw.tinylog.Logger; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class Zone extends AbstractGameObject { public final Set zoneBuildingSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); public final Set zoneNPCSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); public final Set zoneMobSet = Collections.newSetFromMap(new ConcurrentHashMap<>()); public final int playerCityUUID; public final String zoneName; public final float xOffset; public final float zOffset; public final float yOffset; public final int zoneTemplate; public final byte peaceZone; public final String Icon1; public final String Icon2; public final String Icon3; public float absX = 0.0f; public float absY = 0.0f; public float absZ = 0.0f; public int minLvl; public int maxLvl; public boolean hasBeenHotzone = false; public ArrayList nodes = null; public int parentZoneID; public Zone parent = null; public Bounds bounds; public boolean isNPCCity = false; public boolean isGuildZone; public String hash; public float worldAltitude = 0; public float seaLevel = 0f; public static final Set respawnQue = Collections.newSetFromMap(new ConcurrentHashMap<>()); public static long lastRespawn = 0; public Bounds blendBounds; /** * ResultSet Constructor */ public Zone(ResultSet rs) throws SQLException { super(rs); this.parentZoneID = rs.getInt("parent"); this.playerCityUUID = rs.getInt("playerCityUUID"); this.isGuildZone = this.playerCityUUID != 0; this.zoneName = rs.getString("Name"); this.xOffset = rs.getFloat("xOffset"); this.zOffset = rs.getFloat("zOffset"); this.yOffset = rs.getFloat("yOffset"); this.zoneTemplate = rs.getInt("zoneTemplate"); this.peaceZone = rs.getByte("paceZone"); this.Icon1 = rs.getString("icon1"); this.Icon2 = rs.getString("icon2"); this.Icon3 = rs.getString("icon3"); this.minLvl = rs.getInt("min_level"); this.maxLvl = rs.getInt("max_level"); this.hash = rs.getString("hash"); //this needs to be here specifically for new zones created after server boot (e.g. player city zones) Zone parentZone = ZoneManager.getZoneByUUID(parentZoneID); this.setParent(parentZone); if (this.minLvl == 0 && parentZone != null) { this.minLvl = parentZone.minLvl; this.maxLvl = parentZone.maxLvl; } if (parentZone != null) parentZone.addNode(this); // If zone doesn't yet hava a hash then write it back to the zone table if (hash == null) setHash(); } public static void serializeForClientMsg(Zone zone, ByteBufferWriter writer) { if (zone.zoneTemplate == 0 && zone.playerCityUUID == 0) Logger.warn("Warning! WorldServerMap with ID " + zone.getObjectUUID() + " has a loadnum of 0 (player city) and no city linked. This will probably crash the client!"); // Player City Terraform values serialized here. if (zone.playerCityUUID > 0) { writer.put((byte) 1); // Player City - True writer.putFloat(Enum.CityBoundsType.ZONE.halfExtents); writer.putFloat(Enum.CityBoundsType.ZONE.halfExtents); } else writer.put((byte) 0); // Player City - False writer.putFloat(zone.xOffset); writer.putFloat(zone.zOffset); writer.putFloat(zone.yOffset); writer.putInt(0); writer.putInt(0); writer.putInt(zone.zoneTemplate); if (zone.playerCityUUID > 0) { City k = City.getCity(zone.playerCityUUID); if (k != null) { writer.putInt(k.getObjectType().ordinal()); writer.putInt(k.getObjectUUID()); } else writer.putLong(0x0); } else { writer.putInt(zone.getObjectType().ordinal()); writer.putInt(zone.getObjectUUID()); } writer.putInt(zone.nodes.size()); City city = City.getCity(zone.playerCityUUID); if (city != null) writer.putString(city.getCityName()); else writer.putString(zone.zoneName); writer.put(zone.peaceZone); writer.putString(zone.Icon1); writer.putString(zone.Icon2); writer.putString(zone.Icon3); writer.put((byte) 0); // Pad for (Zone child : zone.nodes) { Zone.serializeForClientMsg(child, writer); } } /* Method sets a default value for player cities * otherwise using values derived from the loadnum * field in the obj_zone database table. */ public void setBounds() { // Set initial bounds object this.bounds = Bounds.borrow(); Vector2f zoneSize = ZoneManager._zone_size_data.get(this.zoneTemplate); // Default to player zone size on error? Maybe log this if (zoneSize != null) this.bounds.setBounds(new Vector2f(this.absX, this.absZ), zoneSize, 0.0f); else bounds.setBounds(new Vector2f(this.absX, this.absZ), new Vector2f(Enum.CityBoundsType.ZONE.halfExtents, Enum.CityBoundsType.ZONE.halfExtents), 0.0f); HeightMap heightMap = this.getHeightMap(); // Set heightmap blending bounds if (heightMap == null) { this.blendBounds = bounds; } else { this.blendBounds = Bounds.borrow(); this.blendBounds.setBounds(new Vector2f(this.absX, this.absZ), bounds.getHalfExtents().subtract(heightMap.zone_minBlend, heightMap.zone_minBlend), 0.0f); } } public void setParent(final Zone value) { this.parent = value; this.parentZoneID = (this.parent != null) ? this.parent.getObjectUUID() : 0; // Seafloor if (this.parent == null) { this.absX = this.xOffset; this.absY = MBServerStatics.SEA_FLOOR_ALTITUDE; this.absZ = this.zOffset; this.seaLevel = 0; this.setBounds(); return; } this.absX = this.xOffset + parent.absX; this.absY = this.yOffset + parent.absY; this.absZ = this.zOffset + parent.absZ; if (this.minLvl == 0 || this.maxLvl == 0) { this.minLvl = this.parent.minLvl; this.maxLvl = this.parent.maxLvl; } this.setBounds(); this.worldAltitude = ZoneManager.caclulateWorldAltitude(this); if (this.parent == null) { this.seaLevel = MBServerStatics.SEA_FLOOR_ALTITUDE; return; } if (this.getHeightMap() == null) { this.seaLevel = this.parent.seaLevel; return; } if (this.getHeightMap().seaLevel != 0) this.seaLevel = this.worldAltitude + this.getHeightMap().seaLevel; else this.seaLevel = this.parent.seaLevel; } public boolean isMacroZone() { // Macro zones have icons. if (this.isGuildZone == true) return false; if (this.parent == null) return false; return !Icon1.equals(""); } public Vector3fImmutable getLoc() { return new Vector3fImmutable(this.absX, this.absY, this.absZ); } public int getParentZoneID() { return this.parentZoneID; } public ArrayList getNodes() { if (this.nodes == null) { this.nodes = DbManager.ZoneQueries.GET_MAP_NODES(super.getObjectUUID()); //Add reverse lookup for child->parent if (this.nodes != null) for (Zone zone : this.nodes) { zone.setParent(this); } } return nodes; } /* * Serializing */ public void addNode(Zone child) { this.nodes.add(child); } @Override public void updateDatabase() { // TODO Auto-generated method stub } public boolean isContinent() { if (this.equals(ZoneManager.getSeaFloor())) return false; if (this.getNodes().isEmpty()) return false; if (this.getNodes().get(0).isMacroZone()) return true; return this.parent.equals(ZoneManager.getSeaFloor()); } public void setHash() { this.hash = DataWarehouse.hasher.encrypt(this.getObjectUUID()); // Write hash to player character table DataWarehouse.writeHash(Enum.DataRecordType.ZONE, this.getObjectUUID()); } // Return heightmap for this Zone. public HeightMap getHeightMap() { if (this.isGuildZone) return HeightMap.PlayerCityHeightMap; return HeightMap.heightmapByLoadNum.get(this.zoneTemplate); } }