-
Notifications
You must be signed in to change notification settings - Fork 1
Home
Welcome to the rsmod-pathfinder wiki!
The CollisionFlagMap
is responsible for the collection of your collision data of the game world.
Collision data is allocated on a per Zone
basis.
The intended use of the CollisionFlagMap
is to store all of the game world collision data when you are loading your application during startup.
You MUST allocate all zones within the game world that will be used by players. If you do not, then some areas that are not allocated will be blocked by the pathfinder.
You should create a class or classes that are allocated one time for the entire lifespan of the application.
class CollisionManager {
readonly flags: CollisionFlagMap;
readonly stepValidator: StepValidator;
readonly pathFinder: PathFinder;
readonly naivePathFinder: NaivePathFinder;
readonly lineValidator: LineValidator;
constructor() {
this.flags = new CollisionFlagMap();
this.stepValidator = new StepValidator(this.flags);
this.pathFinder = new PathFinder(this.flags);
this.naivePathFinder = new NaivePathFinder(this.stepValidator);
this.lineValidator = new LineValidator(this.flags);
}
}
When decoding a specific mapsquare:
for (let level = 0; level < 4; level++) {
for (let x = 0; x < 64; x++) {
const absoluteX = x + mapsquareX;
for (let z = 0; z < 64; z++) {
const absoluteZ = z + mapsquareZ;
// Check if to allocate the zone at this mapsquare tile.
this.flags.allocateIfAbsent(absoluteX, absoluteZ, level);
// The rest of the map collision loading.
}
}
}
This is a simple example snippet of adding and removing collision flags:
class LocCollider {
private readonly flags: CollisionFlagMap;
constructor(flags: CollisionFlagMap) {
this.flags = flags;
}
change = (x: number, z: number, level: number, width: number, length: number, blockrange: boolean, add: boolean): void => {
let mask = CollisionFlag.LOC;
if (blockrange) {
mask |= CollisionFlag.LOC_PROJ_BLOCKER;
}
for (let index = 0; index < width * length; index++) {
const deltaX = x + (index % width);
const deltaZ = z + (index / width);
if (add) {
this.flags.add(deltaX, deltaZ, level, mask);
} else {
this.flags.remove(deltaX, deltaZ, level, mask);
}
}
};
}
const coords: RouteCoordinates[] = pathFinder.findPath(this.level, this.x, this.z, pathfindX, pathfindZ).waypoints
if (this.target instanceof PathingEntity) { // if target clicked is a npc or player
const coords: RouteCoordinates[] = pathfinder.findPath(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.target.width, this.target.length, this.target.orientation, -2).waypoints;
} else if (this.target instanceof Loc) { // if target clicked is a loc
const forceapproach = LocType.get(this.target.type).forceapproach; // loc opcode 69 from the cache
const coords: RouteCoordinates[] = pathfinder.findPath(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.target.width, this.target.length, this.target.angle, this.target.shape, true, forceapproach).waypoints;
} else { // if target clicked is an obj
const coords: RouteCoordinates[] = pathfinder.findPath(this.level, this.x, this.z, this.target.x, this.target.z).waypoints;
}
if (!target || target.level !== this.level) {
return false;
}
if (target instanceof PathingEntity) { // if target clicked is a npc or player
return ReachStrategy.reached(collisionFlags, this.level, this.x, this.z, target.x, target.z, target.width, target.length, this.width, target.orientation, -2);
} else if (target instanceof Loc) { // if target clicked is a loc
const forceapproach = LocType.get(target.type).forceapproach; // loc opcode 69 from the cache
return ReachStrategy.reached(collisionFlags, this.level, this.x, this.z, target.x, target.z, target.width, target.length, this.width, target.angle, target.shape, forceapproach);
}
// if target clicked is an obj
const shape = collisionFlags.isFlagged(target.x, target.z, target.level, CollisionFlag.WALK_BLOCKED) ? -2 : -1;
const reached = ReachStrategy.reached(collisionFlags, this.level, this.x, this.z, target.x, target.z, target.width, target.length, this.width, 0, shape);
if (!target || target.level !== this.level) {
return false;
}
if (target instanceof PathingEntity && naivePathFinder.intersects(this.x, this.z, this.width, this.length, target.x, target.z, target.width, target.length)) { // if target clicked is a npc or player && naivePathFinder.intersects
// pathing entity has a -2 shape basically (not allow on same tile) for ap.
// you are not within ap distance of pathing entity if you are underneath it.
return false;
}
const reached = lineValidator.hasLineOfSight(this.level, this.x, this.z, target.x, target.z, this.width, target.width, target.length, CollisionFlag.PLAYER) && Position.distanceTo(this, target) <= range;
For LOS, you must calculate the the two closest possible tiles between two entities which can be dynamic depending on the size of the two entities.
distanceTo = (pos: {x: number; z: number; width: number; length: number}, other: {x: number; z: number; width: number; length: number}): number => {
const p1: {x: number; z: number} = this.closest(pos, other);
const p2: {x: number; z: number} = this.closest(other, pos);
return Math.max(Math.abs(p1.x - p2.x), Math.abs(p1.z - p2.z));
};
closest = (
pos: {x: number; z: number; width: number; length: number},
other: {x: number; z: number; width: number; length: number}
): {
x: number;
z: number;
} => {
const occupiedX: number = pos.x + pos.width - 1;
const occupiedZ: number = pos.z + pos.length - 1;
return {
x: other.x <= pos.x ? pos.x : other.x >= occupiedX ? occupiedX : other.x,
z: other.z <= pos.z ? pos.z : other.z >= occupiedZ ? occupiedZ : other.z
};
};