Modernised some location/pathing architecture

This commit is contained in:
Oven Bread 2026-07-06 13:08:28 +00:00 committed by Ryan
parent afeffc1b98
commit 5822b8df99
12 changed files with 275 additions and 596 deletions

View file

@ -193,7 +193,7 @@ enum class EnchantedJewellery(
),
RING_OF_LIFE(arrayOf<String>(),
arrayOf(
Location.create(ServerConstants.HOME_LOCATION)
Location.create(ServerConstants.HOME_LOCATION!!)
),
true,
Items.RING_OF_LIFE_2570

View file

@ -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)

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}
}

View file

@ -31,10 +31,10 @@ class DraynorManorHouseZone : MapZone("Draynor Manor House", true), Plugin<Any?>
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
}
}

View file

@ -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 {

View file

@ -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

View file

@ -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<Location>
*/
public ArrayList<Location> getSurroundingTiles() {
ArrayList<Location> 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<Location> getCardinalTiles() {
ArrayList<Location> 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<Location>
*/
public ArrayList<Location> get3x3Tiles() {
ArrayList<Location> 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<Location> getStepComponents(Direction dir) {
List<Location> 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()));
}
}

View file

@ -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<Location> get() {
val locations = ArrayList<Location>()
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<Location> get() {
val locations = ArrayList<Location>()
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<Location> {
val output = ArrayList<Location>(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
}
}

View file

@ -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)
}
}

View file

@ -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)
}
}
}