Skip to content
Jordan edited this page Jan 21, 2024 · 2 revisions

Welcome to the rsmod-pathfinder wiki!

Collision

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

Pathfinder

To find a path to a clicked tile in the game world example:

const coords: RouteCoordinates[] = pathFinder.findPath(this.level, this.x, this.z, pathfindX, pathfindZ).waypoints

To find a path to a Npc/Loc/Player/Obj in the game world example:

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

Reach

inOperableDistance

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

inApproachDistance

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;

Positioning

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
    };
};
Clone this wiki locally