// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . // ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· // ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ // ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ // ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ // Magicbane Emulator Project © 2013 - 2022 // www.magicbane.com package engine.gameManager; import engine.Enum; import engine.InterestManagement.Terrain; import engine.db.archive.CityRecord; import engine.db.archive.DataWarehouse; import engine.math.Bounds; import engine.math.Vector2f; import engine.math.Vector3f; import engine.math.Vector3fImmutable; import engine.objects.Building; import engine.objects.City; import engine.objects.Zone; import engine.server.MBServerStatics; import org.pmw.tinylog.Logger; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ThreadLocalRandom; /* * Class contains methods and structures which * track in-game Zones */ public enum ZoneManager { ZONEMANAGER; public static final Set macroZones = Collections.newSetFromMap(new ConcurrentHashMap<>()); private static final ConcurrentHashMap zonesByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD); private static final ConcurrentHashMap zonesByUUID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD); private static final ConcurrentHashMap zonesByName = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD); private static final Set npcCityZones = Collections.newSetFromMap(new ConcurrentHashMap<>()); private static final Set playerCityZones = Collections.newSetFromMap(new ConcurrentHashMap<>()); public static Instant hotZoneLastUpdate; public static Zone hotZone = null; public static int hotZoneCycle = 0; // Used with HOTZONE_DURATION from config. /* Instance variables */ public static Zone seaFloor = null; // Find all zones coordinates fit into, starting with Sea Floor public static ArrayList getAllZonesIn(final Vector3fImmutable loc) { ArrayList allIn = new ArrayList<>(); Zone zone; zone = ZoneManager.findSmallestZone(loc); if (zone != null) { allIn.add(zone); while (zone.parent != null) { zone = zone.parent; allIn.add(zone); } } return allIn; } // Find smallest zone coordinates fit into. public static Zone findSmallestZone(final Vector3fImmutable loc) { Zone zone = ZoneManager.seaFloor; if (zone == null) return null; boolean childFound = true; while (childFound) { childFound = false; ArrayList nodes = zone.getNodes(); // Logger.info("soze", "" + nodes.size()); if (nodes != null) for (Zone child : nodes) { if (Bounds.collide(loc, child.bounds)) { zone = child; childFound = true; break; } } } return zone; } public static void addZone(final int zoneID, final Zone zone) { ZoneManager.zonesByID.put(zoneID, zone); ZoneManager.zonesByUUID.put(zone.getObjectUUID(), zone); ZoneManager.zonesByName.put(zone.zoneName.toLowerCase(), zone); } // Returns the number of available hotZones // remaining in this cycle (1am) public static int availableHotZones() { int count = 0; for (Zone zone : ZoneManager.macroZones) if (ZoneManager.validHotZone(zone)) count = count + 1; return count; } // Resets the availability of hotZones // for this cycle public static void resetHotZones() { for (Zone zone : ZoneManager.macroZones) if (zone.hasBeenHotzone) zone.hasBeenHotzone = false; } public static Zone getZoneByUUID(final int zoneUUID) { return ZoneManager.zonesByUUID.get(zoneUUID); } public static Zone getZoneByZoneID(final int zoneID) { return ZoneManager.zonesByID.get(zoneID); } public static Zone getZoneByName(final String zoneName) { return ZoneManager.zonesByName.get(zoneName); } public static Collection getAllZones() { return ZoneManager.zonesByUUID.values(); } public static void setHotZone(final Zone zone) { if (!zone.isMacroZone()) return; ZoneManager.hotZone = zone; ZoneManager.hotZoneCycle = 1; // Used with HOTZONE_DURATION from config. zone.hasBeenHotzone = true; ZoneManager.hotZoneLastUpdate = LocalDateTime.now().withMinute(0).withSecond(0).atZone(ZoneId.systemDefault()).toInstant(); } public static boolean inHotZone(final Vector3fImmutable loc) { if (ZoneManager.hotZone == null) return false; return (Bounds.collide(loc, ZoneManager.hotZone.bounds)); } public static void setSeaFloor(final Zone value) { ZoneManager.seaFloor = value; } public static void populateWorldZones(final Zone zone) { int loadNum = zone.template; // Zones are added to separate // collections for quick access // based upon their type. if (zone.isMacroZone()) { addMacroZone(zone); return; } if (zone.guild_zone) { addPlayerCityZone(zone); return; } if (zone.isNPCCity) addNPCCityZone(zone); } private static void addMacroZone(final Zone zone) { ZoneManager.macroZones.add(zone); } private static void addNPCCityZone(final Zone zone) { zone.isNPCCity = true; ZoneManager.npcCityZones.add(zone); } public static final void addPlayerCityZone(final Zone zone) { zone.guild_zone = true; ZoneManager.playerCityZones.add(zone); } public static final void generateAndSetRandomHotzone() { Zone hotZone; ArrayList zoneArray = new ArrayList<>(); if (ZoneManager.macroZones.isEmpty()) return; // Reset hotZone availability if none are left. if (ZoneManager.availableHotZones() == 0) ZoneManager.resetHotZones(); for (Zone zone : ZoneManager.macroZones) if (validHotZone(zone)) zoneArray.add(zone.getObjectUUID()); int entryIndex = ThreadLocalRandom.current().nextInt(zoneArray.size()); hotZone = ZoneManager.getZoneByUUID(zoneArray.get(entryIndex)); if (hotZone == null) { Logger.error("Hotzone is null"); return; } ZoneManager.setHotZone(hotZone); } public static final boolean validHotZone(Zone zone) { if (zone.peace_zone == (byte) 1) return false; // no safe zone hotzones// if (this.hotzone == null) if (zone.getNodes().isEmpty()) return false; if (zone.equals(ZoneManager.seaFloor)) return false; //no duplicate hotZones if (zone.hasBeenHotzone == true) return false; // Enforce min level if (zone.min_level < Integer.parseInt(ConfigManager.MB_HOTZONE_MIN_LEVEL.getValue())) return false; if (ZoneManager.hotZone != null) return ZoneManager.hotZone.getObjectUUID() != zone.getObjectUUID(); return true; } // Converts world coordinates to coordinates local to a given zone. public static Vector3fImmutable worldToLocal(Vector3fImmutable worldVector, Zone zone) { Vector3fImmutable localCoords; localCoords = new Vector3fImmutable(worldVector.x - zone.absX, worldVector.y - zone.absY, worldVector.z - zone.absZ); return localCoords; } public static Vector2f worldToZoneOffset(Vector3fImmutable worldLoc, Zone zone) { Vector2f zoneLoc; zoneLoc = new Vector2f(worldLoc.x, worldLoc.z).subtract(zone.getLoc().x, zone.getLoc().z); zoneLoc.y *= -1; return zoneLoc; } public static Vector2f worldToTerrainSpace(Vector3fImmutable worldLoc, Zone zone) { Vector2f localCoords; Vector2f zoneOrigin; // Top left corner of zone is calculated in world space by the center and it's extents. zoneOrigin = new Vector2f(zone.getLoc().x, zone.getLoc().z); zoneOrigin = zoneOrigin.subtract(new Vector2f(zone.bounds.getHalfExtents().x, zone.bounds.getHalfExtents().y)); // Local coordinate in world space translated to an offset from the calculated zone origin. localCoords = new Vector2f(worldLoc.x, worldLoc.z); localCoords = localCoords.subtract(zoneOrigin); // TODO : Make sure this value does not go outside the zone's bounds. return localCoords; } // Converts local zone coordinates to world coordinates /** * Converts from local (relative to this building) to world. * * @param localPos position in local reference (relative to this building) * @return position relative to world */ public static Vector3fImmutable convertLocalToWorld(Building building, Vector3fImmutable localPos) { // convert from SB rotation value to radians if (building.getBounds().getQuaternion() == null) return building.getLoc(); Vector3fImmutable rotatedLocal = Vector3fImmutable.rotateAroundPoint(Vector3fImmutable.ZERO, localPos, building.getBounds().getQuaternion()); // handle building rotation // handle building translation return building.getLoc().add(rotatedLocal.x, rotatedLocal.y, rotatedLocal.z); } //used for regions, Building bounds not set yet. public static Vector3f convertLocalToWorld(Building building, Vector3f localPos, Bounds bounds) { // convert from SB rotation value to radians Vector3f rotatedLocal = Vector3f.rotateAroundPoint(Vector3f.ZERO, localPos, bounds.getQuaternion()); // handle building rotation // handle building translation return new Vector3f(building.getLoc().add(rotatedLocal.x, rotatedLocal.y, rotatedLocal.z)); } public static Vector3fImmutable convertWorldToLocal(Building building, Vector3fImmutable WorldPos) { Vector3fImmutable convertLoc = Vector3fImmutable.rotateAroundPoint(building.getLoc(), WorldPos, -building.getBounds().getQuaternion().angleY); convertLoc = convertLoc.subtract(building.getLoc()); // convert from SB rotation value to radians return convertLoc; } // Method returns a city if the given location is within // a city zone. public static City getCityAtLocation(Vector3fImmutable worldLoc) { Zone currentZone; ArrayList zoneList; City city; currentZone = ZoneManager.findSmallestZone(worldLoc); if (currentZone.guild_zone) return City.getCity(currentZone.playerCityUUID); return null; } /* Method is called when creating a new player city to * validate that the new zone does not overlap any other * zone that might currently exist */ public static boolean validTreePlacementLoc(Zone currentZone, float positionX, float positionZ) { // Member Variable declaration ArrayList zoneList; boolean validLocation = true; Bounds treeBounds; if (currentZone.isContinent() == false) return false; treeBounds = Bounds.borrow(); treeBounds.setBounds(new Vector2f(positionX, positionZ), new Vector2f(Enum.CityBoundsType.PLACEMENT.halfExtents, Enum.CityBoundsType.PLACEMENT.halfExtents), 0.0f); zoneList = currentZone.getNodes(); for (Zone zone : zoneList) { if (zone.isContinent()) continue; if (Bounds.collide(treeBounds, zone.bounds, 0.0f)) validLocation = false; } treeBounds.release(); return validLocation; } 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.getLoc()); // huh? //not player city, must be npc city.. if (!zone.guild_zone) zone.isNPCCity = true; if ((ConfigManager.serverType.equals(Enum.ServerType.WORLDSERVER)) && (city.getHash() == null)) { city.setHash(); if (DataWarehouse.recordExists(Enum.DataRecordType.CITY, city.getObjectUUID()) == false) { CityRecord cityRecord = CityRecord.borrow(city, Enum.RecordEventType.CREATE); DataWarehouse.pushToWarehouse(cityRecord); } } } } public static float calculateGlobalZoneHeight(Zone zone) { float worldAlttitude = MBServerStatics.SEA_FLOOR_ALTITUDE; // Seafloor if (ZoneManager.seaFloor.equals(zone)) return worldAlttitude; // Children of seafloor if (ZoneManager.seaFloor.equals(zone.parent)) return worldAlttitude + zone.yOffset; // return height from heightmap engine at zone location worldAlttitude = Terrain.getWorldHeight(zone.parent, zone.getLoc()); // Add zone offset to value worldAlttitude += zone.yOffset; return worldAlttitude; } public static boolean isLocUnderwater(Vector3fImmutable currentLoc) { float localAltitude = Terrain.getWorldHeight(currentLoc); Zone zone = findSmallestZone(currentLoc); return localAltitude < zone.seaLevel; } }