forked from MagicBane/Server
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
488 lines
15 KiB
488 lines
15 KiB
// • ▌ ▄ ·. ▄▄▄· ▄▄ • ▪ ▄▄· ▄▄▄▄· ▄▄▄· ▐▄▄▄ ▄▄▄ . |
|
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌· |
|
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀ |
|
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌ |
|
// ▀▀ █▪▀▀▀ ▀ ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀ ▀ ▀ ▀▀ █▪ ▀▀▀ |
|
// 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<Zone> macroZones = Collections.newSetFromMap(new ConcurrentHashMap<>()); |
|
private static final ConcurrentHashMap<Integer, Zone> zonesByID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD); |
|
private static final ConcurrentHashMap<Integer, Zone> zonesByUUID = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD); |
|
private static final ConcurrentHashMap<String, Zone> zonesByName = new ConcurrentHashMap<>(MBServerStatics.CHM_INIT_CAP, MBServerStatics.CHM_LOAD); |
|
private static final Set<Zone> npcCityZones = Collections.newSetFromMap(new ConcurrentHashMap<>()); |
|
private static final Set<Zone> 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<Zone> getAllZonesIn(final Vector3fImmutable loc) { |
|
|
|
ArrayList<Zone> 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<Zone> 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<Zone> 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<Integer> 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 localCoords; |
|
Vector2f zoneOrigin; |
|
|
|
zoneOrigin = new Vector2f(zone.getLoc().x, zone.getLoc().z); |
|
localCoords = new Vector2f(worldLoc.x, worldLoc.z); |
|
localCoords = localCoords.subtract(zoneOrigin); |
|
|
|
return localCoords; |
|
|
|
} |
|
|
|
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<Zone> 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<Zone> 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<City> 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; |
|
} |
|
}
|
|
|