From 134ddf2407495b29ed92200f8abd600acb90f773 Mon Sep 17 00:00:00 2001 From: ceikry Date: Tue, 29 Jun 2021 13:30:46 -0500 Subject: [PATCH] Add threat safety to the region manager as well as a kotlin conversion. --- .../interaction/item/SilverSicklePlugin.java | 3 +- .../core/game/node/entity/player/Player.java | 2 +- .../skill/construction/HouseManager.java | 4 +- .../core/game/world/map/RegionManager.java | 737 ---------------- .../java/core/game/world/map/RegionManager.kt | 807 ++++++++++++++++++ .../game/world/map/build/DynamicRegion.java | 4 +- .../kotlin/rs09/game/ai/general/ScriptAPI.kt | 2 +- .../rs09/game/system/config/ConfigParser.kt | 17 +- 8 files changed, 819 insertions(+), 757 deletions(-) delete mode 100644 Server/src/main/java/core/game/world/map/RegionManager.java create mode 100644 Server/src/main/java/core/game/world/map/RegionManager.kt diff --git a/Server/src/main/java/core/game/interaction/item/SilverSicklePlugin.java b/Server/src/main/java/core/game/interaction/item/SilverSicklePlugin.java index 646687452..8e3b1a41d 100644 --- a/Server/src/main/java/core/game/interaction/item/SilverSicklePlugin.java +++ b/Server/src/main/java/core/game/interaction/item/SilverSicklePlugin.java @@ -29,7 +29,7 @@ public final class SilverSicklePlugin extends OptionHandler { @Override public boolean handle(Player player, Node node, String option) { - Region region = RegionManager.getRegionCache().get(player.getLocation().getRegionId()); + Region region = RegionManager.forId(player.getLocation().getRegionId()); switch (option) { case "operate": case "cast bloom": @@ -48,6 +48,7 @@ public final class SilverSicklePlugin extends OptionHandler { } } } + RegionManager.getLock().unlock(); return true; } return false; diff --git a/Server/src/main/java/core/game/node/entity/player/Player.java b/Server/src/main/java/core/game/node/entity/player/Player.java index 15ed96bb7..a39ff8e26 100644 --- a/Server/src/main/java/core/game/node/entity/player/Player.java +++ b/Server/src/main/java/core/game/node/entity/player/Player.java @@ -761,7 +761,7 @@ public class Player extends Entity { */ public void updateSceneGraph(boolean login) { Region region = getViewport().getRegion(); - if (region instanceof DynamicRegion || (region == null && (region = RegionManager.getRegionCache().get(location.getRegionId())) instanceof DynamicRegion || region == null)) { + if (region instanceof DynamicRegion || region == null && (region = RegionManager.forId(location.getRegionId())) instanceof DynamicRegion) { PacketRepository.send(BuildDynamicScene.class, new DynamicSceneContext(this, login)); } else { PacketRepository.send(UpdateSceneGraph.class, new SceneGraphContext(this, login)); diff --git a/Server/src/main/java/core/game/node/entity/skill/construction/HouseManager.java b/Server/src/main/java/core/game/node/entity/skill/construction/HouseManager.java index 1d574a457..ed39f3893 100644 --- a/Server/src/main/java/core/game/node/entity/skill/construction/HouseManager.java +++ b/Server/src/main/java/core/game/node/entity/skill/construction/HouseManager.java @@ -381,7 +381,7 @@ public final class HouseManager { region = new DynamicRegion(-1, borders.getSouthWestX() >> 6, borders.getSouthWestY() >> 6); region.setBorders(borders); region.setUpdateAllPlanes(true); - RegionManager.getRegionCache().put(region.getId(), region); + RegionManager.addRegion(region.getId(), region); configureRoofs(); for (int z = 0; z < 3; z++) { for (int x = 0; x < 8; x++) { @@ -406,7 +406,7 @@ public final class HouseManager { dungeon = new DynamicRegion(-1, borders.getSouthWestX() >> 6, borders.getSouthWestY() >> 6); dungeon.setBorders(borders); dungeon.setUpdateAllPlanes(true); - RegionManager.getRegionCache().put(dungeon.getId(), dungeon); + RegionManager.addRegion(dungeon.getId(), dungeon); for (int x = 0; x < 8; x++) { for (int y = 0; y < 8; y++) { Room room = rooms[3][x][y]; diff --git a/Server/src/main/java/core/game/world/map/RegionManager.java b/Server/src/main/java/core/game/world/map/RegionManager.java deleted file mode 100644 index 1a7ef923f..000000000 --- a/Server/src/main/java/core/game/world/map/RegionManager.java +++ /dev/null @@ -1,737 +0,0 @@ -package core.game.world.map; - -import core.game.node.Node; -import core.game.node.entity.Entity; -import core.game.node.entity.npc.NPC; -import core.game.node.entity.player.Player; -import core.game.node.object.Scenery; -import rs09.game.system.SystemLogger; -import core.game.world.map.zone.ZoneBorders; -import core.tools.RandomFunction; - -import java.util.*; - -/** - * Manages the regions. - * @author Emperor - * - */ -public final class RegionManager { - - /** - * The region cache mapping. - */ - private static final Map REGION_CACHE = new HashMap<>(); - - /** - * Gets the region for the given region id. - * @param regionId The region id. - * @return The region. - */ - public static Region forId(int regionId) { - Region region = REGION_CACHE.get(regionId); - if (region == null) { - region = new Region(regionId >> 8 & 0xFF, regionId & 0xFF); - REGION_CACHE.put(regionId, region); - } - return region; - } - - /** - * Pulses the active regions. - */ - public static void pulse() { - Object[] rCacheArray = new Object[0]; - try { - rCacheArray = REGION_CACHE.values().toArray(); - } catch (ConcurrentModificationException e){ - SystemLogger.logErr("Region Cache tried to process too early -- restarting..."); - System.exit(0); - } - int rCacheLength = rCacheArray.length; - for (int i = 0; i < rCacheLength; i++) { - Region r = (Region) rCacheArray[i]; - if (r != null && r.isActive()) { - RegionPlane[] planes = r.getPlanes(); - int size = planes.length; - for (int j = 0; j < size; j++) { - planes[j].pulse(); - } - } - } - } - - /** - * Gets the clipping flag on the given location. - * @param l The location. - * @return The clipping flag. - */ - public static int getClippingFlag(Location l) { - return getClippingFlag(l.getZ(), l.getX(), l.getY()); - } - - /** - * Gets the clipping flag. - * @param z The plane. - * @param x The absolute x-coordinate. - * @param y The absolute y-coordinate. - * @return The clipping flags. - */ - public static int getClippingFlag(int z, int x, int y) { - Region region = forId((x >> 6) << 8 | y >> 6); - Region.load(region); - if (!region.isHasFlags()) { - return -1; - } - x -= (x >> 6) << 6; - y -= (y >> 6) << 6; - return region.getPlanes()[z].getFlags().getClippingFlags()[x][y]; - } - - /** - * Gets the water variant of a tile's clipping flag - * Essentially strips the landscape flag off a tile and keeps other flags, and makes normally walkable tiles unwalkable. - * @author Ceikry - */ - public static int getWaterClipFlag(int z, int x, int y){ - int flag = getClippingFlag(z,x,y); - if(!isClipped(z,x,y)){ - return flag | 0x100; - } - return flag & (~0x200000); - } - - /** - * Checks if the tile is part of the landscape. - * @param l The location. - * @return {@code True} if so. - */ - public static boolean isLandscape(Location l) { - return isLandscape(l.getZ(), l.getX(), l.getY()); - } - - /** - * Checks if the tile is part of the landscape. - * @param z The plane. - * @param x The absolute x-coordinate. - * @param y The absolute y-coordinate. - * @return {@code True} if so. - */ - public static boolean isLandscape(int z, int x, int y) { - Region region = forId((x >> 6) << 8 | y >> 6); - Region.load(region); - if (!region.isHasFlags() || region.getPlanes()[z].getFlags().getLandscape() == null) { - return false; - } - x -= (x >> 6) << 6; - y -= (y >> 6) << 6; - return region.getPlanes()[z].getFlags().getLandscape()[x][y]; - } - - /** - * Sets the clipping flag on the given location. - * @param l The location. - * @param flag The flag to set. - */ - public static void setClippingFlag(Location l, int flag) { - int x = l.getX(); - int y = l.getY(); - int z = l.getZ(); - Region region = forId((x >> 6) << 8 | y >> 6); - Region.load(region); - if (!region.isHasFlags()) { - return; - } - x -= (x >> 6) << 6; - y -= (y >> 6) << 6; - region.getPlanes()[z].getFlags().getClippingFlags()[x][y] = flag; - } - - /** - * Adds a clipping flag. - * @param z The plane. - * @param x The absolute x-coordinate. - * @param y The absolute y-coordinate. - * @param projectile If the flag is being set for projectile pathfinding. - * @param flag The clipping flag. - */ - public static void addClippingFlag(int z, int x, int y, boolean projectile, int flag) { - Region region = forId((x >> 6) << 8 | y >> 6); - Region.load(region); - if (!region.isHasFlags()) { - return; - } - x -= (x >> 6) << 6; - y -= (y >> 6) << 6; - if (projectile) { - region.getPlanes()[z].getProjectileFlags().flag(x, y, flag); - } else { - region.getPlanes()[z].getFlags().flag(x, y, flag); - } - } - - /** - * Adds a clipping flag. - * @param z The plane. - * @param x The absolute x-coordinate. - * @param y The absolute y-coordinate. - * @param projectile If the flag is being set for projectile pathfinding. - * @param flag The clipping flag. - */ - public static void removeClippingFlag(int z, int x, int y, boolean projectile, int flag) { - Region region = forId((x >> 6) << 8 | y >> 6); - Region.load(region); - if (!region.isHasFlags()) { - return; - } - x -= (x >> 6) << 6; - y -= (y >> 6) << 6; - if (projectile) { - region.getPlanes()[z].getProjectileFlags().unflag(x, y, flag); - } else { - region.getPlanes()[z].getFlags().unflag(x, y, flag); - } - } - - /** - * Gets the clipping flag. - * @param z The plane. - * @param x The absolute x-coordinate. - * @param y The absolute y-coordinate. - * @return The clipping flags. - */ - public static int getProjectileFlag(int z, int x, int y) { - Region region = forId((x >> 6) << 8 | y >> 6); - Region.load(region); - if (!region.isHasFlags()) { - return -1; - } - x -= (x >> 6) << 6; - y -= (y >> 6) << 6; - return region.getPlanes()[z].getProjectileFlags().getClippingFlags()[x][y]; - } - - /** - * Gets the clipping flag - * @param location the Location - * @return the clipping flag - */ - public static boolean isTeleportPermitted(Location location) { - return isTeleportPermitted(location.getZ(), location.getX(), location.getY()); - } - - /** - * Gets the clipping flag. - * @param z The plane. - * @param x The absolute x-coordinate. - * @param y The absolute y-coordinate. - * @return The clipping flags. - */ - public static boolean isTeleportPermitted(int z, int x, int y) { - if (!isLandscape(z, x, y)) { - return false; - } - int flag = getClippingFlag(z, x, y); - return (flag & 0x12c0102) == 0 || (flag & 0x12c0108) == 0 || (flag & 0x12c0120) == 0 || (flag & 0x12c0180) == 0; - } - - /** - * Checks if the location has any clipping flags. - * @param location The location. - * @return {@code True} if a clipping flag disables access for this location. - */ - public static boolean isClipped(Location location) { - return isClipped(location.getZ(), location.getX(), location.getY()); - } - - /** - * Checks if the location has any clipping flags. - * @param z The plane. - * @param x The x-coordinate. - * @param y The y-coordinate. - * @return {@code True} if a clipping flag disables access for this location. - */ - public static boolean isClipped(int z, int x, int y) { - if (!isLandscape(z, x, y)) { - return true; - } - int flag = getClippingFlag(z, x, y); - return (flag & 0x12c0102) != 0 || (flag & 0x12c0108) != 0 || (flag & 0x12c0120) != 0 || (flag & 0x12c0180) != 0; - } - - /** - * Gets the spawn location of a node. - * @param owner the owner. - * @param node the node. - * @return the location. - */ - public static Location getSpawnLocation(Player owner, Node node) { - if (owner == null || node == null) { - return null; - } - Location destination = null; - for (int i = 0; i < 4; i++) { - Direction dir = Direction.get(i); - Location l = owner.getLocation().transform(dir, dir.toInteger() < 2 ? 1 : node.size()); - boolean success = true; - loop:{ - for (int x = 0; x < node.size(); x++) { - for (int y = 0; y < node.size(); y++) { - if (RegionManager.isClipped(l.transform(x, y, 0))) { - success = false; - break loop; - } - } - } - } - if (success) { - destination = l; - break; - } - } - return destination; - } - - /** - * Gets the scenery on the current location. - * @param l The location. - * @return The scenery, or {@code null} if no object was found. - */ - public static Scenery getObject(Location l) { - return getObject(l.getZ(), l.getX(), l.getY()); - } - - /** - * Gets the scenery on the current absolute coordinates. - * @param z The height. - * @param x The x-coordinate. - * @param y The y-coordinate. - * @return The scenery, or {@code null} if no object was found. - */ - public static Scenery getObject(int z, int x, int y) { - return getObject(z, x, y, -1); - } - - /** - * Gets the object on the given absolute coordinates. - * @param z The height. - * @param x The x-coordinate. - * @param y The y-coordinate. - * @param objectId The object id. - * @return The scenery, or {@code null} if no object was found. - */ - public static Scenery getObject(int z, int x, int y, int objectId) { - int regionId = ((x >> 6) << 8) | y >> 6; - x -= ((x >> 6) << 6); - y -= ((y >> 6) << 6); - Region region = forId(regionId); - Region.load(region); - Scenery object = region.getPlanes()[z].getChunkObject(x, y, objectId); - if (object != null && !object.isRenderable()) { - return null; - } - return object; - } - - /** - * Gets the region plane for this location. - * @param l The location. - * @return The region plane. - */ - public static RegionPlane getRegionPlane(Location l) { - int regionId = ((l.getX() >> 6) << 8) | l.getY() >> 6; - return forId(regionId).getPlanes()[l.getZ()]; - } - - /** - * Gets a region chunk. - * @param l The location. - * @return The region chunk. - */ - public static RegionChunk getRegionChunk(Location l) { - RegionPlane plane = getRegionPlane(l); - return plane.getRegionChunk(l.getLocalX() / RegionChunk.SIZE, l.getLocalY() / RegionChunk.SIZE); - } - - /** - * Moves the entity from the current region to the new one. - * @param entity The entity. - */ - public static void move(Entity entity) { - boolean player = entity instanceof Player; - int regionId = (entity.getLocation().getRegionX() >> 3) << 8 | (entity.getLocation().getRegionY() >> 3); - Viewport viewport = entity.getViewport(); - Region current = forId(regionId); - int z = entity.getLocation().getZ(); - RegionPlane plane = current.getPlanes()[z]; - viewport.updateViewport(entity); - if (plane == viewport.getCurrentPlane()) { - entity.getZoneMonitor().updateLocation(entity.getWalkingQueue().getFootPrint()); - return; - } - viewport.remove(entity); - if (player) { - current.add((Player) entity); - } else { - current.add((NPC) entity); - } - viewport.setRegion(current); - viewport.setCurrentPlane(plane); - List view = new LinkedList<>(); - for (int regionX = (entity.getLocation().getRegionX() >> 3) - 1; regionX <= (entity.getLocation().getRegionX() >> 3) + 1; regionX++) { - for (int regionY = (entity.getLocation().getRegionY() >> 3) - 1; regionY <= (entity.getLocation().getRegionY() >> 3) + 1; regionY++) { - if (regionX < 0 || regionY < 0) { - continue; - } - Region region = forId(regionX << 8 | regionY); - RegionPlane p = region.getPlanes()[z]; - if (player) { - region.incrementViewAmount(); - region.flagActive(); - } - view.add(p); - } - } - viewport.setViewingPlanes(view); - entity.getZoneMonitor().updateLocation(entity.getWalkingQueue().getFootPrint()); - } - - /** - * Gets the list of local NPCs with a maximum distance of 16. - * @param n The entity. - * @return The list of local NPCs. - */ - public static List getLocalNpcs(Entity n) { - return getLocalNpcs(n, MapDistance.RENDERING.getDistance()); - } - - /** - * Gets the location entitys. - * @param location the location. - * @param distance the distance. - * @return the list. - */ - public static List getLocalEntitys(Location location, int distance){ - List entitys = new ArrayList<>(20); - entitys.addAll(getLocalNpcs(location, distance)); - entitys.addAll(getLocalPlayers(location, distance)); - return entitys; - } - - /** - * Gets the location entitys. - * @param entity the entity. - * @param distance the distance. - * @return the list. - */ - public static List getLocalEntitys(Entity entity, int distance) { - return getLocalEntitys(entity.getLocation(), distance); - } - - /** - * Gets the local entitys. - * @param entity the entity. - * @return the entitys. - */ - public static List getLocalEntitys(Entity entity) { - return getLocalEntitys(entity.getLocation(), MapDistance.RENDERING.getDistance()); - } - - /** - * Gets the list of local NPCs. - * @param n The entity. - * @param distance The distance to the entity. - * @return The list of local NPCs. - */ - public static List getLocalNpcs(Entity n, int distance) { - List npcs = new LinkedList<>(); - for (RegionPlane r : n.getViewport().getViewingPlanes()) { - for (NPC npc : r.getNpcs()) { - if (npc.getLocation().withinDistance(n.getLocation(), distance)) { - npcs.add(npc); - } - } - } - return npcs; - } - - /** - * Gets the list of local players with a maximum distance of 15. - * @param n The entity. - * @return The list of local players. - */ - public static List getLocalPlayers(Entity n) { - return getLocalPlayers(n, MapDistance.RENDERING.getDistance()); - } - - /** - * Gets the list of local players. - * @param n The entity. - * @param distance The distance to the entity. - * @return The list of local players. - */ - public static List getLocalPlayers(Entity n, int distance) { - List players = new LinkedList<>(); - for (RegionPlane r : n.getViewport().getViewingPlanes()) { - for (Player p : r.getPlayers()) { - if (p.getLocation().withinDistance(n.getLocation(), distance)) { - players.add(p); - } - } - } - return players; - } - - /** - * Gets the surrounding players. - * @param n The node the players should be surrounding. - * @param ignore The nodes not to add to the list. - * @return The list of players. - */ - public static List getSurroundingPlayers(Node n, Node...ignore) { - return getSurroundingPlayers(n, 9, ignore); - } - - /** - * Gets the surrounding players. - * @param n The node the players should be surrounding. - * @param ignore The nodes not to add to the list. - * @return The list of players. - */ - public static List getSurroundingPlayers(Node n, int maximum, Node...ignore) { - List players = getLocalPlayers(n.getLocation(), 1); - int count = 0; - for (Iterator it = players.iterator(); it.hasNext();) { - Player p = it.next(); - if (++count >= maximum) { - it.remove(); - continue; - } - for (Node node : ignore) { - if (p == node) { - count--; - it.remove(); - break; - } - } - } - return players; - } - - /** - * Gets the surrounding players. - * @param n The node the players should be surrounding. - * @param ignore The nodes not to add to the list. - * @return The list of players. - */ - public static List getSurroundingNPCs(Node n, Node...ignore) { - return getSurroundingNPCs(n, 9, ignore); - } - - /** - * Gets the surrounding players. - * @param n The node the npcs should be surrounding. - * @param ignore The nodes not to add to the list. - * @return The list of npcs. - */ - public static List getSurroundingNPCs(Node n, int maximum, Node...ignore) { - List npcs = getLocalNpcs(n.getLocation(), 1); - int count = 0; - for (Iterator it = npcs.iterator(); it.hasNext();) { - NPC p = it.next(); - if (++count > maximum) { - it.remove(); - continue; - } - for (Node node : ignore) { - if (p == node) { - count--; - it.remove(); - break; - } - } - } - return npcs; - } - - /** - * Gets a random teleport location in the radius around the given location. - * @param location The centre location. - * @param radius The radius. - * @return A random teleport location. - */ - public static Location getTeleportLocation(Location location, int radius) { - int mod = radius >> 1; - if (mod == 0) { - mod++; - radius--; - } - return getTeleportLocation(location.transform(-mod, -mod, 0), mod + radius, mod + radius); - } - - /** - * Gets a random teleport location in the radius around the given location. - * @param location The centre location. - * @return A random teleport location. - */ - public static Location getTeleportLocation(Location location, int areaX, int areaY) { - Location destination = location; - int x = RandomFunction.random(1 + areaX); - int y = RandomFunction.random(1 + areaY); - int count = 0; - while (!isTeleportPermitted(destination = location.transform(x, y, 0))) { - x = RandomFunction.random(1 + areaX); - y = RandomFunction.random(1 + areaY); - if (count++ >= areaX * 2) { - //This would be able to keep looping for several seconds otherwise (this actually happens). - for (x = 0; x < areaX + 1; x++) { - for (y = 0; y < areaY + 1; y++) { - if (isTeleportPermitted(destination = location.transform(x, y, 0))) { - return destination; - } - } - } - break; - } - } - return destination; - } - - /** - * Gets the current viewport for the location. - * @param l The location. - * @return The viewport. - */ - public static List getViewportPlayers(Location l) { - List players = new LinkedList<>(); - l = l.getChunkBase().transform(-8, -8, 0); - ZoneBorders b = new ZoneBorders(l.getX(), l.getY(), l.getX() + 24, l.getY() + 24); - for (int regionX = (l.getRegionX() - 6) >> 3; regionX <= (l.getRegionX() + 6) >> 3; regionX++) { - for (int regionY = (l.getRegionY() - 6) >> 3; regionY <= (l.getRegionY() + 6) >> 3; regionY++) { - for (Player player : forId(regionX << 8 | regionY).getPlanes()[l.getZ()].getPlayers()) { - l = player.getLocation(); - if (b.insideBorder(l.getX(), l.getY())) { - players.add(player); - } - } - } - } - return players; - } - - /** - * Gets a list of players in the given region. - * @param regionId The region id. - * @return The list of players in this region. - */ - public static List getRegionPlayers(int regionId) { - Region r = forId(regionId); - List players = new ArrayList<>(20); - for (RegionPlane plane : r.getPlanes()) { - players.addAll(plane.getPlayers()); - } - return players; - } - - /** - * Gets a list of local players within rendering distance of the location. - * @param l The location. - * @return The list of players. - */ - public static List getLocalPlayers(Location l) { - return getLocalPlayers(l, MapDistance.RENDERING.getDistance()); - } - - /** - * Gets a list of local players. - * @param l The location. - * @param distance The distance to that location. - * @return The list of players. - */ - public static List getLocalPlayers(Location l, int distance) { - List players = new LinkedList<>(); - for (int regionX = (l.getRegionX() - 6) >> 3; regionX <= (l.getRegionX() + 6) >> 3; regionX++) { - for (int regionY = (l.getRegionY() - 6) >> 3; regionY <= (l.getRegionY() + 6) >> 3; regionY++) { - for (Player player : forId(regionX << 8 | regionY).getPlanes()[l.getZ()].getPlayers()) { - if (player.getLocation().withinDistance(l, distance)) { - players.add(player); - } - } - } - } - return players; - } - - /** - * Gets a list of local players within 16 tile distance of the location. - * @param l The location. - * @return The list of players. - */ - public static List getLocalNpcs(Location l) { - return getLocalNpcs(l, MapDistance.RENDERING.getDistance()); - } - - /** - * Gets the npc. - * @param entity the entity. - * @param id the id. - */ - public static NPC getNpc(final Entity entity, final int id) { - return getNpc(entity, id, 16); - } - - /** - * Gets the npc. - * @param entity the entity. - * @param id the id. - * @param distance the distance. - * @return the npc. - */ - public static NPC getNpc(Entity entity, int id, int distance) { - return getNpc(entity.getLocation(), id, distance); - } - - /** - * Gets an npc near the entity. - * @param id the id, - * @param distance the dinstance. - * @return the npc. - */ - public static NPC getNpc(final Location location, int id, int distance) { - List npcs = getLocalNpcs(location, distance); - for (NPC n : npcs) { - if (n.getId() == id) { - return n; - } - } - return null; - } - - /** - * Gets a list of local players. - * @param l The location. - * @param distance The distance to that location. - * @return The list of players. - */ - public static List getLocalNpcs(Location l, int distance) { - List npcs = new LinkedList<>(); - for (int regionX = (l.getRegionX() - 6) >> 3; regionX <= (l.getRegionX() + 6) >> 3; regionX++) { - for (int regionY = (l.getRegionY() - 6) >> 3; regionY <= (l.getRegionY() + 6) >> 3; regionY++) { - for (NPC n : forId(regionX << 8 | regionY).getPlanes()[l.getZ()].getNpcs()) { - if (n.getLocation().withinDistance(l, (n.size() >> 1) + distance)) { - npcs.add(n); - } - } - } - } - return npcs; - } - - /** - * Gets the regionCache. - * @return The regionCache. - */ - public static Map getRegionCache() { - return REGION_CACHE; - } -} \ No newline at end of file diff --git a/Server/src/main/java/core/game/world/map/RegionManager.kt b/Server/src/main/java/core/game/world/map/RegionManager.kt new file mode 100644 index 000000000..93d5e891b --- /dev/null +++ b/Server/src/main/java/core/game/world/map/RegionManager.kt @@ -0,0 +1,807 @@ +package core.game.world.map + +import core.game.world.map.RegionManager +import core.game.world.map.RegionPlane +import core.game.node.`object`.Scenery +import core.game.node.Node +import core.game.node.entity.Entity +import core.game.node.entity.npc.NPC +import core.game.node.entity.player.Player +import core.game.world.map.zone.ZoneBorders +import core.tools.RandomFunction +import rs09.game.system.SystemLogger +import java.util.* +import java.util.concurrent.TimeUnit +import java.util.concurrent.locks.ReentrantLock + +/** + * Manages the regions. + * @author Emperor + */ +object RegionManager { + /** + * The region cache mapping. + */ + private val REGION_CACHE: MutableMap = HashMap() + + public val LOCK = ReentrantLock() + + /** + * Gets the region for the given region id. + * @param regionId The region id. + * @return The region. + */ + @JvmStatic + fun forId(regionId: Int): Region { + LOCK.tryLock(10000, TimeUnit.MILLISECONDS) + var region = REGION_CACHE[regionId] + if (region == null) { + region = Region((regionId shr 8) and 0xFF, regionId and 0xFF) + if(region!!.regionId != regionId){ + SystemLogger.logErr("IDs do NOT match - ${region!!.regionId} supposed to be $regionId") + } + REGION_CACHE[regionId] = region + } + LOCK.unlock() + return REGION_CACHE[regionId]!! + } + + /** + * Pulses the active regions. + */ + @JvmStatic + fun pulse() { + LOCK.tryLock(10000,TimeUnit.MILLISECONDS) + for (r in REGION_CACHE.values) { + if (r != null && r.isActive) { + for (p in r.planes) { + p.pulse() + } + } + } + LOCK.unlock() + } + + /** + * Gets the clipping flag on the given location. + * @param l The location. + * @return The clipping flag. + */ + @JvmStatic + fun getClippingFlag(l: Location): Int { + return getClippingFlag(l.z, l.x, l.y) + } + + /** + * Gets the clipping flag. + * @param z The plane. + * @param x The absolute x-coordinate. + * @param y The absolute y-coordinate. + * @return The clipping flags. + */ + @JvmStatic + fun getClippingFlag(z: Int, x: Int, y: Int): Int { + var x = x + var y = y + val region = forId(((x shr 6) shl 8) or (y shr 6)) + Region.load(region) + if (!region.isHasFlags) { + return -1 + } + x -= x shr 6 shl 6 + y -= y shr 6 shl 6 + return region.planes[z].flags.clippingFlags[x][y] + } + + /** + * Gets the water variant of a tile's clipping flag + * Essentially strips the landscape flag off a tile and keeps other flags, and makes normally walkable tiles unwalkable. + * @author Ceikry + */ + @JvmStatic + fun getWaterClipFlag(z: Int, x: Int, y: Int): Int { + val flag = getClippingFlag(z, x, y) + return if (!isClipped(z, x, y)) { + flag or 0x100 + } else flag and 0x200000.inv() + } + + /** + * Checks if the tile is part of the landscape. + * @param l The location. + * @return `True` if so. + */ + @JvmStatic + fun isLandscape(l: Location): Boolean { + return isLandscape(l.z, l.x, l.y) + } + + /** + * Checks if the tile is part of the landscape. + * @param z The plane. + * @param x The absolute x-coordinate. + * @param y The absolute y-coordinate. + * @return `True` if so. + */ + @JvmStatic + fun isLandscape(z: Int, x: Int, y: Int): Boolean { + var x = x + var y = y + val region = forId(((x shr 6) shl 8) or (y shr 6)) + Region.load(region) + if (!region.isHasFlags || region.planes[z].flags.landscape == null) { + return false + } + x -= x shr 6 shl 6 + y -= y shr 6 shl 6 + return region.planes[z].flags.landscape[x][y] + } + + /** + * Sets the clipping flag on the given location. + * @param l The location. + * @param flag The flag to set. + */ + @JvmStatic + fun setClippingFlag(l: Location, flag: Int) { + var x = l.x + var y = l.y + val z = l.z + val region = forId(((x shr 6) shl 8) or (y shr 6)) + Region.load(region) + if (!region.isHasFlags) { + return + } + x -= x shr 6 shl 6 + y -= y shr 6 shl 6 + region.planes[z].flags.clippingFlags[x][y] = flag + } + + /** + * Adds a clipping flag. + * @param z The plane. + * @param x The absolute x-coordinate. + * @param y The absolute y-coordinate. + * @param projectile If the flag is being set for projectile pathfinding. + * @param flag The clipping flag. + */ + @JvmStatic + fun addClippingFlag(z: Int, x: Int, y: Int, projectile: Boolean, flag: Int) { + var x = x + var y = y + val region = forId(((x shr 6) shl 8) or (y shr 6)) + Region.load(region) + if (!region.isHasFlags) { + return + } + x -= x shr 6 shl 6 + y -= y shr 6 shl 6 + if (projectile) { + region.planes[z].projectileFlags.flag(x, y, flag) + } else { + region.planes[z].flags.flag(x, y, flag) + } + } + + /** + * Adds a clipping flag. + * @param z The plane. + * @param x The absolute x-coordinate. + * @param y The absolute y-coordinate. + * @param projectile If the flag is being set for projectile pathfinding. + * @param flag The clipping flag. + */ + @JvmStatic + fun removeClippingFlag(z: Int, x: Int, y: Int, projectile: Boolean, flag: Int) { + var x = x + var y = y + val region = forId(((x shr 6) shl 8) or (y shr 6)) + Region.load(region) + if (!region.isHasFlags) { + return + } + x -= x shr 6 shl 6 + y -= y shr 6 shl 6 + if (projectile) { + region.planes[z].projectileFlags.unflag(x, y, flag) + } else { + region.planes[z].flags.unflag(x, y, flag) + } + } + + /** + * Gets the clipping flag. + * @param z The plane. + * @param x The absolute x-coordinate. + * @param y The absolute y-coordinate. + * @return The clipping flags. + */ + @JvmStatic + fun getProjectileFlag(z: Int, x: Int, y: Int): Int { + var x = x + var y = y + val region = forId(((x shr 6) shl 8) or (y shr 6)) + Region.load(region) + if (!region.isHasFlags) { + return -1 + } + x -= x shr 6 shl 6 + y -= y shr 6 shl 6 + return region.planes[z].projectileFlags.clippingFlags[x][y] + } + + /** + * Gets the clipping flag + * @param location the Location + * @return the clipping flag + */ + @JvmStatic + fun isTeleportPermitted(location: Location): Boolean { + return isTeleportPermitted(location.z, location.x, location.y) + } + + /** + * Gets the clipping flag. + * @param z The plane. + * @param x The absolute x-coordinate. + * @param y The absolute y-coordinate. + * @return The clipping flags. + */ + @JvmStatic + fun isTeleportPermitted(z: Int, x: Int, y: Int): Boolean { + if (!isLandscape(z, x, y)) { + return false + } + val flag = getClippingFlag(z, x, y) + return flag and 0x12c0102 == 0 || flag and 0x12c0108 == 0 || flag and 0x12c0120 == 0 || flag and 0x12c0180 == 0 + } + + /** + * Checks if the location has any clipping flags. + * @param location The location. + * @return `True` if a clipping flag disables access for this location. + */ + @JvmStatic + fun isClipped(location: Location): Boolean { + return isClipped(location.z, location.x, location.y) + } + + /** + * Checks if the location has any clipping flags. + * @param z The plane. + * @param x The x-coordinate. + * @param y The y-coordinate. + * @return `True` if a clipping flag disables access for this location. + */ + @JvmStatic + fun isClipped(z: Int, x: Int, y: Int): Boolean { + if (!isLandscape(z, x, y)) { + return true + } + val flag = getClippingFlag(z, x, y) + return flag and 0x12c0102 != 0 || flag and 0x12c0108 != 0 || flag and 0x12c0120 != 0 || flag and 0x12c0180 != 0 + } + + /** + * Gets the spawn location of a node. + * @param owner the owner. + * @param node the node. + * @return the location. + */ + @JvmStatic + fun getSpawnLocation(owner: Player?, node: Node?): Location? { + if (owner == null || node == null) { + return null + } + var destination: Location? = null + for (i in 0..3) { + val dir = Direction.get(i) + val l = owner.location.transform(dir, if (dir.toInteger() < 2) 1 else node.size()) + var success = true + for (x in 0 until node.size()) { + for (y in 0 until node.size()) { + if (isClipped(l.transform(x, y, 0))) { + success = false + break + } + } + } + if (success) { + destination = l + break + } + } + return destination + } + + /** + * Gets the scenery on the current location. + * @param l The location. + * @return The scenery, or `null` if no object was found. + */ + @JvmStatic + fun getObject(l: Location): Scenery? { + return getObject(l.z, l.x, l.y) + } + + /** + * Gets the scenery on the current absolute coordinates. + * @param z The height. + * @param x The x-coordinate. + * @param y The y-coordinate. + * @return The scenery, or `null` if no object was found. + */ + @JvmStatic + fun getObject(z: Int, x: Int, y: Int): Scenery? { + return getObject(z, x, y, -1) + } + + /** + * Gets the object on the given absolute coordinates. + * @param z The height. + * @param x The x-coordinate. + * @param y The y-coordinate. + * @param objectId The object id. + * @return The scenery, or `null` if no object was found. + */ + @JvmStatic + fun getObject(z: Int, x: Int, y: Int, objectId: Int): Scenery? { + var x = x + var y = y + val regionId = ((x shr 6) shl 8) or (y shr 6) + x -= (x shr 6) shl 6 + y -= (y shr 6) shl 6 + val region = forId(regionId) + Region.load(region) + val `object`: Scenery? = region.planes[z].getChunkObject(x, y, objectId) + return if (`object` != null && !`object`.isRenderable()) { + null + } else `object` + } + + /** + * Gets the region plane for this location. + * @param l The location. + * @return The region plane. + */ + @JvmStatic + fun getRegionPlane(l: Location): RegionPlane { + val regionId = ((l.x shr 6) shl 8) or (l.y shr 6) + return forId(regionId).planes[l.z] + } + + /** + * Gets a region chunk. + * @param l The location. + * @return The region chunk. + */ + @JvmStatic + fun getRegionChunk(l: Location): RegionChunk { + val plane = getRegionPlane(l) + return plane.getRegionChunk(l.localX / RegionChunk.SIZE, l.localY / RegionChunk.SIZE) + } + + /** + * Moves the entity from the current region to the new one. + * @param entity The entity. + */ + @JvmStatic + fun move(entity: Entity) { + val player = entity is Player + val regionId = ((entity.location.regionX shr 3) shl 8) or (entity.location.regionY shr 3) + val viewport = entity.viewport + val current = forId(regionId) + val z = entity.location.z + val plane = current.planes[z] + viewport.updateViewport(entity) + if (plane == viewport.currentPlane) { + entity.zoneMonitor.updateLocation(entity.walkingQueue.footPrint) + return + } + viewport.remove(entity) + if (player) { + current.add(entity as Player) + } else { + current.add(entity as NPC) + } + viewport.region = current + viewport.currentPlane = plane + val view: MutableList = LinkedList() + for (regionX in ((entity.location.regionX shr 3) - 1)..((entity.location.regionX shr 3) + 1)) { + for (regionY in ((entity.location.regionY shr 3) - 1)..((entity.location.regionY shr 3) + 1)) { + if (regionX < 0 || regionY < 0) { + continue + } + val region = forId((regionX shl 8) or regionY) + val p = region.planes[z] + if (player) { + region.incrementViewAmount() + region.flagActive() + } + view.add(p) + } + } + viewport.viewingPlanes = view + entity.zoneMonitor.updateLocation(entity.walkingQueue.footPrint) + } + + /** + * Gets the list of local NPCs with a maximum distance of 16. + * @param n The entity. + * @return The list of local NPCs. + */ + @JvmStatic + fun getLocalNpcs(n: Entity): List { + return getLocalNpcs(n, MapDistance.RENDERING.distance) + } + + /** + * Gets the location entitys. + * @param location the location. + * @param distance the distance. + * @return the list. + */ + @JvmStatic + fun getLocalEntitys(location: Location, distance: Int): List { + val entitys: MutableList = ArrayList(20) + entitys.addAll(getLocalNpcs(location, distance)) + entitys.addAll(getLocalPlayers(location, distance)) + return entitys + } + + /** + * Gets the location entitys. + * @param entity the entity. + * @param distance the distance. + * @return the list. + */ + @JvmStatic + fun getLocalEntitys(entity: Entity, distance: Int): List { + return getLocalEntitys(entity.location, distance) + } + + /** + * Gets the local entitys. + * @param entity the entity. + * @return the entitys. + */ + @JvmStatic + fun getLocalEntitys(entity: Entity): List { + return getLocalEntitys(entity.location, MapDistance.RENDERING.distance) + } + + /** + * Gets the list of local NPCs. + * @param n The entity. + * @param distance The distance to the entity. + * @return The list of local NPCs. + */ + @JvmStatic + fun getLocalNpcs(n: Entity, distance: Int): List { + val npcs: MutableList = LinkedList() + for (r in n.viewport.viewingPlanes) { + for (npc in r.npcs) { + if (npc.location.withinDistance(n.location, distance)) { + npcs.add(npc) + } + } + } + return npcs + } + + /** + * Gets the list of local players with a maximum distance of 15. + * @param n The entity. + * @return The list of local players. + */ + @JvmStatic + fun getLocalPlayers(n: Entity): List { + return getLocalPlayers(n, MapDistance.RENDERING.distance) + } + + /** + * Gets the list of local players. + * @param n The entity. + * @param distance The distance to the entity. + * @return The list of local players. + */ + @JvmStatic + fun getLocalPlayers(n: Entity, distance: Int): List { + val players: MutableList = LinkedList() + for (r in n.viewport.viewingPlanes) { + for (p in r.players) { + if (p.location.withinDistance(n.location, distance)) { + players.add(p) + } + } + } + return players + } + + /** + * Gets the surrounding players. + * @param n The node the players should be surrounding. + * @param ignore The nodes not to add to the list. + * @return The list of players. + */ + @JvmStatic + fun getSurroundingPlayers(n: Node, vararg ignore: Node): List { + return getSurroundingPlayers(n, 9, *ignore) + } + + /** + * Gets the surrounding players. + * @param n The node the players should be surrounding. + * @param ignore The nodes not to add to the list. + * @return The list of players. + */ + @JvmStatic + fun getSurroundingPlayers(n: Node, maximum: Int, vararg ignore: Node): List { + val players = getLocalPlayers(n.location, 1) + var count = 0 + val it = players.iterator() + while (it.hasNext()) { + val p = it.next() + if (++count >= maximum) { + it.remove() + continue + } + for (node in ignore) { + if (p === node) { + count-- + it.remove() + break + } + } + } + return players + } + + /** + * Gets the surrounding players. + * @param n The node the players should be surrounding. + * @param ignore The nodes not to add to the list. + * @return The list of players. + */ + @JvmStatic + fun getSurroundingNPCs(n: Node, vararg ignore: Node): List { + return getSurroundingNPCs(n, 9, *ignore) + } + + /** + * Gets the surrounding players. + * @param n The node the npcs should be surrounding. + * @param ignore The nodes not to add to the list. + * @return The list of npcs. + */ + @JvmStatic + fun getSurroundingNPCs(n: Node, maximum: Int, vararg ignore: Node): List { + val npcs = getLocalNpcs(n.location, 1) + var count = 0 + val it = npcs.iterator() + while (it.hasNext()) { + val p = it.next() + if (++count > maximum) { + it.remove() + continue + } + for (node in ignore) { + if (p === node) { + count-- + it.remove() + break + } + } + } + return npcs + } + + /** + * Gets a random teleport location in the radius around the given location. + * @param location The centre location. + * @param radius The radius. + * @return A random teleport location. + */ + @JvmStatic + fun getTeleportLocation(location: Location, radius: Int): Location { + var radius = radius + var mod = radius shr 1 + if (mod == 0) { + mod++ + radius-- + } + return getTeleportLocation(location.transform(-mod, -mod, 0), mod + radius, mod + radius) + } + + /** + * Gets a random teleport location in the radius around the given location. + * @param location The centre location. + * @return A random teleport location. + */ + @JvmStatic + fun getTeleportLocation(location: Location, areaX: Int, areaY: Int): Location { + var destination = location + var x: Int = RandomFunction.random(1 + areaX) + var y: Int = RandomFunction.random(1 + areaY) + var count = 0 + while (!isTeleportPermitted(location.transform(x, y, 0).also { destination = it })) { + x = RandomFunction.random(1 + areaX) + y = RandomFunction.random(1 + areaY) + if (count++ >= areaX * 2) { + //This would be able to keep looping for several seconds otherwise (this actually happens). + x = 0 + while (x < areaX + 1) { + y = 0 + while (y < areaY + 1) { + if (isTeleportPermitted(location.transform(x, y, 0).also { destination = it })) { + return destination + } + y++ + } + x++ + } + break + } + } + return destination + } + + /** + * Gets the current viewport for the location. + * @param l The location. + * @return The viewport. + */ + @JvmStatic + fun getViewportPlayers(l: Location): List { + var l = l + val players: MutableList = LinkedList() + l = l.chunkBase.transform(-8, -8, 0) + val b = ZoneBorders(l.x, l.y, l.x + 24, l.y + 24) + for (regionX in ((l.regionX - 6) shr 3)..((l.regionX + 6) shr 3)) { + for (regionY in ((l.regionY - 6) shr 3)..((l.regionY + 6) shr 3)) { + for (player in forId(regionX shl 8 or regionY).planes[l.z].players) { + l = player.location + if (b.insideBorder(l.x, l.y)) { + players.add(player) + } + } + } + } + return players + } + + /** + * Gets a list of players in the given region. + * @param regionId The region id. + * @return The list of players in this region. + */ + @JvmStatic + fun getRegionPlayers(regionId: Int): List { + val r = forId(regionId) + val players: MutableList = ArrayList(20) + for (plane in r.planes) { + players.addAll(plane.players) + } + return players + } + + /** + * Gets a list of local players within rendering distance of the location. + * @param l The location. + * @return The list of players. + */ + @JvmStatic + fun getLocalPlayers(l: Location): List { + return getLocalPlayers(l, MapDistance.RENDERING.distance) + } + + /** + * Gets a list of local players. + * @param l The location. + * @param distance The distance to that location. + * @return The list of players. + */ + @JvmStatic + fun getLocalPlayers(l: Location, distance: Int): MutableList { + val players: MutableList = LinkedList() + for (regionX in ((l.regionX - 6) shr 3)..((l.regionX + 6) shr 3)) { + for (regionY in ((l.regionY - 6) shr 3)..((l.regionY + 6) shr 3)) { + for (player in forId((regionX shl 8) or regionY).planes[l.z].players) { + if (player.location.withinDistance(l, distance)) { + players.add(player) + } + } + } + } + return players + } + + /** + * Gets a list of local players within 16 tile distance of the location. + * @param l The location. + * @return The list of players. + */ + @JvmStatic + fun getLocalNpcs(l: Location): List { + return getLocalNpcs(l, MapDistance.RENDERING.distance) + } + + /** + * Gets the npc. + * @param entity the entity. + * @param id the id. + */ + @JvmStatic + fun getNpc(entity: Entity, id: Int): NPC? { + return getNpc(entity, id, 16) + } + + /** + * Gets the npc. + * @param entity the entity. + * @param id the id. + * @param distance the distance. + * @return the npc. + */ + @JvmStatic + fun getNpc(entity: Entity, id: Int, distance: Int): NPC? { + return getNpc(entity.location, id, distance) + } + + /** + * Gets an npc near the entity. + * @param id the id, + * @param distance the dinstance. + * @return the npc. + */ + @JvmStatic + fun getNpc(location: Location, id: Int, distance: Int): NPC? { + val npcs: List = getLocalNpcs(location, distance) + for (n in npcs) { + if (n.id == id) { + return n + } + } + return null + } + + /** + * Gets a list of local players. + * @param l The location. + * @param distance The distance to that location. + * @return The list of players. + */ + @JvmStatic + fun getLocalNpcs(l: Location, distance: Int): MutableList { + val npcs: MutableList = LinkedList() + for (regionX in ((l.regionX - 6) shr 3)..((l.regionX + 6) shr 3)) { + for (regionY in ((l.regionY - 6) shr 3)..((l.regionY + 6) shr 3)) { + for (n in forId(regionX shl 8 or regionY).planes[l.z].npcs) { + if (n.location.withinDistance(l, (n.size() shr 1) + distance)) { + npcs.add(n) + } + } + } + } + return npcs + } + + @JvmStatic + fun addRegion(id: Int, region: Region){ + LOCK.tryLock(10000, TimeUnit.MILLISECONDS) + REGION_CACHE[id] = region + LOCK.unlock() + } + + /** + * Gets the regionCache. + * @return The regionCache. + */ + val regionCache: Map + @JvmStatic get(){ + return REGION_CACHE + } + + val lock: ReentrantLock + @JvmStatic get() = LOCK +} \ No newline at end of file diff --git a/Server/src/main/java/core/game/world/map/build/DynamicRegion.java b/Server/src/main/java/core/game/world/map/build/DynamicRegion.java index 10f231770..48ae4f9c6 100644 --- a/Server/src/main/java/core/game/world/map/build/DynamicRegion.java +++ b/Server/src/main/java/core/game/world/map/build/DynamicRegion.java @@ -122,14 +122,14 @@ public final class DynamicRegion extends Region { Location loc = l.transform((x - baseX) << 6, (y - baseY) << 6, 0); DynamicRegion region = copy(regionId, loc); region.setBorders(border); - Region r = RegionManager.getRegionCache().get(region.getId()); + Region r = RegionManager.forId(region.getId()); if (r != null) { for (int z = 0; z < 4; z++) { region.getPlanes()[z].getPlayers().addAll(r.getPlanes()[z].getPlayers()); region.getPlanes()[z].getNpcs().addAll(r.getPlanes()[z].getNpcs()); } } - RegionManager.getRegionCache().put(region.getId(), region); + RegionManager.addRegion(region.getId(), region); regions.add(region); } } diff --git a/Server/src/main/kotlin/rs09/game/ai/general/ScriptAPI.kt b/Server/src/main/kotlin/rs09/game/ai/general/ScriptAPI.kt index 18226073b..0f16063fd 100644 --- a/Server/src/main/kotlin/rs09/game/ai/general/ScriptAPI.kt +++ b/Server/src/main/kotlin/rs09/game/ai/general/ScriptAPI.kt @@ -247,7 +247,7 @@ class ScriptAPI(private val bot: Player) { * @return an Array of the nearest NPCs with matching name, or null. * @author Ceikry */ - private fun findTargets(entity: Entity?, radius: Int, name: String? = null): List? { + private fun findTargets(entity: Entity, radius: Int, name: String? = null): List? { val targets: MutableList = ArrayList() val localNPCs: Array = RegionManager.getLocalNpcs(entity, radius).toTypedArray() var length = localNPCs.size diff --git a/Server/src/main/kotlin/rs09/game/system/config/ConfigParser.kt b/Server/src/main/kotlin/rs09/game/system/config/ConfigParser.kt index 68b6b6c20..99b15d2e1 100644 --- a/Server/src/main/kotlin/rs09/game/system/config/ConfigParser.kt +++ b/Server/src/main/kotlin/rs09/game/system/config/ConfigParser.kt @@ -4,29 +4,20 @@ import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch class ConfigParser { - fun prePlugin() = GlobalScope.launch{ - launch { + fun prePlugin() { NPCConfigParser().load() ItemConfigParser().load() - } - launch { ObjectConfigParser().load() XteaParser().load() - } - InterfaceConfigParser().load() + InterfaceConfigParser().load() } - - fun postPlugin() = GlobalScope.launch{ - launch { + fun postPlugin() { ShopParser().load() DropTableParser().load() NPCSpawner().load() - } - launch { DoorConfigLoader().load() GroundSpawnLoader().load() MusicConfigLoader().load() - } - RangedConfigLoader().load() + RangedConfigLoader().load() } } \ No newline at end of file