Skip to content

Instantly share code, notes, and snippets.

@ultraviolet-jordan
Created April 2, 2024 04:48
Show Gist options
  • Save ultraviolet-jordan/b0a0b0ff5be8a0cc80cd12b266a43372 to your computer and use it in GitHub Desktop.
Save ultraviolet-jordan/b0a0b0ff5be8a0cc80cd12b266a43372 to your computer and use it in GitHub Desktop.
import fs from 'fs';
import Packet from '#jagex2/io/Packet.js';
import ZoneManager from '#lostcity/engine/zone/ZoneManager.js';
import FloorCollider from '#lostcity/engine/collision/FloorCollider.js';
import WallCollider from '#lostcity/engine/collision/WallCollider.js';
import LocCollider from '#lostcity/engine/collision/LocCollider.js';
import LocAngle from '#lostcity/engine/collision/LocAngle.js';
import LocLayer from '#lostcity/engine/collision/LocLayer.js';
import NpcCollider from '#lostcity/engine/collision/NpcCollider.js';
import { LocShapes } from '#lostcity/engine/collision/LocShape.js';
import RoofCollider from '#lostcity/engine/collision/RoofCollider.js';
import PlayerCollider from '#lostcity/engine/collision/PlayerCollider.js';
// all of this above needs to be refactored into an export ^ for one line imports.
import LocType from '#lostcity/cache/LocType.js';
import Loc from '#lostcity/entity/Loc.js';
import { CollisionFlagMap, LineValidator, NaivePathFinder, PathFinder, StepValidator } from '@2004scape/rsmod-pathfinder';
export default class CollisionManager {
private readonly floorCollider: FloorCollider;
private readonly wallCollider: WallCollider;
private readonly locCollider: LocCollider;
private readonly npcCollider: NpcCollider;
private readonly roofCollider: RoofCollider;
private readonly playerCollider: PlayerCollider;
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.floorCollider = new FloorCollider(this.flags);
this.wallCollider = new WallCollider(this.flags);
this.locCollider = new LocCollider(this.flags);
this.npcCollider = new NpcCollider(this.flags);
this.roofCollider = new RoofCollider(this.flags);
this.playerCollider = new PlayerCollider(this.flags);
this.pathFinder = new PathFinder(this.flags);
this.naivePathFinder = new NaivePathFinder(this.stepValidator);
this.lineValidator = new LineValidator(this.flags);
}
init(zoneManager: ZoneManager) {
console.time('Loading collision');
const maps: string[] = fs.readdirSync('data/pack/server/maps').filter((x: string): boolean => x[0] === 'm');
for (let index: number = 0; index < maps.length; index++) {
const [mx, mz] = maps[index].substring(1).split('_').map((x: string) => parseInt(x));
const mapsquareX: number = mx << 6;
const mapsquareZ: number = mz << 6;
const lands: Int8Array = new Int8Array(4 * 64 * 64); // 4 * 64 * 64 size is guaranteed for lands
this.decodeLands(lands, Packet.load(`data/pack/server/maps/m${mx}_${mz}`), mapsquareX, mapsquareZ);
this.decodeLocs(zoneManager, lands, Packet.load(`data/pack/server/maps/l${mx}_${mz}`), mapsquareX, mapsquareZ);
}
console.timeEnd('Loading collision');
}
/**
* Change collision at a specified Position for lands/floors.
* @param x The x pos.
* @param z The z pos.
* @param level The level pos.
* @param add True if adding this collision. False if removing.
*/
changeLandCollision(x: number, z: number, level: number, add: boolean): void {
this.floorCollider.change(x, z, level, add);
}
/**
* Change collision at a specified Position for locs.
* @param shape The shape of the loc to change.
* @param angle The angle of the loc to change.
* @param blockrange If this loc blocks range.
* @param length The length of this loc.
* @param width The width of this loc.
* @param active If this loc is active.
* @param x The x pos.
* @param z The z pos.
* @param level The level pos.
* @param add True if adding this collision. False if removing.
*/
changeLocCollision(shape: number, angle: number, blockrange: boolean, length: number, width: number, active: number, x: number, z: number, level: number, add: boolean): void {
const locLayer: LocLayer = LocShapes.layer(shape);
if (locLayer === LocLayer.WALL) {
this.wallCollider.change(x, z, level, angle, shape, blockrange, add);
} else if (locLayer === LocLayer.GROUND) {
if (angle === LocAngle.NORTH || angle === LocAngle.SOUTH) {
this.locCollider.change(x, z, level, length, width, blockrange, add);
} else {
this.locCollider.change(x, z, level, width, length, blockrange, add);
}
} else if (locLayer === LocLayer.GROUND_DECOR) {
if (active === 1) {
this.floorCollider.change(x, z, level, add);
}
}
}
/**
* Change collision at a specified Position for npcs.
* @param size The size square of this npc. (1x1, 2x2, etc).
* @param x The x pos.
* @param z The z pos.
* @param level The level pos.
* @param add True if adding this collision. False if removing.
*/
changeNpcCollision(size: number, x: number, z: number, level: number, add: boolean): void {
this.npcCollider.change(x, z, level, size, add);
}
/**
* Change collision at a specified Position for players.
* @param size The size square of this npc. (1x1, 2x2, etc).
* @param x The x pos.
* @param z The z pos.
* @param level The level pos.
* @param add True if adding this collision. False if removing.
*/
changePlayerCollision(size: number, x: number, z: number, level: number, add: boolean): void {
this.playerCollider.change(x, z, level, size, add);
}
/**
* Change collision at a specified Position for roofs.
* @param x The x pos.
* @param z The z pos.
* @param level The level pos.
* @param add True if adding this collision. False if removing.
*/
changeRoofCollision(x: number, z: number, level: number, add: boolean): void {
this.roofCollider.change(x, z, level, add);
}
private decodeLands(lands: Int8Array, packet: Packet, mapsquareX: number, mapsquareZ: number): void {
for (let level: number = 0; level < 4; level++) {
for (let x: number = 0; x < 64; x++) {
const absoluteX: number = x + mapsquareX;
for (let z: number = 0; z < 64; z++) {
const absoluteZ: number = z + mapsquareZ;
if (x % 7 === 0 && z % 7 === 0) { // allocate per zone
this.flags.allocateIfAbsent(absoluteX, absoluteZ, level);
}
const coord: number = this.packCoord(x, z, level);
const land: number = lands[coord] = this.decodeLand(packet);
if ((land & 0x4) !== 0) {
this.changeRoofCollision(absoluteX, absoluteZ, level, true);
}
const bridged: boolean = (level === 1 ? land & 0x2 : lands[this.packCoord(x, z, 1)] & 0x2) === 2;
if (level === 1 && bridged) {
this.changeLandCollision(absoluteX, absoluteZ, 0, false);
}
if ((land & 0x1) !== 1) {
continue;
}
this.changeLandCollision(absoluteX, absoluteZ, bridged ? level - 1 : level, true);
}
}
}
}
private decodeLand(packet: Packet, collision: number = 0): number {
const opcode: number = packet.g1();
if (opcode === 0 || opcode === 1) {
if (opcode === 1) {
packet.g1();
}
return collision;
}
if (opcode >= 2 && opcode <= 49) {
packet.g1s();
}
return this.decodeLand(packet, opcode >= 50 && opcode <= 81 ? opcode - 49 : collision);
}
private decodeLocs(zoneManager: ZoneManager, lands: Int8Array, packet: Packet, mapsquareX: number, mapsquareZ: number): void {
let locId: number = -1;
let locIdOffset: number = packet.gsmart();
while (locIdOffset !== 0) {
locId += locIdOffset;
let coord: number = 0;
let coordOffset: number = packet.gsmart();
while (coordOffset !== 0) {
const {x, z, level} = this.unpackCoord(coord += coordOffset - 1);
const bridged: boolean = (level === 1 ? lands[coord] & 0x2 : lands[this.packCoord(x, z, 1)] & 0x2) === 2;
const blevel: number = bridged ? level - 1 : level;
if (blevel < 0) {
continue;
}
const type: LocType = LocType.get(locId);
const width: number = type.width;
const length: number = type.length;
const info: number = packet.g1();
const shape: number = info >> 2;
const angle: number = info & 0x3;
const absoluteX: number = x + mapsquareX;
const absoluteZ: number = z + mapsquareZ;
zoneManager.getZone(absoluteX, absoluteZ, blevel).addStaticLoc(new Loc(blevel, absoluteX, absoluteZ, width, length, locId, shape, angle));
if (type.blockwalk) {
this.changeLocCollision(shape, angle, type.blockrange, length, width, type.active, absoluteX, absoluteZ, blevel, true);
}
coordOffset = packet.gsmart();
}
locIdOffset = packet.gsmart();
}
}
private packCoord(x: number, z: number, level: number): number {
return (z & 0x3f) | ((x & 0x3f) << 6) | ((level & 0x3) << 12);
}
private unpackCoord(packed: number): { level: number; x: number; z: number } {
return {
level: packed >> 12 & 0x3,
x: packed >> 6 & 0x3f,
z: packed & 0x3f,
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment