// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // 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 { private final int playerCityID; private final String zoneName; private final float xCoord; private final float zCoord; private final float yCoord; public float absX = 0.0f; public float absY = 0.0f; public float absZ = 0.0f; private final int loadNum; private final byte safeZone; private final String Icon1; private final String Icon2; private final String Icon3; private ArrayList nodes = null; private int parentZoneID; private Zone parent = null; private Bounds bounds; private boolean isNPCCity = false; private boolean isPlayerCity = false; private String hash; public int minLvl; public int maxLvl; private float worldAltitude = 0; private float seaLevel = 0; 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 boolean hasBeenHotzone = false; /** * ResultSet Constructor */ public Zone(ResultSet rs) throws SQLException { super(rs); this.parentZoneID = rs.getInt("parent"); this.playerCityID = rs.getInt("isPlayerCity"); this.isPlayerCity = this.playerCityID != 0; this.zoneName = rs.getString("Name"); this.xCoord = rs.getFloat("XCoord"); this.zCoord = rs.getFloat("ZCoord"); this.yCoord = rs.getFloat("YOffset"); this.loadNum = rs.getInt("LoadNum"); this.safeZone = rs.getByte("SafeZone"); this.Icon1 = rs.getString("Icon1"); this.Icon2 = rs.getString("Icon2"); this.Icon3 = rs.getString("Icon3"); this.hash = rs.getString("hash"); this.minLvl = rs.getInt("minLvl"); this.maxLvl = rs.getInt("maxLvl"); //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(); } /* 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() { float halfExtentX; float halfExtentY; // Set initial bounds object this.bounds = Bounds.borrow(); // Player cities are assigned default value if (this.loadNum == 0) { bounds.setBounds(new Vector2f(this.absX, this.absZ), new Vector2f(Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents), 0.0f); return; } // All other zones have bounding boxes loaded from database ResultSet rs = DbManager.ZoneQueries.GET_ZONE_EXTENTS(this.loadNum); boolean loaded = false; if (rs != null) try { if (rs.next()) { halfExtentX = rs.getFloat("xRadius"); halfExtentY = rs.getFloat("zRadius"); this.bounds.setBounds(new Vector2f(this.absX, this.absZ), new Vector2f(halfExtentX, halfExtentY), 0.0f); loaded = true; } } catch (SQLException e) { Logger.error("SQLException: " + e.getMessage()); } if (!loaded) { // Default to Citygrid size on error bounds.setBounds(new Vector2f(this.absX, this.absZ), new Vector2f(Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents), 0.0f); } } /* * Getters */ public int getPlayerCityUUID() { if (this.playerCityID == 0) return 0; return this.playerCityID; } public String getName() { return zoneName; } public float getXCoord() { return xCoord; } public float getYCoord() { return yCoord; } public float getZCoord() { return zCoord; } public int getLoadNum() { return loadNum; } public int getLoadNumClient() { return loadNum; } public byte getSafeZone() { return safeZone; } public String getIcon1() { return Icon1; } public String getIcon2() { return Icon2; } public String getIcon3() { return Icon3; } public void setParent(final Zone value) { this.parent = value; this.parentZoneID = (this.parent != null) ? this.parent.getObjectUUID() : 0; if (this.parent != null) { this.absX = this.xCoord + parent.absX; this.absY = this.yCoord + parent.absY; this.absZ = this.zCoord + parent.absZ; if (this.minLvl == 0 || this.maxLvl == 0){ this.minLvl = this.parent.minLvl; this.maxLvl = this.parent.maxLvl; } } else { //only the Sea Floor zone does not have a parent this.absX = this.xCoord; this.absY = MBServerStatics.SEA_FLOOR_ALTITUDE; this.absZ = this.zCoord; } // Zone AABB is set here as it's coordinate space is world requiring a parent. this.setBounds(); if (this.getHeightMap() != null && this.getHeightMap().getSeaLevel() != 0) this.seaLevel = this.getHeightMap().getSeaLevel(); } public void generateWorldAltitude(){ if (ZoneManager.getSeaFloor().getObjectUUID() == this.getObjectUUID()){ this.worldAltitude = MBServerStatics.SEA_FLOOR_ALTITUDE; return; } Zone parentZone = this.parent; Zone currentZone = this; float altitude = this.absY; //seafloor only zone with null parent; while(parentZone != ZoneManager.getSeaFloor()){ if(parentZone.getHeightMap() != null){ Vector2f zoneLoc = ZoneManager.worldToZoneSpace(currentZone.getLoc(), parentZone); altitude += parentZone.getHeightMap().getInterpolatedTerrainHeight(zoneLoc); } currentZone = parentZone; parentZone = parentZone.parent; } this.worldAltitude = altitude; if (ZoneManager.getSeaFloor().equals(this)) this.seaLevel = 0; else if (this.getHeightMap() != null && this.getHeightMap().getSeaLevel() == 0){ this.seaLevel = this.parent.seaLevel; }else if (this.getHeightMap() != null){ this.seaLevel = this.worldAltitude + this.getHeightMap().getSeaLevel(); }else { this.seaLevel = this.parent.seaLevel; } } public Zone getParent() { return this.parent; } public float getAbsX() { return this.absX; } public float getAbsY() { return this.absY; } public float getAbsZ() { return this.absZ; } public boolean isMacroZone() { // Macro zones have icons. if (this.isPlayerCity == true) return false; if (this.parent == null) return false; return !this.getIcon1().equals(""); } public boolean isNPCCity() { return this.isNPCCity; } public boolean isPlayerCity() { return this.isPlayerCity; } public void setNPCCity(boolean value) { this.isNPCCity = value; } public void setPlayerCity(boolean value) { this.isPlayerCity = value; } 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; } public void addNode(Zone child) { this.nodes.add(child); } public void removeNode(Zone child) { this.nodes.remove(child); } /* * Serializing */ public static void serializeForClientMsg(Zone zone,ByteBufferWriter writer) { if (zone.loadNum == 0 && zone.playerCityID == 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.playerCityID > 0) { writer.put((byte) 1); // Player City - True writer.putFloat(Enum.CityBoundsType.ZONE.extents); writer.putFloat(Enum.CityBoundsType.ZONE.extents); } else writer.put((byte) 0); // Player City - False writer.putFloat(zone.xCoord); writer.putFloat(zone.zCoord); writer.putFloat(zone.yCoord); writer.putInt(0); writer.putInt(0); writer.putInt(zone.loadNum); if (zone.playerCityID > 0) { City k = City.getCity(zone.playerCityID); 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.playerCityID); if (city != null) writer.putString(city.getCityName()); else writer.putString(zone.zoneName); writer.put(zone.safeZone); 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); } } @Override public void updateDatabase() { // TODO Auto-generated method stub } public Zone findRuinedCityZone(float centerX, float centerY, float centerZ){ Bounds cityBounds; cityBounds = Bounds.borrow(); Zone RuinedZone = null; cityBounds.setBounds(new Vector2f(centerX, centerZ), new Vector2f(Enum.CityBoundsType.ZONE.extents, Enum.CityBoundsType.ZONE.extents), 0.0f); Zone currentZone = ZoneManager.findSmallestZone(new Vector3fImmutable(centerX, centerY, centerZ)); if (currentZone != null) if (this.getObjectUUID() == currentZone.getObjectUUID()){ if (currentZone.getPlayerCityUUID() != 0){ //null player city? skip.. if (City.GetCityFromCache(currentZone.getPlayerCityUUID()) == null) RuinedZone = null; else //no tol? skip... if (City.GetCityFromCache(currentZone.getPlayerCityUUID()).getTOL() == null) RuinedZone = null; else if (City.GetCityFromCache(currentZone.getPlayerCityUUID()).getTOL().getRank() == -1) RuinedZone = currentZone; //Dead tree? skip. cityBounds.release(); return RuinedZone; } } for (Zone zone : this.getNodes()) { if (zone == this) continue; if (zone.isContinent() && zone.getPlayerCityUUID() == 0) continue; if (zone.getPlayerCityUUID() != 0){ //null player city? skip.. if (City.GetCityFromCache(zone.getPlayerCityUUID()) == null) continue; //no tol? skip... if (City.GetCityFromCache(zone.getPlayerCityUUID()).getTOL() == null) continue; //Dead tree? skip. if (Bounds.collide(zone.bounds, cityBounds, 0.0f)){ if (City.GetCityFromCache(zone.getPlayerCityUUID()).getTOL().getRank() == -1){ RuinedZone = zone; break; } } } } cityBounds.release(); return RuinedZone; } 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.getParent().equals(ZoneManager.getSeaFloor()); } /** * @return the bounds */ public Bounds getBounds() { return bounds; } public String getHash() { return hash; } 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.isPlayerCity) return HeightMap.PlayerCityHeightMap; return HeightMap.heightmapByLoadNum.get(this.loadNum); } public float getSeaLevel() { return seaLevel; } public float getWorldAltitude() { return worldAltitude; } }