diff --git a/Server/src/main/content/data/EnchantedJewellery.kt b/Server/src/main/content/data/EnchantedJewellery.kt index 242c9305f..251bfa6c1 100644 --- a/Server/src/main/content/data/EnchantedJewellery.kt +++ b/Server/src/main/content/data/EnchantedJewellery.kt @@ -193,7 +193,7 @@ enum class EnchantedJewellery( ), RING_OF_LIFE(arrayOf(), arrayOf( - Location.create(ServerConstants.HOME_LOCATION) + Location.create(ServerConstants.HOME_LOCATION!!) ), true, Items.RING_OF_LIFE_2570 diff --git a/Server/src/main/content/global/handlers/npc/SheepBehavior.kt b/Server/src/main/content/global/handlers/npc/SheepBehavior.kt index fbd283e77..c6ed46374 100644 --- a/Server/src/main/content/global/handlers/npc/SheepBehavior.kt +++ b/Server/src/main/content/global/handlers/npc/SheepBehavior.kt @@ -129,9 +129,9 @@ class SheepBehavior : NPCBehavior(*sheepIds), InteractionListener { val sheepDirection = Direction.getDirection(sheepLocation, playerLocation) // Get direction sheep is facing, from the player's location val sheepOppositeDirection = sheepDirection.getOpposite() // Switch to opposite direction - val xWalkLocation = sheepLocation.getX() + (sheepOppositeDirection.getStepX() * 3) // Gets x location, if set, 3 steps away from player - val yWalkLocation = sheepLocation.getY() + (sheepOppositeDirection.getStepY() * 3) // Gets y location, if set, 3 steps away from player - val sheepWalkToLocation = Location(xWalkLocation, yWalkLocation, sheepLocation.getZ()); // New location for pathfinding + val xWalkLocation = sheepLocation.x + (sheepOppositeDirection.getStepX() * 3) // Gets x location, if set, 3 steps away from player + val yWalkLocation = sheepLocation.y + (sheepOppositeDirection.getStepY() * 3) // Gets y location, if set, 3 steps away from player + val sheepWalkToLocation = Location(xWalkLocation, yWalkLocation, sheepLocation.z); // New location for pathfinding sendMessage(player, messagePlayer) diff --git a/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt b/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt index d636ee588..b558ec38f 100644 --- a/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt +++ b/Server/src/main/content/global/skill/crafting/lightsources/LightSourceLighter.kt @@ -79,7 +79,7 @@ class LightSourceLighter : UseWithHandler(590,36,38){ // For Temple of Ikov - if you are in the dark basement and light a light source, switch to the light basement. // For the listener that covers the firemaking cape perk, see content.global.skill.skillcapeperks.SkillcapePerks.kt if(event.player.location.isInRegion(10648) && event.player.location.withinDistance(Location(2639,9738,0), 8)) { - teleport(event.player, Location.create(event.player.getLocation().getX(), event.player.getLocation().getY() + 23, event.player.getLocation().getZ())) + teleport(event.player, Location.create(event.player.getLocation().x, event.player.getLocation().y + 23, event.player.getLocation().z)) closeDialogue(event.player) // Dark basement is region 10648, min 2639 9738 0, max 2643 9744 0. Add 23 to the Y loc to tele to light basement } diff --git a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt index 28cfc2c86..5a4a137b8 100644 --- a/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt +++ b/Server/src/main/content/global/skill/gather/woodcutting/WoodcuttingListener.kt @@ -270,7 +270,7 @@ class WoodcuttingListener : InteractionListener { var amount = amount var experience: Double = resource.getExperience() val reward = resource.reward - if (player.getLocation().getRegionId() == 10300) { + if (player.getLocation().regionId == 10300) { return 1.0 } diff --git a/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt b/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt index 7bb35b190..148696543 100644 --- a/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt +++ b/Server/src/main/content/global/skill/skillcapeperks/SkillcapePerks.kt @@ -142,7 +142,7 @@ enum class SkillcapePerks(val attribute: String, val effect: ((Player) -> Unit)? // For Temple of Ikov - if you are in the dark basement and put on the firemaking cape with its 2009Scape light source perk, switch to the light basement. // For the listener that teleports if you light a normal light source, see content.global.skill.crafting.lightsources.LightSourceLighter.kt if(player.location.isInRegion(10648) && player.location.withinDistance(Location(2639,9738,0), 8)) { - teleport(player, Location.create(player.getLocation().getX(), player.getLocation().getY() + 23, player.getLocation().getZ())) + teleport(player, Location.create(player.getLocation().x, player.getLocation().y + 23, player.getLocation().z)) closeDialogue(player) } } diff --git a/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt b/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt index 9478c6656..529b497a6 100644 --- a/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt +++ b/Server/src/main/content/region/misthalin/draynor/handlers/DraynorManorHouseZone.kt @@ -31,10 +31,10 @@ class DraynorManorHouseZone : MapZone("Draynor Manor House", true), Plugin if (n.shouldPreventStacking(e)) { val s1 = e.size() val s2 = n.size() - val x = destination.getX() - val y = destination.getY() + val x = destination.x + val y = destination.y val l = n.getLocation() - if (Pathfinder.isStandingIn(x, y, s1, s1, l.getX(), l.getY(), s2, s2)) { + if (Pathfinder.isStandingIn(x, y, s1, s1, l.x, l.y, s2, s2)) { return false } } diff --git a/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/GardenerGhostDialogue.kt b/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/GardenerGhostDialogue.kt index 70387d2c8..6c7bd9830 100644 --- a/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/GardenerGhostDialogue.kt +++ b/Server/src/main/content/region/morytania/quest/creatureoffenkenstrain/GardenerGhostDialogue.kt @@ -18,7 +18,7 @@ class GardenerGhostDialogue (player: Player? = null) : DialoguePlugin(player) { if (gardenerGhost.location.withinDistance(gardenerGhost.graveLocation, 5)) { sendChat(gardenerGhost, "Here is the place where I met me' maker.") } else { - sendChat(gardenerGhost, "Go " + gardenerGhost.location.deriveDirection(gardenerGhost.graveLocation).name.lowercase(Locale.getDefault()).replace('_', '-') + ", mate") + sendChat(gardenerGhost, "Go " + gardenerGhost.location.deriveDirection(gardenerGhost.graveLocation)!!.name.lowercase(Locale.getDefault()).replace('_', '-') + ", mate") gardenerGhost.continueFollowing(player) } } else { diff --git a/Server/src/main/core/game/system/command/oldsys/VisualCommand.kt b/Server/src/main/core/game/system/command/oldsys/VisualCommand.kt index fc2a08c28..17d98c883 100644 --- a/Server/src/main/core/game/system/command/oldsys/VisualCommand.kt +++ b/Server/src/main/core/game/system/command/oldsys/VisualCommand.kt @@ -197,7 +197,7 @@ class VisualCommand : CommandPlugin() { return true } location = if (args.size > 2) Location.create(args[1]!!.toInt(), args[2]!!.toInt(), player!!.location.z) else player!!.location - `object` = RegionManager.getObject(location) + `object` = RegionManager.getObject(location!!) if (`object` == null) { player!!.debug("error: object not found in region cache.") return true diff --git a/Server/src/main/core/game/world/map/Location.java b/Server/src/main/core/game/world/map/Location.java deleted file mode 100644 index ac4b1b310..000000000 --- a/Server/src/main/core/game/world/map/Location.java +++ /dev/null @@ -1,578 +0,0 @@ -package core.game.world.map; - -import core.game.interaction.DestinationFlag; -import core.game.node.Node; -import core.game.world.map.path.Path; -import core.game.world.map.path.Pathfinder; -import core.tools.RandomFunction; -import org.jetbrains.annotations.NotNull; -import core.api.utils.Vector; - -import java.util.ArrayList; -import java.util.List; -import java.lang.Math; - -/** - * Represents a location on the world map. - * @author Emperor - */ -public final class Location extends Node { - - /** - * The x-coordinate. - */ - private int x; - - /** - * The y-coordinate. - */ - private int y; - - /** - * The plane. - */ - private int z; - - /** - * Constructs a new {@code Location} {@code Object}. - * @param x The x-coordinate. - * @param y The y-coordinate. - * @param z The z-coordinate. - */ - public Location(int x, int y, int z) { - super(null, null); - super.destinationFlag = DestinationFlag.LOCATION; - this.x = x; - this.y = y; - if (z < 0) { - z += 4; - } - this.z = z; - } - - /** - * Constructs a new {@code Location} {@code Object} - * @param x The x-coordinate. - * @param y The y-coordinate. - */ - public Location(int x, int y) { - this(x, y, 0); - } - - /** - * Constructs a new {@code Location} {@code Object}. - * @param x The x-coordinate. - * @param y The y coordinate. - * @param z The z-coordinate. - * @param randomizer The amount we should randomize the x and y coordinates - * with (x + random(randomizer), y + random(randomizer)). - */ - public Location(int x, int y, int z, int randomizer) { - this(x + RandomFunction.getRandom(randomizer), y + RandomFunction.getRandom(randomizer), z); - } - - /** - * Construct a new Location. - * @param x The x-coordinate. - * @param y The y-coordinate. - * @param z The z-coordinate. - * @return The constructed location. - */ - public static Location create(int x, int y, int z) { - return new Location(x, y, z); - } - - public static Location create(int x, int y) { - return new Location(x, y, 0); - } - - /** - * Creates a new instance of the given location. - * @param location The given location. - * @return The new instance. - */ - public static Location create(Location location) { - return create(location.getX(), location.getY(), location.getZ()); - } - - /** - * Creates a location instance with coordinates being the difference between - * {@code other} & {@code location}. - * @param location The first location. - * @param other The other location. - * @return The delta location. - */ - public static Location getDelta(Location location, Location other) { - return Location.create(other.x - location.x, other.y - location.y, other.z - location.z); - } - - /** - * Gets a random location near the main location. - * @param main The main location. - * @param radius The radius. - * @param reachable If the locations should be able to reach eachother. - * @return The location. - */ - public static Location getRandomLocation(Location main, int radius, boolean reachable) { - Location location = RegionManager.getTeleportLocation(main, radius); - if (!reachable) { - return location; - } - Path path = Pathfinder.find(main, location, false, Pathfinder.DUMB); - if (!path.isSuccessful()) { - location = main; - if (!path.getPoints().isEmpty()) { - Point p = path.getPoints().getLast(); - location = Location.create(p.getX(), p.getY(), main.getZ()); - } - } - return location; - } - - @Override - public Location getLocation() { - return this; - } - - /** - * Checks if this location is right next to the node (assuming the node is - * size 1x1). - * @param node The node to check. - * @return {@code True} if this location is 1 tile north, west, south or - * east of the node location. - */ - public boolean isNextTo(Node node) { - Location l = node.getLocation(); - if (l.getY() == y) { - return l.getX() - x == -1 || l.getX() - x == 1; - } - if (l.getX() == x) { - return l.getY() - y == -1 || l.getY() - y == 1; - } - return false; - } - - /** - * Gets the region id. - * @return The region id. - */ - public int getRegionId() { - return (x >> 6) << 8 | (y >> 6); - } - - /** - * Compares the users region with the one given - * @return True if user is in given region - */ - public boolean isInRegion(int region) { - return getRegionId() == region; - } - - /** - * Gets the location incremented by the given coordinates. - * @param dir The direction to transform this location. - * @return The location. - */ - public Location transform(Direction dir) { - return transform(dir, 1); - } - - /** - * Gets the location incremented by the given coordinates. - * @param dir The direction to transform this location. - * @param steps The amount of steps to move in this direction. - * @return The location. - */ - public Location transform(Direction dir, int steps) { - return new Location(x + (dir.getStepX() * steps), y + (dir.getStepY() * steps), this.z); - } - - /** - * Gets the location incremented by the given coordinates. - * @param l incremental location - * @return The location. - */ - public Location transform(Location l) { - return new Location(x + l.getX(), y + l.getY(), this.z + l.getZ()); - } - - /** - * Gets the location incremented by the given coordinates. - * @param diffX The x-difference. - * @param diffY The y-difference. - * @param z The height difference. - * @return The location. - */ - public Location transform(int diffX, int diffY, int z) { - return new Location(x + diffX, y + diffY, this.z + z); - } - - /** - * Checks if the other location is within viewing distance. - * @param other The other location. - * @return If you're within the other distance. - */ - public boolean withinDistance(Location other) { - return withinDistance(other, MapDistance.RENDERING.getDistance()); - } - - /** - * Returns if a player is within a specified distance. - * @param other The other location. - * @param dist The amount of distance. - * @return If you're within the other distance. - */ - public boolean withinDistance(Location other, int dist) { - if (other.z != z) { - return false; - } - - int a = (other.x - x); - int b = (other.y - y); - double product = Math.sqrt((a*a) + (b*b)); - return product <= dist; - } - - /** - * Returns if a player is within a specified distance using max norm distance. - * @param other The other location. - * @param dist The amount of distance. - * @return If you're within the other distance. - */ - public boolean withinMaxnormDistance(Location other, int dist) { - if (other.z != z) { - return false; - } - - int a = Math.abs(other.x - x); - int b = Math.abs(other.y - y); - double max = Math.max(a, b); - return max <= dist; - } - - /** - * Returns the distance between you and the other. - * @param other The other location. - * @return The amount of distance between you and other. - */ - public double getDistance(Location other) { - int xdiff = this.getX() - other.getX(); - int ydiff = this.getY() - other.getY(); - return Math.sqrt(xdiff * xdiff + ydiff * ydiff); - } - - /** - * Returns the distance between the first and the second specified distance. - * @param first The first location. - * @param second The other location. - * @return The amount of distance between first and other. - */ - public static double getDistance(Location first, Location second) { - int xdiff = first.getX() - second.getX(); - int ydiff = first.getY() - second.getY(); - return Math.sqrt(xdiff * xdiff + ydiff * ydiff); - } - - /** - * Gets the 8 tiles surrounding this location as an ArrayList - */ - public ArrayList getSurroundingTiles() { - ArrayList locs = new ArrayList<>(); - - locs.add(transform(-1,-1,0));//SW - locs.add(transform(0,-1,0)); //S - locs.add(transform(1,-1,0)); //SE - locs.add(transform(1,0,0)); //E - locs.add(transform(1,1,0)); //NE - locs.add(transform(0,1,0)); //N - locs.add(transform(-1,1,0));//NW - locs.add(transform(-1,0,0));//W - - return locs; - } - - public ArrayList getCardinalTiles() { - ArrayList locs = new ArrayList<>(); - - locs.add(transform(-1, 0, 0)); - locs.add(transform(0, -1, 0)); - locs.add(transform(1, 0, 0)); - locs.add(transform(0, 1, 0)); - return locs; - } - - /** - * Gets a square of 3 x 3 tiles as an ArrayList - */ - public ArrayList get3x3Tiles() { - ArrayList locs = new ArrayList<>(); - locs.add(transform(0,0,0)); //Center - locs.add(transform(0,1,0)); //N - locs.add(transform(1,1,0)); //NE - locs.add(transform(1,0,0)); //E - locs.add(transform(1,-1,0)); //SE - locs.add(transform(0,-1,0)); //S - locs.add(transform(-1,-1,0));//SW - locs.add(transform(-1,0,0));//W - locs.add(transform(-1,1,0));//NW - return locs; - } - - /** - * Gets the x position on the region chunk. - * @return The x position on the region chunk. - */ - public int getChunkOffsetX() { - int x = getLocalX(); - //return x - ((x / RegionChunk.SIZE) * RegionChunk.SIZE); - return x & 7; - } - - /** - * Gets the y position on the region chunk. - * @return The y position on the region chunk. - */ - public int getChunkOffsetY() { - int y = getLocalY(); - //return y - ((y / RegionChunk.SIZE) * RegionChunk.SIZE); - return y & 7; - } - - /** - * Gets the base location for the chunk this location is in. - * @return The base location. - */ - public Location getChunkBase() { - return create(getRegionX() << 3, getRegionY() << 3, z); - } - - /** - * Gets the region x-coordinate. - * @return The region x-coordinate. - */ - public int getRegionX() { - return x >> 3; - } - - /** - * Gets the region y-coordinate. - * @return The region y-coordinate. - */ - public int getRegionY() { - return y >> 3; - } - - /** - * Gets the local x-coordinate on the current region in [0, 64). - * @return The local x-coordinate. - */ - public int getLocalX() { - return x & 63; - } - - /** - * Gets the local y-coordinate on the current region in [0, 64). - * @return The local y-coordinate. - */ - public int getLocalY() { - return y & 63; - } - - /** - * Gets the scene x-coordinate in [48, 55] (note that 104/2 = 52). - * @return The local x-coordinate. - */ - public int getSceneX() { - return x - ((getRegionX() - 6) << 3); - } - - /** - * Gets the local y-coordinate in [48, 55] (note that 104/2 = 52). - * @return The local y-coordinate. - */ - public int getSceneY() { - return y - ((getRegionY() - 6) << 3); - } - - /** - * Gets the local x-coordinate. - * @param loc The location containing the regional coordinates. - * @return The local x-coordinate. - */ - public int getSceneX(Location loc) { - return x - ((loc.getRegionX() - 6) << 3); - } - - /** - * Gets the local y-coordinate. - * @param loc The location containing the regional coordinates. - * @return The local y-coordinate. - */ - public int getSceneY(Location loc) { - return y - ((loc.getRegionY() - 6) << 3); - } - - /** - * Gets the chunk's x-coordinate (0-7). - * @return The x in the (8x8) region. - */ - public int getChunkX() { - return getLocalX() >> 3; - } - - /** - * Gets the chunk's y-coordinate (0-7). - * @return The y in the (8x8) region. - */ - public int getChunkY() { - return getLocalY() >> 3; - } - - @Override - public boolean equals(Object other) { - if (!(other instanceof Location)) { - return false; - } - Location loc = (Location) other; - return loc.x == x && loc.y == y && loc.z == z; - } - - /** - * Checks if these coordinates equal this location. - * @param x the x. - * @param y the y. - * @param z the x. - * @return {@code True} if so. - */ - public boolean equals(int x, int y, int z) { - return equals(new Location(x, y, z)); - } - - @Override - public String toString() { - return "[" + x + ", " + y + ", " + z + "]"; - } - - public static Location fromString(String locString) { - String trimmed = locString.replace("[", "").replace("]", ""); - String[] tokens = trimmed.split(","); - return Location.create( - Integer.parseInt(tokens[0].trim()), - Integer.parseInt(tokens[1].trim()), - Integer.parseInt(tokens[2].trim()) - ); - } - - @Override - public int hashCode() { - return z << 30 | x << 15 | y; - } - - /** - * Gets the x. - * @return The x. - */ - public int getX() { - return x; - } - - /** - * Sets the x. - * @param x The x to set. - */ - public void setX(int x) { - this.x = x; - } - - /** - * Gets the y. - * @return The y. - */ - public int getY() { - return y; - } - - /** - * Sets the y. - * @param y The y to set. - */ - public void setY(int y) { - this.y = y; - } - - /** - * Gets the z. - * @return The z. - */ - public int getZ() { - return z % 4; - } - - /** - * Sets the z. - * @param z The z to set. - */ - public void setZ(int z) { - this.z = z; - } - - @NotNull - public List getStepComponents(Direction dir) { - List output = new ArrayList<>(2); - int stepX = dir.getStepX(); - int stepY = dir.getStepY(); - - // alright ill break it down real simple - // - // we can move diagonally but theres no melee attacking diagonally. its gotta be from N, W, S or E. - // if we are moving diagonally, we then need to check if we can attack from one of the valid directions. - // in other words we need to check the two tiles that share a corner with our destination and make sure they can be attacked from - // - // picture this: you are at (0,0). im at (1,1). you want to strangle my head off so you gotta check - // 1. to the west of destination: (1,1) + (-1,0) = (0,1) or north from where you started - // 2. to the south of the destination: (1,1) + (0,-1) = (1,0) or east from where you started - // this function is used only by CombatSwingHandler.kt and assumes that we return the X tiles, west or east, FIRST - // and then the Y tiles South and North. - // - // idk what else to say, if youre reading this youre either crazy or crazy good at weird ass shit like this - // and dont even need letters to understand, theres just numbers and arrows and fking formulas or something in ur head (and a lot of work to do here) - - if (stepX != 0) output.add(transform(-stepX, 0, 0)); // Ive never dealt with coordinate systems at this level, but it feels like Im in a cold, damp, deep and dark cave with fking goblins etc - if (stepY != 0) output.add(transform(0, -stepY, 0)); // and minotaurs - return output; - } - - public Direction deriveDirection(Location location) { - int diffX = location.x - this.x; - int diffY = location.y - this.y; - - diffX = diffX >= 0 ? Math.min(diffX, 1) : -1; - diffY = diffY >= 0 ? Math.min(diffY, 1) : -1; - - StringBuilder sb = new StringBuilder(); - - if (diffY != 0) { - if (diffY > 0) { - sb.append("NORTH"); - } else { - sb.append("SOUTH"); - } - } - - if (diffX != 0) { - if (sb.length() > 0) sb.append("_"); - if (diffX > 0) { - sb.append("EAST"); - } else { - sb.append("WEST"); - } - } - - if (sb.length() == 0) return null; - return Direction.valueOf(sb.toString()); - } - - public Location transform (Vector vector) { - return Location.create(this.x + (int) Math.floor(vector.getX()), this.y + (int) Math.floor(vector.getY())); - } -} diff --git a/Server/src/main/core/game/world/map/Location.kt b/Server/src/main/core/game/world/map/Location.kt new file mode 100644 index 000000000..a202e4a49 --- /dev/null +++ b/Server/src/main/core/game/world/map/Location.kt @@ -0,0 +1,257 @@ +package core.game.world.map + +import core.api.utils.Vector +import core.game.interaction.DestinationFlag +import core.game.node.Node +import core.game.world.map.RegionManager.getTeleportLocation +import core.game.world.map.path.Pathfinder +import core.tools.RandomFunction +import kotlin.math.* + +/** + * Specify a position (x,y,z), implements [Node]. + * - Coordinates x and y can be negative for delta values. Values ±16,383. + * - Coordinate z (level) has a quirk, this class doesn't let you init with a negative z. Values 0 - 3. + */ +/* +Official Terms + - Zones: 8x8 tiles + - Map Squares: 64x64 tiles + - Build Area: 104x104 tiles (13 x 13 chunks centered around player's chunk) - client's loaded area + +Some Definitions: + - Cardinal: Four Base Direction (N E S W) + - Diagonal: Four Diagonal directions (NE SE SW NW) + - Compass: All 8 directions (For this game, compass in relation to 8-point Cardinal+Diagonal) + - Euclidean distance: Straight-line length of two points. Direct calculation. e.g. [2,3] distance=√13(≈3.61) + - Manhattan distance: Cardinal length of two points. Count of steps along grid lines. e.g. [2,3] distance=5 + - Chebyshev distance: Cardinal+Diagonal length of two points. Count of king moves to make. e.g. [2,3] distance=3 + */ +class Location(var hash: LocationHash) : Node(null, null) { + @JvmOverloads + constructor(x: Int, y: Int, z: Int = 0) : this( + LocationHash(x, y, (z % 4 + 4) % 4) // The previous z calc didn't make sense since they play around with negative numbers. + ) + constructor(location: Location) : this(location.hash) + constructor(x: Int, y: Int, z: Int, randomizer: Int) : + this(x + RandomFunction.getRandom(randomizer), y + RandomFunction.getRandom(randomizer), z) + init { super.destinationFlag = DestinationFlag.LOCATION } // This is to set the type of [Node]. + + // Map properties directly to the underlying hash for convenience + var x: Int + get() = hash.x + set(value) { hash = LocationHash(value, y, z) } + + var y: Int + get() = hash.y + set(value) { hash = LocationHash(x, value, z) } + + var z: Int + get() = hash.z + set(value) { hash = LocationHash(x, y, (value % 4 + 4) % 4) } + + companion object { + // Alternate static constructors + @JvmStatic @JvmOverloads fun create(x: Int, y: Int, z: Int = 0): Location = Location(x, y, z) + @JvmStatic fun create(location: Location): Location = Location(location) + /** @return new Location parsed from a string format "[x, y, z]". */ + fun fromString(locString: String): Location { + val coordinates = locString.removeSurrounding("[", "]").split(",").map { it.trim().toInt() } + return create(coordinates[0], coordinates[1], coordinates[2]) + } + + /** @return Straight-line(Euclidean) distance between two locations. */ + @JvmStatic fun getDistance(first: Location, second: Location): Double { + val xDiff: Int = first.x - second.x + val yDiff: Int = first.y - second.y + return sqrt((xDiff * xDiff + yDiff * yDiff).toDouble()) + } + + /** @return A location representing the delta between two locations. This is not a good function because it doesn't care about z. */ + @JvmStatic fun getDelta(location: Location, other: Location): Location = create(other.x - location.x, other.y - location.y, other.z - location.z) + + /** @return A random location within radius, optionally verifying reachability. */ + fun getRandomLocation(main: Location, radius: Int, reachable: Boolean): Location { + val loc = getTeleportLocation(main, radius) + if (!reachable) return loc + val path = Pathfinder.find(main, loc, false, Pathfinder.DUMB) + if (!path.isSuccessful && path.points.isNotEmpty()) { + return create(path.points.last.x, path.points.last.y, main.z) + } else if (path.isSuccessful) { + return loc + } else { + return main + } + } + } + + // Function overrides of Node class and Java class + override fun getLocation(): Location = this + override fun toString(): String = "[$x, $y, $z]" + override fun hashCode(): Int = hash.hash + override fun equals(other: Any?): Boolean { + return when (other) { + is Location -> other.hash == this.hash + is LocationHash -> other == this.hash + else -> false + } + } + fun equals(x: Int, y: Int, z: Int): Boolean = hash == LocationHash(x, y, z) + + // Derived variables. Please note the following isn't a 1-1 representation of what is officially used. + + // Region - (Officially: Map Square) 64x64 + /** Unique index "key" (Officially: region_uid) for a 64x64 area. Made of x and y joined bitwise together. Used in xteas.json. */ + val regionId: Int get() = x shr 6 shl 8 or (y shr 6) // Cuts off the end 6 bits of both x y and joins them together. E.g. 10392 + /** The chunk x index for this position (x/8). For sets of 8x8 area. THIS IS NAMED WRONG, THIS IS A CHUNK(8) NOT REGION(64). */ + val regionX: Int get() = x shr 3 // shr 3 is essentially divide by 8 + /** The chunk y index for this position (y/8). For sets of 8x8 area. THIS IS NAMED WRONG, THIS IS A CHUNK(8) NOT REGION(64). */ + val regionY: Int get() = y shr 3 // shr 3 is essentially divide by 8 + // Local - x,y position inside a region + /** The relative x position inside a region(64x64). */ + val localX: Int get() = x and 63 // and 63 is essentially modulo 64 + /** The relative y position inside a region(64x64). */ + val localY: Int get() = y and 63 // and 63 is essentially modulo 64 + /** @return Check if it is in the same region id. */ + fun isInRegion(region: Int): Boolean = regionId == region + + // Chunk - (Officially: Zones) 8x8 + val chunkBase: Location get() = create(regionX shl 3, regionY shl 3, z) + /** The chunk x index for this position RELATIVE to a region[localX](x/8). THIS IS NOT GLOBAL CHUNK X. */ + val chunkX: Int get() = localX shr 3 // shr 3 is essentially divide by 8 (note this is localX) + /** The chunk y index for this position RELATIVE to a region[localY](y/8). THIS IS NOT GLOBAL CHUNK Y. */ + val chunkY: Int get() = localY shr 3 // shr 3 is essentially divide by 8 (note this is localX) + /** The relative x position inside a chunk(8x8). */ + val chunkOffsetX: Int get() = localX and 7 // and 7 is essentially modulo 8 + /** The relative y position inside a chunk(8x8). */ + val chunkOffsetY: Int get() = localY and 7 // and 7 is essentially modulo 8 + + // Scene - (Officially: Build Area) 104x104 - Made of 13x13 chunks(8x8) + /** The relative x position inside a scene(104x104). Used for client viewport rendering. */ + val sceneX: Int get() = x - (regionX - 6 shl 3) // Centers the current region at [7chunk,7chunk] + /** The relative y position inside a scene(104x104). Used for client viewport rendering. */ + val sceneY: Int get() = y - (regionY - 6 shl 3) // Centers the current region at [7chunk,7chunk] + /** @return The local scene x-coordinate relative to another location's regionX. */ + fun getSceneX(loc: Location): Int = x - (loc.regionX - 6 shl 3) + /** @return The local scene y-coordinate relative to another location's regionY. */ + fun getSceneY(loc: Location): Int = y - (loc.regionY - 6 shl 3) + + + + /** @return The location incremented by another location's coordinates. (POTENTIAL Z PROBLEMS HERE) */ + // TODO: THE FOLLOWING TRANSFORM HAS SHIT Z HANDLING. + fun transform(l: Location): Location = Location(x + l.x, y + l.y, (z + l.z) % 4) + /** @return The location incremented by specific x, y, z differences. */ + fun transform(diffX: Int, diffY: Int, z: Int): Location = Location(x + diffX, y + diffY, (this.z + z) % 4) + /** @return The location incremented by direction and steps. */ + @JvmOverloads fun transform(dir: Direction, steps: Int = 1): Location = Location(x + dir.stepX * steps, y + dir.stepY * steps, z) + /** Location transformed by a Vector. Limit usage to more complicated functionality. */ + fun transform(vector: Vector): Location = create(x + floor(vector.x).toInt(), y + floor(vector.y).toInt()) + + + /** @return True if this location is 1 tile N, W, S, or E of the node. THIS CAN BE REFACTORED OUT. */ + fun isNextTo(node: Node): Boolean { + val l = node.location + return if (l.y == y) abs(l.x - x) == 1 else if (l.x == x) abs(l.y - y) == 1 else false + } + + /** @return True if this other location is within dist of this location. */ + @JvmOverloads fun withinDistance(other: Location, dist: Int = MapDistance.RENDERING.distance): Boolean { + if (other.z != z) return false + return getDistance(this, other) <= dist + } + + /** @return True if within specified distance using max norm (Chebyshev) distance. */ + fun withinMaxnormDistance(other: Location, dist: Int): Boolean = if (other.z != z) false else max(abs(other.x - x), abs(other.y - y)) <= dist + + /** @return The straight-line(Euclidean) distance between this and another location. */ + fun getDistance(other: Location): Double = getDistance(this, other) + + /** @returns ArrayList of the 8 cardinal and diagonal tiles. Order is important and follows [Direction] indexing **/ + val surroundingTiles: ArrayList get() { + val locations = ArrayList() + locations.add(transform(-1, -1, 0)) + locations.add(transform(0, -1, 0)) + locations.add(transform(1, -1, 0)) + locations.add(transform(1, 0, 0)) + locations.add(transform(1, 1, 0)) + locations.add(transform(0, 1, 0)) + locations.add(transform(-1, 1, 0)) + locations.add(transform(-1, 0, 0)) + return locations + } + + /** @returns ArrayList of the 4 cardinal direction tiles. Order is important and follows [Direction] indexing **/ + val cardinalTiles: ArrayList get() { + val locations = ArrayList() + locations.add(transform(-1, 0, 0)) + locations.add(transform(0, -1, 0)) + locations.add(transform(1, 0, 0)) + locations.add(transform(0, 1, 0)) + return locations + } + + + /** + * Gets the directional components of a movement step. + * TODO: would prefer this in the other place. This doesn't exactly belong here since this isn't a common function for other instances of Location + * + * we can move diagonally but there's no melee attacking diagonally. its gotta be from N, W, S or E. + * if we are moving diagonally, we then need to check if we can attack from one of the valid directions. + * in other words we need to check the two tiles that share a corner with our destination and make sure they can be attacked from + * + * picture this: you are at (0,0). im at (1,1). you want to strangle my head off, so you got to check + * 1. to the west of destination: (1,1) + (-1,0) = (0,1) or north from where you started + * 2. to the south of the destination: (1,1) + (0,-1) = (1,0) or east from where you started + * this function is used only by CombatSwingHandler.kt and assumes that we return the X tiles, west or east, FIRST + * and then the Y tiles South and North. + */ + fun getStepComponents(dir: Direction): List { + val output = ArrayList(2) + if (dir.stepX != 0) output.add(transform(-dir.stepX, 0, 0)) + if (dir.stepY != 0) output.add(transform(0, -dir.stepY, 0)) + return output + } + + /** @return the Direction of the another location relative to this one. */ + fun deriveDirection(location: Location): Direction? { + val dx = (location.x - x).let { if (it >= 0) min(it, 1) else -1 } + val dy = (location.y - y).let { if (it >= 0) min(it, 1) else -1 } + val sb = StringBuilder() + if (dy != 0) sb.append(if (dy > 0) "NORTH" else "SOUTH") + if (dx != 0) { if (sb.isNotEmpty()) sb.append("_"); sb.append(if (dx > 0) "EAST" else "WEST") } + return if (sb.isEmpty()) null else Direction.valueOf(sb.toString()) + } + + /** + * Locations as an Integer rather than a Location object. This is for space efficiency. + * Inner class, because this is only used here, and to avoid adding another standard until this replaces Location + * + * 32 bits for an int (z z x x x x x x x x x x x x x x x y y y y y y y y y y y y y y y) + * - Rightmost set of 15 bits is for y (can handle negative numbers) + * - Middle set of 15 bits is for x (can handle negative numbers) + * - Leftmost set of 2 bits is for z + * + * Since x and y occupy 15 bits (0 to 32,767) it becomes => 1 sign + 14 bits (e.g., 16,384) for range of ±16,383. + */ + @JvmInline + value class LocationHash(val hash: Int) { + constructor(x: Int, y: Int, z: Int = 0) : this( + ((z and 0x3) shl 30) or + ((x and 0x7FFF) shl 15) or + (y and 0x7FFF) + ) + val x: Int get() { + val raw = (hash ushr 15) and 0x7FFF + // If the 15th bit is set, it's a negative number in 15-bit range + return if (raw >= 0x4000) raw - 0x8000 else raw + } + + val y: Int get() { + val raw = hash and 0x7FFF + // If the 15th bit is set, it's a negative number + return if (raw >= 0x4000) raw - 0x8000 else raw + } + val z: Int get() = hash ushr 30 + } +} diff --git a/Server/src/main/core/game/world/map/RegionManager.kt b/Server/src/main/core/game/world/map/RegionManager.kt index 46022fd01..9fb7b848b 100644 --- a/Server/src/main/core/game/world/map/RegionManager.kt +++ b/Server/src/main/core/game/world/map/RegionManager.kt @@ -783,10 +783,10 @@ object RegionManager { 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.x >= l.getX() - xdist && - player.location.x <= l.getX() + xdist && - player.location.y >= l.getY() - ydist && - player.location.y <= l.getY() + ydist) { + if (player.location.x >= l.x - xdist && + player.location.x <= l.x + xdist && + player.location.y >= l.y - ydist && + player.location.y <= l.y + ydist) { players.add(player) } } diff --git a/Server/src/test/kotlin/core/PathfinderTests.kt b/Server/src/test/kotlin/core/PathfinderTests.kt index f38eecb68..84f6c640c 100644 --- a/Server/src/test/kotlin/core/PathfinderTests.kt +++ b/Server/src/test/kotlin/core/PathfinderTests.kt @@ -235,7 +235,7 @@ class PathfinderTests { TestUtils.advanceTicks(5, false) Assertions.assertEquals(true, npc.getAttribute("return-to-spawn", false)) TestUtils.advanceTicks(50, false) - Assertions.assertEquals(true, npc.location.getDistance(ServerConstants.HOME_LOCATION) <= 9) + Assertions.assertEquals(true, npc.location.getDistance(ServerConstants.HOME_LOCATION!!) <= 9) } } @@ -254,7 +254,7 @@ class PathfinderTests { RegionManager.forId(npc.location.regionId).flagInactive(true) TestUtils.advanceTicks(50, false) Assertions.assertEquals(false, npc.getAttribute("return-to-spawn", false)) - Assertions.assertEquals(true, npc.location.getDistance(ServerConstants.HOME_LOCATION) <= 5) + Assertions.assertEquals(true, npc.location.getDistance(ServerConstants.HOME_LOCATION!!) <= 5) } } } \ No newline at end of file