diff --git a/src/lostcity/engine/World.ts b/src/lostcity/engine/World.ts index b75cdbf12..7b57c574e 100644 --- a/src/lostcity/engine/World.ts +++ b/src/lostcity/engine/World.ts @@ -56,6 +56,7 @@ import { PlayerTimerType } from '#lostcity/entity/EntityTimer.js'; import { Position } from '#lostcity/entity/Position.js'; import ZoneManager from './zone/ZoneManager.js'; import { getLatestModified, getModified } from '#lostcity/util/PackFile.js'; +import { ZoneEvent } from './zone/Zone.js'; class World { id = Environment.WORLD_ID as number; @@ -82,6 +83,9 @@ class World { npcs: (Npc | null)[] = new Array(8192); + trackedZones: number[] = []; + zoneBuffers: Map = new Map(); + futureUpdates: Map = new Map(); queue: { script: ScriptState; delay: number; @@ -695,31 +699,72 @@ class World { // - loc/obj despawn/respawn // - compute shared buffer let zoneProcessing = Date.now(); - const activeZones: Set = new Set(); - for (let i = 0; i < this.players.length; i++) { - const player = this.players[i]; - if (!player) { - continue; - } + const future = this.futureUpdates.get(this.currentTick); + if (future) { + // despawn dynamic + for (let i = 0; i < future.length; i++) { + const zoneIndex = future[i]; + const zone = this.getZoneIndex(zoneIndex); + + for (let i = 0; i < zone.locs.length; i++) { + const loc = zone.locs[i]; + if (!loc || loc.despawn === -1) { + continue; + } - const centerX = Position.zone(player.x); - const centerZ = Position.zone(player.z); + if (loc.despawn === this.currentTick) { + this.removeLoc(loc, -1); + i--; + } + } - for (let x = centerX - 3; x <= centerX + 3; x++) { - for (let z = centerZ - 3; z <= centerZ + 3; z++) { - activeZones.add(ZoneManager.zoneIndex(x << 3, z << 3, player.level)); + for (let i = 0; i < zone.objs.length; i++) { + const obj = zone.objs[i]; + if (!obj || obj.despawn === -1) { + continue; + } + + if (obj.despawn === this.currentTick) { + this.removeObj(obj, null); + i--; + } } } - } - for (const zoneIndex of activeZones) { - const zone = this.getZoneIndex(zoneIndex); - if (!zone) { - continue; + // respawn static + for (let i = 0; i < future.length; i++) { + const zoneIndex = future[i]; + const zone = this.getZoneIndex(zoneIndex); + + for (let i = 0; i < zone.staticLocs.length; i++) { + const loc = zone.staticLocs[i]; + if (!loc || loc.respawn === -1) { + continue; + } + + if (loc.respawn === this.currentTick) { + loc.respawn = -1; + this.addLoc(loc, -1); + } + } + + for (let i = 0; i < zone.staticObjs.length; i++) { + const obj = zone.staticObjs[i]; + if (!obj || obj.respawn === -1) { + continue; + } + + if (obj.respawn === this.currentTick) { + obj.respawn = -1; + this.addObj(obj, null, -1); + } + } } - zone.cycle(); + this.futureUpdates.delete(this.currentTick); } + + this.computeSharedEvents(); zoneProcessing = Date.now() - zoneProcessing; // client output @@ -945,7 +990,70 @@ class World { } getZoneIndex(zoneIndex: number) { - return this.gameMap.zoneManager.zones.get(zoneIndex); + return this.gameMap.zoneManager.zones.get(zoneIndex)!; + } + + computeSharedEvents() { + this.trackedZones = []; + this.zoneBuffers = new Map(); + + for (let i = 0; i < this.players.length; i++) { + const player = this.players[i]; + if (!player) { + continue; + } + + // TODO: optimize this + const zones = Object.keys(player.loadedZones); + for (let j = 0; j < zones.length; j++) { + const zoneIndex = parseInt(zones[j]); + if (!this.trackedZones.includes(zoneIndex)) { + this.trackedZones.push(zoneIndex); + } + } + } + + for (let i = 0; i < this.trackedZones.length; i++) { + const zoneIndex = this.trackedZones[i]; + const zone = this.getZoneIndex(zoneIndex); + + const updates = zone.updates; + if (!updates.length) { + continue; + } + + zone.updates = updates.filter((event: ZoneEvent): boolean => { + // filter transient updates + if ((event.type === ServerProt.LOC_MERGE || event.type === ServerProt.LOC_ANIM || event.type === ServerProt.MAP_ANIM || event.type === ServerProt.MAP_PROJANIM) && event.tick < this.currentTick) { + return false; + } + + return true; + }); + } + } + + getSharedEvents(zoneIndex: number): Packet | undefined { + return this.zoneBuffers.get(zoneIndex); + } + + getUpdates(zoneIndex: number) { + return this.gameMap.zoneManager.zones.get(zoneIndex)!.updates; + } + + getReceiverUpdates(zoneIndex: number, receiverId: number) { + const updates = this.getUpdates(zoneIndex); + return updates.filter((event: ZoneEvent): boolean => { + if (event.type !== ServerProt.OBJ_ADD && event.type !== ServerProt.OBJ_DEL && event.type !== ServerProt.OBJ_COUNT && event.type !== ServerProt.OBJ_REVEAL) { + return false; + } + + // if (event.type === ServerProt.OBJ_DEL && receiverId !== -1 && event.receiverId !== receiverId) { + // return false; + // } + + return true; + }); } getZonePlayers(x: number, z: number, level: number) { @@ -956,23 +1064,6 @@ class World { return this.getZone(x, z, level).npcs; } - // todo: rewrite, this is for legacy code compatibility - getZoneLocs(x: number, z: number, level: number): Loc[] { - const zone = this.getZone(x, z, level); - const locs = []; - for (let dx = 0; dx < 8; dx++) { - for (let dz = 0; dz < 8; dz++) { - for (let layer = 0; layer < 4; layer++) { - const loc = zone.getLoc(x + dx, z + dz, layer); - if (loc) { - locs.push(loc); - } - } - } - } - return locs; - } - addNpc(npc: Npc) { this.npcs[npc.nid] = npc; npc.x = npc.startX; @@ -980,24 +1071,50 @@ class World { const zone = this.getZone(npc.x, npc.z, npc.level); zone.enter(npc); + + switch (npc.blockWalk) { + case BlockWalk.NPC: + this.collisionManager.changeNpcCollision(npc.width, npc.x, npc.z, npc.level, true); + break; + case BlockWalk.ALL: + this.collisionManager.changeNpcCollision(npc.width, npc.x, npc.z, npc.level, true); + this.collisionManager.changePlayerCollision(npc.width, npc.x, npc.z, npc.level, true); + break; + } + + npc.resetEntity(true); + npc.playAnimation(-1, 0); } removeNpc(npc: Npc) { const zone = this.getZone(npc.x, npc.z, npc.level); zone.leave(npc); - } - getLoc(x: number, z: number, level: number, locId: number): Loc | null { - const zone = this.getZone(x, z, level); + switch (npc.blockWalk) { + case BlockWalk.NPC: + this.collisionManager.changeNpcCollision(npc.width, npc.x, npc.z, npc.level, false); + break; + case BlockWalk.ALL: + this.collisionManager.changeNpcCollision(npc.width, npc.x, npc.z, npc.level, false); + this.collisionManager.changePlayerCollision(npc.width, npc.x, npc.z, npc.level, false); + break; + } - for (let layer = 0; layer < 4; layer++) { - const loc = zone.getLoc(x, z, layer); - if (loc && loc.type === locId) { - return loc; - } + if (!npc.static) { + this.npcs[npc.nid] = null; + } else { + const type = NpcType.get(npc.type); + npc.despawn = this.currentTick; + npc.respawn = this.currentTick + type.respawnrate; } + } - return null; + getLoc(x: number, z: number, level: number, locId: number) { + return this.getZone(x, z, level).getLoc(x, z, locId); + } + + getZoneLocs(x: number, z: number, level: number) { + return [...this.getZone(x, z, level).staticLocs.filter(l => l.respawn < this.currentTick), ...this.getZone(x, z, level).locs]; } getObj(x: number, z: number, level: number, objId: number) { @@ -1006,27 +1123,106 @@ class World { addLoc(loc: Loc, duration: number) { const zone = this.getZone(loc.x, loc.z, loc.level); - zone.addLoc(loc.x, loc.z, loc.type, loc.shape, loc.angle, duration); - } + zone.addLoc(loc, duration); - changeLoc(loc: Loc, duration: number) { - const zone = this.getZone(loc.x, loc.z, loc.level); - zone.changeLoc(loc.x, loc.z, loc.type, loc.shape, loc.angle, duration); + const type = LocType.get(loc.type); + if (type.blockwalk) { + this.collisionManager.changeLocCollision(loc.shape, loc.angle, type.blockrange, type.length, type.width, type.active, loc.x, loc.z, loc.level, true); + } + + loc.despawn = this.currentTick + duration; + loc.respawn = -1; + if (duration !== -1) { + const endTick = this.currentTick + duration; + let future = this.futureUpdates.get(endTick); + if (!future) { + future = []; + } + + if (!future.includes(zone.index)) { + future.push(zone.index); + } + + this.futureUpdates.set(endTick, future); + } } removeLoc(loc: Loc, duration: number) { const zone = this.getZone(loc.x, loc.z, loc.level); - zone.removeLoc(loc.x, loc.z, loc.shape, duration); + zone.removeLoc(loc, duration); + + const type = LocType.get(loc.type); + if (type.blockwalk) { + this.collisionManager.changeLocCollision(loc.shape, loc.angle, type.blockrange, type.length, type.width, type.active, loc.x, loc.z, loc.level, false); + } + + loc.despawn = -1; + loc.respawn = this.currentTick + duration; + if (duration !== -1) { + const endTick = this.currentTick + duration; + let future = this.futureUpdates.get(endTick); + if (!future) { + future = []; + } + + if (!future.includes(zone.index)) { + future.push(zone.index); + } + + this.futureUpdates.set(endTick, future); + } } addObj(obj: Obj, receiver: Player | null, duration: number) { const zone = this.getZone(obj.x, obj.z, obj.level); + const existing = this.getObj(obj.x, obj.z, obj.level, obj.id); + if (existing && existing.id == obj.id) { + const type = ObjType.get(obj.type); + const nextCount = obj.count + existing.count; + if (type.stackable && nextCount <= Inventory.STACK_LIMIT) { + // if an obj of the same type exists and is stackable, then we merge them. + obj.count = nextCount; + zone.removeObj(existing, receiver); + } + } zone.addObj(obj, receiver, duration); + + obj.despawn = this.currentTick + duration; + obj.respawn = -1; + if (duration !== -1) { + const endTick = this.currentTick + duration; + let future = this.futureUpdates.get(endTick); + if (!future) { + future = []; + } + + if (!future.includes(zone.index)) { + future.push(zone.index); + } + + this.futureUpdates.set(endTick, future); + } } removeObj(obj: Obj, receiver: Player | null) { + // TODO + // stackable objs when they overflow are created into another slot on the floor + // currently when you pickup from a tile with multiple stackable objs + // you will pickup one of them and the other one disappears const zone = this.getZone(obj.x, obj.z, obj.level); - zone.removeObj(obj, receiver); + zone.removeObj(obj, receiver, -1); + obj.despawn = this.currentTick; + obj.respawn = this.currentTick + ObjType.get(obj.type).respawnrate; + if (zone.staticObjs.includes(obj)) { + let future = this.futureUpdates.get(obj.respawn); + if (!future) { + future = []; + } + if (!future.includes(zone.index)) { + future.push(zone.index); + } + this.futureUpdates.set(obj.respawn, future); + } } // ---- diff --git a/src/lostcity/engine/collision/CollisionManager.ts b/src/lostcity/engine/collision/CollisionManager.ts index 81cc66d09..1a62d3c80 100644 --- a/src/lostcity/engine/collision/CollisionManager.ts +++ b/src/lostcity/engine/collision/CollisionManager.ts @@ -205,7 +205,16 @@ export default class CollisionManager { continue; } - zoneManager.getZone(absoluteX, absoluteZ, adjustedLevel).addStaticLoc(absoluteX, absoluteZ, id, shape, angle); + const type = LocType.get(id); + const width = type.width; + const length = type.length; + + const loc = new Loc(adjustedLevel, absoluteX, absoluteZ, width, length, id, shape, angle); + zoneManager.getZone(absoluteX, absoluteZ, adjustedLevel).addStaticLoc(loc); + + if (type.blockwalk) { + this.changeLocCollision(shape, angle, type.blockrange, length, width, type.active, absoluteX, absoluteZ, adjustedLevel, true); + } } } diff --git a/src/lostcity/engine/script/handlers/LocOps.ts b/src/lostcity/engine/script/handlers/LocOps.ts index d934b25cc..efd741454 100644 --- a/src/lostcity/engine/script/handlers/LocOps.ts +++ b/src/lostcity/engine/script/handlers/LocOps.ts @@ -58,7 +58,7 @@ const LocOps: CommandHandlers = { const seq = state.popInt(); const loc = state.activeLoc; - World.getZone(loc.x, loc.z, loc.level).locanim(loc, seq); + World.getZone(loc.x, loc.z, loc.level).animLoc(loc, seq); }), [ScriptOpcode.LOC_CATEGORY]: checkedHandler(ActiveLoc, state => { @@ -73,10 +73,12 @@ const LocOps: CommandHandlers = { throw new Error(`attempted to use duration that was out of range: ${duration}. Duration should be greater than zero.`); } - const type = LocType.get(id); - const loc = new Loc(state.activeLoc.level, state.activeLoc.x, state.activeLoc.z, type.width, type.length, id, state.activeLoc.shape, state.activeLoc.angle); + World.removeLoc(state.activeLoc, duration); + + const locType = LocType.get(id); + const loc = new Loc(state.activeLoc.level, state.activeLoc.x, state.activeLoc.z, locType.width, locType.length, id, state.activeLoc.shape, state.activeLoc.angle); + World.addLoc(loc, duration); - World.changeLoc(loc, duration); state.activeLoc = loc; state.pointerAdd(ActiveLoc[state.intOperand]); }), @@ -105,7 +107,7 @@ const LocOps: CommandHandlers = { const pos = Position.unpackCoord(coord); const loc = World.getLoc(pos.x, pos.z, pos.level, locId); - if (!loc) { + if (!loc || loc.respawn !== -1) { state.pushInt(0); return; } diff --git a/src/lostcity/engine/script/handlers/PlayerOps.ts b/src/lostcity/engine/script/handlers/PlayerOps.ts index 2a58b6646..633cdb6bb 100644 --- a/src/lostcity/engine/script/handlers/PlayerOps.ts +++ b/src/lostcity/engine/script/handlers/PlayerOps.ts @@ -724,7 +724,7 @@ const PlayerOps: CommandHandlers = { const north = nw.z; const loc = state.activeLoc; - World.getZone(loc.x, loc.z, loc.level).locmerge(loc, state.activePlayer, startCycle, endCycle, south, east, north, west); + World.getZone(loc.x, loc.z, loc.level).mergeLoc(loc, state.activePlayer, startCycle, endCycle, south, east, north, west); }), [ScriptOpcode.LAST_LOGIN_INFO]: state => { diff --git a/src/lostcity/engine/script/handlers/ServerOps.ts b/src/lostcity/engine/script/handlers/ServerOps.ts index c7bf59182..ca98954f8 100644 --- a/src/lostcity/engine/script/handlers/ServerOps.ts +++ b/src/lostcity/engine/script/handlers/ServerOps.ts @@ -103,7 +103,7 @@ const ServerOps: CommandHandlers = { const z = pos.z; const level = pos.level; - World.getZone(x, z, level).anim(x, z, spotanim, height, delay); + World.getZone(x, z, level).animMap(x, z, spotanim, height, delay); }, [ScriptOpcode.DISTANCE]: state => { @@ -275,7 +275,7 @@ const ServerOps: CommandHandlers = { const srcPos = Position.unpackCoord(srcCoord); const zone = World.getZone(srcPos.x, srcPos.z, srcPos.level); - zone.projanim(srcPos.x, srcPos.z, player.x, player.z, -player.pid - 1, spotanim, srcHeight + 100, dstHeight + 100, delay, duration, peak, arc); + zone.mapProjAnim(srcPos.x, srcPos.z, player.x, player.z, -player.pid - 1, spotanim, srcHeight + 100, dstHeight + 100, delay, duration, peak, arc); }, [ScriptOpcode.PROJANIM_NPC]: state => { @@ -299,7 +299,7 @@ const ServerOps: CommandHandlers = { const srcPos = Position.unpackCoord(srcCoord); const zone = World.getZone(srcPos.x, srcPos.z, srcPos.level); - zone.projanim(srcPos.x, srcPos.z, npc.x, npc.z, npc.nid + 1, spotanim, srcHeight + 100, dstHeight + 100, delay, duration, peak, arc); + zone.mapProjAnim(srcPos.x, srcPos.z, npc.x, npc.z, npc.nid + 1, spotanim, srcHeight + 100, dstHeight + 100, delay, duration, peak, arc); }, [ScriptOpcode.PROJANIM_MAP]: state => { @@ -318,7 +318,7 @@ const ServerOps: CommandHandlers = { const srcPos = Position.unpackCoord(srcCoord); const dstPos = Position.unpackCoord(dstCoord); const zone = World.getZone(srcPos.x, srcPos.z, srcPos.level); - zone.projanim(srcPos.x, srcPos.z, dstPos.x, dstPos.z, 0, spotanim, srcHeight + 100, dstHeight, delay, duration, peak, arc); + zone.mapProjAnim(srcPos.x, srcPos.z, dstPos.x, dstPos.z, 0, spotanim, srcHeight + 100, dstHeight, delay, duration, peak, arc); }, [ScriptOpcode.MAP_LOCADDUNSAFE]: state => { @@ -328,50 +328,48 @@ const ServerOps: CommandHandlers = { throw new Error(`attempted to use coord that was out of range: ${coord}. Range should be: 0 to ${Position.max}`); } - // const pos = Position.unpackCoord(coord); - - // todo: rewrite map_locaddunsafe for the new loc collection - // const zone = World.getZone(pos.x, pos.z, pos.level); - // const locs = zone.staticLocs.concat(zone.locs); - - // for (let index = 0; index < locs.length; index++) { - // const loc = locs[index]; - // const type = LocType.get(loc.type); - - // if (type.active !== 1) { - // continue; - // } - - // const layer = LocShapes.layer(loc.shape); - - // if (loc.respawn !== -1 && layer === LocLayer.WALL) { - // continue; - // } - - // if (layer === LocLayer.WALL) { - // if (loc.x === pos.x && loc.z === pos.z) { - // state.pushInt(1); - // return; - // } - // } else if (layer === LocLayer.GROUND) { - // const width = loc.angle === LocAngle.NORTH || loc.angle === LocAngle.SOUTH ? loc.length : loc.width; - // const length = loc.angle === LocAngle.NORTH || loc.angle === LocAngle.SOUTH ? loc.width : loc.length; - // for (let index = 0; index < width * length; index++) { - // const deltaX = loc.x + (index % width); - // const deltaZ = loc.z + index / width; - // if (deltaX === pos.x && deltaZ === pos.z) { - // state.pushInt(1); - // return; - // } - // } - // } else if (layer === LocLayer.GROUND_DECOR) { - // if (loc.x === pos.x && loc.z === pos.z) { - // state.pushInt(1); - // return; - // } - // } - // } + const pos = Position.unpackCoord(coord); + const zone = World.getZone(pos.x, pos.z, pos.level); + const locs = zone.staticLocs.concat(zone.locs); + + for (let index = 0; index < locs.length; index++) { + const loc = locs[index]; + const type = LocType.get(loc.type); + + if (type.active !== 1) { + continue; + } + + const layer = LocShapes.layer(loc.shape); + + if (loc.respawn !== -1 && layer === LocLayer.WALL) { + continue; + } + + if (layer === LocLayer.WALL) { + if (loc.x === pos.x && loc.z === pos.z) { + state.pushInt(1); + return; + } + } else if (layer === LocLayer.GROUND) { + const width = loc.angle === LocAngle.NORTH || loc.angle === LocAngle.SOUTH ? loc.length : loc.width; + const length = loc.angle === LocAngle.NORTH || loc.angle === LocAngle.SOUTH ? loc.width : loc.length; + for (let index = 0; index < width * length; index++) { + const deltaX = loc.x + (index % width); + const deltaZ = loc.z + index / width; + if (deltaX === pos.x && deltaZ === pos.z) { + state.pushInt(1); + return; + } + } + } else if (layer === LocLayer.GROUND_DECOR) { + if (loc.x === pos.x && loc.z === pos.z) { + state.pushInt(1); + return; + } + } + } state.pushInt(0); } }; diff --git a/src/lostcity/engine/zone/Zone.ts b/src/lostcity/engine/zone/Zone.ts index 8941dcdcd..7d1eaafa1 100644 --- a/src/lostcity/engine/zone/Zone.ts +++ b/src/lostcity/engine/zone/Zone.ts @@ -3,255 +3,192 @@ import Loc from '#lostcity/entity/Loc.js'; import Npc from '#lostcity/entity/Npc.js'; import Obj from '#lostcity/entity/Obj.js'; import Player from '#lostcity/entity/Player.js'; -import { ServerProt, ServerProtEncoders } from '#lostcity/server/ServerProt.js'; +import { ServerProt } from '#lostcity/server/ServerProt.js'; import World from '#lostcity/engine/World.js'; import { LocShapes } from '#lostcity/engine/collision/LocShape.js'; import PathingEntity from '#lostcity/entity/PathingEntity.js'; -import LocType from '#lostcity/cache/LocType.js'; -import BlockWalk from '#lostcity/entity/BlockWalk.js'; -import ZoneManager from './ZoneManager.js'; -export default class Zone { - index = -1; // packed coord - level = 0; +export class ZoneEvent { + type = -1; + receiverId = -1; buffer: Packet = new Packet(); - events: Packet[] = []; + tick = -1; + static = false; - // zone entities - players: Set = new Set(); // list of player uids - npcs: Set = new Set(); // list of npc nids (not uid because type may change) - - // locs - bits 0-2 = x (local), bits 3-5 = z (local), bits 6-7 = layer, bit 8 = static - // loc info - bits 0-15 = type, bits 16-20 = shape, bits 21-23 = angle - locs: Set = new Set(); - locInfo: Map = new Map(); - locDelEvent: Set = new Set(); - locAddEvent: Set = new Set(); - locDelCached: Map = new Map(); - locAddCached: Map = new Map(); - locDelTimer: Map = new Map(); - locAddTimer: Map = new Map(); - locChangeTimer: Map = new Map(); - - staticObjs: Obj[] = []; // source of truth from map - staticObjAddCached: Packet[] = []; - staticObjDelCached: Packet[] = []; - objs: Obj[] = []; // objs actually in the zone - - constructor(index: number, level: number) { - this.index = index; - this.level = level; - } - - debug() { - // console.log('zone', this.index); - - if (this.buffer.length > 0) { - // console.log('buffer', this.buffer); - } - - if (this.locDelEvent.size > 0 || this.locAddEvent.size > 0) { - // console.log('events', this.locDelEvent.size, this.locAddEvent.size); - } + // temp + x = -1; + z = -1; - if (this.locDelCached.size > 0 || this.locAddCached.size > 0) { - // console.log('cached', this.locDelCached.size, this.locAddCached.size); - } + // loc + layer = -1; - if (this.locDelTimer.size > 0 || this.locAddTimer.size > 0 || this.locChangeTimer.size > 0) { - // console.log('timer', this.locDelTimer.size, this.locAddTimer.size, this.locChangeTimer.size); - } + constructor(type: number) { + this.type = type; + } +} - for (const [packed, timer] of this.locDelTimer) { - // console.log('del', packed, timer - World.currentTick); - } +export default class Zone { + static mapAnim(srcX: number, srcZ: number, id: number, height: number, delay: number) { + const out = new Packet(); + out.p1(ServerProt.MAP_ANIM); - for (const [packed, timer] of this.locAddTimer) { - // console.log('add', packed, timer - World.currentTick); - } + out.p1(((srcX & 0x7) << 4) | (srcZ & 0x7)); + out.p2(id); + out.p1(height); + out.p2(delay); - for (const [packed, timer] of this.locChangeTimer) { - // console.log('change', packed, timer - World.currentTick); - } + return out; + } - // console.log('----'); + // variables fully broken out for now + //coord $from, coord $to, spotanim $spotanim, int $fromHeight, int $toHeight, int $startDelay, int $endDelay, int $peak, int $arc + static mapProjAnim(srcX: number, srcZ: number, dstX: number, dstZ: number, target: number, spotanim: number, srcHeight: number, dstHeight: number, startDelay: number, endDelay: number, peak: number, arc: number) { + const out = new Packet(); + out.p1(ServerProt.MAP_PROJANIM); + + out.p1(((srcX & 0x7) << 4) | (srcZ & 0x7)); + out.p1(dstX - srcX); + out.p1(dstZ - srcZ); + out.p2(target); // 0: coord, > 0: npc, < 0: player + out.p2(spotanim); + out.p1(srcHeight); + out.p1(dstHeight); + out.p2(startDelay); + out.p2(endDelay); + out.p1(peak); + out.p1(arc); + + return out; } - cycle() { - // despawn - for (const [packed, timer] of this.locAddTimer) { - if (timer - World.currentTick <= 0) { - // console.log('locAddTimer: despawning loc on tile'); - this.locAddTimer.delete(packed); - this.locAddCached.delete(packed); - - const isStatic = (packed >> 8) & 1; - if (!isStatic) { - this.locDelEvent.add(packed); - } - } - } + static locAddChange(srcX: number, srcZ: number, id: number, shape: number, angle: number) { + const out = new Packet(); + out.p1(ServerProt.LOC_ADD_CHANGE); - for (const obj of this.objs) { - // - } + out.p1(((srcX & 0x7) << 4) | (srcZ & 0x7)); + out.p1((shape << 2) | (angle & 3)); + out.p2(id); - // respawn - for (const [packed, timer] of this.locDelTimer) { - if (timer - World.currentTick <= 0) { - // console.log('locDelTimer: despawning loc on tile'); - this.locDelTimer.delete(packed); - this.locDelCached.delete(packed); - - const staticPacked = packed | (1 << 8); - if (this.locs.has(staticPacked)) { - // console.log('locDelTimer: respawning static loc on tile'); - this.locs.delete(packed); - this.locInfo.delete(packed); - this.locAddEvent.add(staticPacked); - } - } - } + return out; + } - for (const [packed, timer] of this.locChangeTimer) { - if (timer - World.currentTick <= 0) { - // console.log('locChangeTimer: changing loc on tile'); - this.locs.delete(packed); - this.locInfo.delete(packed); - this.locChangeTimer.delete(packed); - this.locAddCached.delete(packed); + static locDel(srcX: number, srcZ: number, shape: number, angle: number) { + const out = new Packet(); + out.p1(ServerProt.LOC_DEL); - const staticPacked = packed | (1 << 8); - this.locAddEvent.add(staticPacked); - } - } + out.p1(((srcX & 0x7) << 4) | (srcZ & 0x7)); + out.p1((shape << 2) | (angle & 3)); - for (const obj of this.staticObjs) { - // - } - - // shared events (this tick) - this.computeSharedEvents(); + return out; } - computeSharedEvents() { - this.buffer = new Packet(); - - for (const packed of this.locDelEvent) { - let info = this.locInfo.get(packed); - if (typeof info === 'undefined') { - // deleted static loc - info = this.locInfo.get(packed | (1 << 8)); - } - - if (typeof info === 'undefined') { - continue; - } + // merge player with loc, e.g. agility training through pipes + // useful due to draw prioritizes + static locMerge(srcX: number, srcZ: number, shape: number, angle: number, locId: number, startCycle: number, endCycle: number, pid: number, east: number, south: number, west: number, north: number) { + const out = new Packet(); + out.p1(ServerProt.LOC_MERGE); + + out.p1(((srcX & 0x7) << 4) | (srcZ & 0x7)); + out.p1((shape << 2) | (angle & 3)); + out.p2(locId); + out.p2(startCycle); + out.p2(endCycle); + out.p2(pid); + out.p1(east - srcX); + out.p1(south - srcZ); + out.p1(west - srcX); + out.p1(north - srcZ); + + return out; + } - const x = packed & 0x7; - const z = (packed >> 3) & 0x7; + static locAnim(srcX: number, srcZ: number, shape: number, angle: number, id: number) { + const out = new Packet(); + out.p1(ServerProt.LOC_ANIM); - const id = info & 0xFFFF; - const shape = (info >> 16) & 0x1F; - const angle = (info >> 21) & 3; + out.p1(((srcX & 0x7) << 4) | (srcZ & 0x7)); + out.p1((shape << 2) | (angle & 3)); + out.p2(id); - const buf = Zone.write(ServerProt.LOC_DEL, x, z, shape, angle); - this.buffer.pdata(buf); + return out; + } - const isStatic = (packed >> 8) & 1; - if (isStatic) { - this.locDelCached.set(packed, buf); - } else { - this.locs.delete(packed); - this.locInfo.delete(packed); - } + static objAdd(srcX: number, srcZ: number, id: number, count: number) { + const out = new Packet(); + out.p1(ServerProt.OBJ_ADD); - const type = LocType.get(id); - const { x: zoneX, z: zoneZ } = ZoneManager.unpackIndex(this.index); - World.collisionManager.changeLocCollision(shape, angle, type.blockrange, type.length, type.width, type.active, zoneX + x, zoneZ + z, this.level, false); + out.p1(((srcX & 0x7) << 4) | (srcZ & 0x7)); + out.p2(id); - // console.log('locDelEvent:', x, z, id, shape, angle, isStatic); + if (count > 65535) { + count = 65535; } + out.p2(count); - for (const packed of this.locAddEvent) { - const info = this.locInfo.get(packed); - if (typeof info === 'undefined') { - // console.log('locAddEvent: missing loc info'); - continue; - } - - const x = packed & 0x7; - const z = (packed >> 3) & 0x7; + return out; + } - const id = info & 0xFFFF; - const shape = (info >> 16) & 0x1F; - const angle = (info >> 21) & 3; + static objCount(srcX: number, srcZ: number, id: number, count: number) { + const out = new Packet(); + out.p1(ServerProt.OBJ_COUNT); - const buf = Zone.write(ServerProt.LOC_ADD_CHANGE, x, z, shape, angle, id); - this.buffer.pdata(buf); + out.p1(((srcX & 0x7) << 4) | (srcZ & 0x7)); + out.p2(id); - const isStatic = (packed >> 8) & 1; - if (!isStatic) { - this.locAddCached.set(packed, buf); - } + if (count > 65535) { + count = 65535; + } + out.p2(count); - const type = LocType.get(id); - const { x: zoneX, z: zoneZ } = ZoneManager.unpackIndex(this.index); - World.collisionManager.changeLocCollision(shape, angle, type.blockrange, type.length, type.width, type.active, zoneX + x, zoneZ + z, this.level, type.blockwalk); + return out; + } - // console.log('locAddEvent:', x, z, id, shape, angle, isStatic); - } + static objDel(srcX: number, srcZ: number, id: number, count: number) { + const out = new Packet(); + out.p1(ServerProt.OBJ_DEL); - for (const event of this.events) { - this.buffer.pdata(event); - } + out.p1(((srcX & 0x7) << 4) | (srcZ & 0x7)); + out.p2(id); - this.locDelEvent = new Set(); - this.locAddEvent = new Set(); - this.events = []; + return out; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private static write(opcode: ServerProt, ...args: any[]) { - const buf = new Packet(); - buf.p1(opcode); - ServerProtEncoders[opcode](buf, ...args); - return buf; - } + static objReveal(srcX: number, srcZ: number, id: number, count: number, receiverId: number) { + const out = new Packet(); + out.p1(ServerProt.OBJ_REVEAL); - anim(x: number, z: number, spotanim: number, height: number, delay: number) { - this.events.push(Zone.write(ServerProt.MAP_ANIM, x, z, spotanim, height, delay)); - } + out.p1(((srcX & 0x7) << 4) | (srcZ & 0x7)); + out.p2(id); + out.p2(count); + out.p2(receiverId); - projanim(x: number, z: number, dstX: number, dstZ: number, target: number, spotanim: number, srcHeight: number, dstHeight: number, startDelay: number, endDelay: number, peak: number, arc: number) { - this.events.push(Zone.write(ServerProt.MAP_PROJANIM, x, z, dstX, dstZ, target, spotanim, srcHeight, dstHeight, startDelay, endDelay, peak, arc)); + return out; } - locmerge(loc: Loc, player: Player, startCycle: number, endCycle: number, south: number, east: number, north: number, west: number) { - this.events.push(Zone.write(ServerProt.LOC_MERGE, loc.x, loc.z, loc.shape, loc.angle, loc.type, startCycle, endCycle, player.pid, east, south, west, north)); - } + index = -1; // packed coord - locanim(loc: Loc, seq: number) { - this.events.push(Zone.write(ServerProt.LOC_ANIM, loc.x, loc.z, loc.shape, loc.angle, seq)); - } + // zone entities + players: Set = new Set(); // list of player uids + npcs: Set = new Set(); // list of npc nids (not uid because type may change) + staticLocs: Loc[] = []; // source of truth from map data + locs: Loc[] = []; // dynamic locs + staticObjs: Obj[] = []; // source of truth from server map data + objs: Obj[] = []; // dynamic objs + + // zone events + updates: ZoneEvent[] = []; + lastEvent = -1; + buffer: Packet = new Packet(); - // ---- + constructor(index: number) { + this.index = index; + } enter(entity: PathingEntity) { if (entity instanceof Player && !this.players.has(entity.uid)) { this.players.add(entity.uid); } else if (entity instanceof Npc && !this.npcs.has(entity.nid)) { this.npcs.add(entity.nid); - - switch (entity.blockWalk) { - case BlockWalk.NPC: - World.collisionManager.changeNpcCollision(entity.width, entity.x, entity.z, entity.level, true); - break; - case BlockWalk.ALL: - World.collisionManager.changeNpcCollision(entity.width, entity.x, entity.z, entity.level, true); - World.collisionManager.changePlayerCollision(entity.width, entity.x, entity.z, entity.level, true); - break; - } } } @@ -260,191 +197,202 @@ export default class Zone { this.players.delete(entity.uid); } else if (entity instanceof Npc) { this.npcs.delete(entity.nid); - - switch (entity.blockWalk) { - case BlockWalk.NPC: - World.collisionManager.changeNpcCollision(entity.width, entity.x, entity.z, entity.level, false); - break; - case BlockWalk.ALL: - World.collisionManager.changeNpcCollision(entity.width, entity.x, entity.z, entity.level, false); - World.collisionManager.changePlayerCollision(entity.width, entity.x, entity.z, entity.level, false); - break; - } } } - // ---- - - addStaticLoc(absX: number, absZ: number, id: number, shape: number, angle: number) { - const x = absX & 0x7; - const z = absZ & 0x7; + // ---- not tied to any entities ---- - const packed = x | (z << 3) | (LocShapes.layer(shape) << 6) | (1 << 8); - const packedInfo = id | (shape << 16) | (angle << 21); + animMap(x: number, z: number, spotanim: number, height: number, delay: number) { + const event = new ZoneEvent(ServerProt.MAP_ANIM); - this.locs.add(packed); - this.locInfo.set(packed, packedInfo); + event.buffer = Zone.mapAnim(x, z, spotanim, height, delay); + event.x = x; + event.z = z; + event.tick = World.currentTick; - const type = LocType.get(id); - World.collisionManager.changeLocCollision(shape, angle, type.blockrange, type.length, type.width, type.active, absX, absZ, this.level, type.blockwalk); + this.updates.push(event); + this.lastEvent = World.currentTick; } - addLoc(absX: number, absZ: number, id: number, shape: number, angle: number, duration: number) { - const x = absX & 0x7; - const z = absZ & 0x7; + mapProjAnim(x: number, z: number, dstX: number, dstZ: number, target: number, spotanim: number, srcHeight: number, dstHeight: number, startDelay: number, endDelay: number, peak: number, arc: number) { + const event = new ZoneEvent(ServerProt.MAP_PROJANIM); - const packed = x | (z << 3) | (LocShapes.layer(shape) << 6); - const packedInfo = id | (shape << 16) | (angle << 21); + event.buffer = Zone.mapProjAnim(x, z, dstX, dstZ, target, spotanim, srcHeight, dstHeight, startDelay, endDelay, peak, arc); + event.x = x; + event.z = z; + event.tick = World.currentTick; - const staticPacked = packed | (1 << 8); - const staticInfo = this.locInfo.get(staticPacked); + this.updates.push(event); + this.lastEvent = World.currentTick; + } - if (this.locs.has(staticPacked) && typeof staticInfo !== 'undefined') { - const id = packedInfo & 0xFFFF; - const staticId = staticInfo & 0xFFFF; + // ---- static locs/objs are added during world init ---- - if (id === staticId) { - this.locs.delete(packed); - this.locInfo.delete(packed); - this.locAddEvent.add(staticPacked); - return; - } - } + addStaticLoc(loc: Loc) { + this.staticLocs.push(loc); + } - this.locs.add(packed); - this.locInfo.set(packed, packedInfo); - // console.log('addLoc(): adding loc on tile'); + addStaticObj(obj: Obj) { + this.staticObjs.push(obj); - const type = LocType.get(id); - World.collisionManager.changeLocCollision(shape, angle, type.blockrange, type.length, type.width, type.active, absX, absZ, this.level, type.blockwalk); + const event = new ZoneEvent(ServerProt.OBJ_ADD); + event.buffer = Zone.objAdd(obj.x, obj.z, obj.id, obj.count); + event.tick = World.currentTick; + event.static = true; + this.updates.push(event); + this.lastEvent = World.currentTick; + } + + // ---- - if (this.locDelEvent.has(packed)) { - // console.log('addLoc(): clearing old delete event'); - this.locDelEvent.delete(packed); - this.locDelCached.delete(packed); - this.locDelTimer.delete(packed); + addLoc(loc: Loc, duration: number) { + if (this.staticLocs.indexOf(loc) === -1) { + loc.despawn = World.currentTick + duration; + this.locs.push(loc); } - this.locAddEvent.add(packed); - this.locAddTimer.set(packed, World.currentTick + duration); - } + const event = new ZoneEvent(ServerProt.LOC_ADD_CHANGE); + event.buffer = Zone.locAddChange(loc.x, loc.z, loc.type, loc.shape, loc.angle); + event.x = loc.x; + event.z = loc.z; + event.tick = World.currentTick; + event.layer = LocShapes.layer(loc.shape); - changeLoc(absX: number, absZ: number, id: number, shape: number, angle: number, duration: number) { - const x = absX & 0x7; - const z = absZ & 0x7; + this.updates = this.updates.filter(event => { + if (event.x === loc.x && event.z === loc.z && event.layer === LocShapes.layer(loc.shape)) { + return false; + } - const packed = x | (z << 3) | (LocShapes.layer(shape) << 6); + return true; + }); - // console.log('changeLoc(): changing loc on tile'); - this.locs.add(packed); - this.locInfo.set(packed, id | (shape << 16) | (angle << 21)); - this.locAddEvent.add(packed); - this.locChangeTimer.set(packed, World.currentTick + duration); + this.updates.push(event); + this.lastEvent = World.currentTick; } - removeLoc(absX: number, absZ: number, shape: number, duration: number) { - const x = absX & 0x7; - const z = absZ & 0x7; + removeLoc(loc: Loc, duration: number) { + const event = new ZoneEvent(ServerProt.LOC_DEL); - // delete dynamic loc if it exists - const packed = x | (z << 3) | (LocShapes.layer(shape) << 6); - const info = this.locInfo.get(packed); - if (this.locs.has(packed) && typeof info !== 'undefined') { - // console.log('removeLoc(): deleting loc on tile'); - // this.locs.delete(packed); - // this.locInfo.delete(packed); + const dynamicIndex = this.locs.indexOf(loc); + if (dynamicIndex !== -1) { + this.locs.splice(dynamicIndex, 1); + } else { + // static locs remain forever in memory, just create a zone event + loc.respawn = World.currentTick + duration; + event.static = true; + } - const type = LocType.get(info & 0xFFFF); - const angle = (info >> 21) & 3; + event.buffer = Zone.locDel(loc.x, loc.z, loc.shape, loc.angle); + event.x = loc.x; + event.z = loc.z; + event.tick = World.currentTick; + event.layer = LocShapes.layer(loc.shape); - World.collisionManager.changeLocCollision(shape, angle, type.blockrange, type.length, type.width, type.active, absX, absZ, this.level, false); - } + this.updates = this.updates.filter(event => { + if (event.x === loc.x && event.z === loc.z && event.layer === LocShapes.layer(loc.shape)) { + return false; + } - // (temporarily) delete static loc if it exists - const staticPacked = packed | (1 << 8); - const staticInfo = this.locInfo.get(staticPacked); - if (this.locs.has(staticPacked) && typeof staticInfo !== 'undefined') { - // console.log('removeLoc(): deleting static loc on tile'); - this.locs.add(packed); // temporarily add dynamic loc to prevent static loc from respawning - this.locInfo.delete(packed); + return true; + }); - const type = LocType.get(staticInfo & 0xFFFF); - const angle = (staticInfo >> 21) & 3; + this.updates.push(event); + this.lastEvent = World.currentTick; + } - World.collisionManager.changeLocCollision(shape, angle, type.blockrange, type.length, type.width, type.active, absX, absZ, this.level, false); + getLoc(x: number, z: number, type: number): Loc | null { + const dynamicLoc = this.locs.findIndex(loc => loc.x === x && loc.z === z && loc.type === type); + if (dynamicLoc !== -1) { + return this.locs[dynamicLoc]; } - if (this.locAddEvent.has(packed)) { - // console.log('removeLoc(): clearing old add event'); - this.locAddEvent.delete(packed); - this.locAddCached.delete(packed); - this.locAddTimer.delete(packed); + const staticLoc = this.staticLocs.findIndex(loc => loc.x === x && loc.z === z && loc.type === type && loc.respawn < World.currentTick); + if (staticLoc !== -1) { + return this.staticLocs[staticLoc]; } - this.locDelEvent.add(packed); - this.locDelTimer.set(packed, World.currentTick + duration); + return null; } - getLoc(absX: number, absZ: number, layer: number): Loc | null { - const x = absX & 0x7; - const z = absZ & 0x7; - - // dynamic loc on the same layer takes precedence over static loc - const packed = x | (z << 3) | (layer << 6); - const info = this.locInfo.get(packed); - - if (this.locs.has(packed) && typeof info === 'undefined') { - // static loc has been despawned - return null; - } else if (this.locs.has(packed) && typeof info !== 'undefined') { - const id = info & 0xFFFF; - const shape = (info >> 16) & 0x1F; - const angle = (info >> 21) & 3; - - // legacy code compatibility - const type = LocType.get(id); - return new Loc(this.level, absX, absZ, type.width, type.length, id, shape, angle); - } + mergeLoc(loc: Loc, player: Player, startCycle: number, endCycle: number, south: number, east: number, north: number, west: number) { + const event = new ZoneEvent(ServerProt.LOC_MERGE); - // static loc - const staticPacked = packed | (1 << 8); - const staticInfo = this.locInfo.get(staticPacked); + event.buffer = Zone.locMerge(loc.x, loc.z, loc.shape, loc.angle, loc.type, startCycle, endCycle, player.pid, east, south, west, north); + event.x = loc.x; + event.z = loc.z; + event.tick = World.currentTick; + event.layer = LocShapes.layer(loc.shape); - if (this.locs.has(staticPacked) && typeof staticInfo !== 'undefined') { - const id = staticInfo & 0xFFFF; - const shape = (staticInfo >> 16) & 0x1F; - const angle = (staticInfo >> 21) & 3; + this.updates.push(event); + this.lastEvent = World.currentTick; + } - // legacy code compatibility - const type = LocType.get(id); - return new Loc(this.level, absX, absZ, type.width, type.length, id, shape, angle); - } + animLoc(loc: Loc, seq: number) { + const event = new ZoneEvent(ServerProt.LOC_ANIM); - return null; + event.buffer = Zone.locAnim(loc.x, loc.z, loc.shape, loc.angle, seq); + event.x = loc.x; + event.z = loc.z; + event.tick = World.currentTick; + event.layer = LocShapes.layer(loc.shape); + + this.updates.push(event); + this.lastEvent = World.currentTick; } // ---- - addStaticObj(obj: Obj) { - this.staticObjs.push(obj); - this.addObj(Obj.clone(obj), null, -1); // temp + addObj(obj: Obj, receiver: Player | null, duration: number) { + const event = new ZoneEvent(ServerProt.OBJ_ADD); + if (this.staticObjs.indexOf(obj) === -1) { + obj.despawn = World.currentTick + duration; + this.objs.push(obj); + } else { + event.static = true; + } - const buf = Zone.write(ServerProt.OBJ_ADD, obj.x, obj.z, obj.type, obj.count); - this.staticObjAddCached.push(buf); - } + if (receiver) { + event.receiverId = receiver.uid; + } + event.buffer = Zone.objAdd(obj.x, obj.z, obj.id, obj.count); + event.x = obj.x; + event.z = obj.z; + event.tick = World.currentTick; - addObj(obj: Obj, receiver: Player | null, duration: number) { - this.objs.push(obj); + this.updates.push(event); + this.lastEvent = World.currentTick; } - removeObj(obj: Obj, receiver: Player | null) { + removeObj(obj: Obj, receiver: Player | null, subtractTick: number = 0) { + const event = new ZoneEvent(ServerProt.OBJ_DEL); + + const dynamicIndex = this.objs.indexOf(obj); + if (dynamicIndex !== -1) { + this.objs.splice(dynamicIndex, 1); + + if (receiver) { + event.receiverId = receiver.uid; + } + } + + event.buffer = Zone.objDel(obj.x, obj.z, obj.id, obj.count); + event.x = obj.x; + event.z = obj.z; + event.tick = World.currentTick - subtractTick; + + this.updates.push(event); + this.lastEvent = World.currentTick; } getObj(x: number, z: number, type: number): Obj | null { - for (const obj of this.objs) { - if (obj.x === x && obj.z === z && obj.type === type) { - return obj; - } + const dynamicObj = this.objs.findIndex(obj => obj.x === x && obj.z === z && obj.type === type); + if (dynamicObj !== -1) { + return this.objs[dynamicObj]; + } + + const staticObj = this.staticObjs.findIndex(obj => obj.x === x && obj.z === z && obj.type === type && obj.respawn < World.currentTick); + if (staticObj !== -1) { + return this.staticObjs[staticObj]; } return null; diff --git a/src/lostcity/engine/zone/ZoneManager.ts b/src/lostcity/engine/zone/ZoneManager.ts index 2195ad282..be20f4054 100644 --- a/src/lostcity/engine/zone/ZoneManager.ts +++ b/src/lostcity/engine/zone/ZoneManager.ts @@ -20,7 +20,7 @@ export default class ZoneManager { const zoneIndex = ZoneManager.zoneIndex(absoluteX, absoluteZ, level); let zone = this.zones.get(zoneIndex); if (typeof zone == 'undefined') { - zone = new Zone(zoneIndex, level); + zone = new Zone(zoneIndex); this.zones.set(zoneIndex, zone); } return zone; diff --git a/src/lostcity/entity/Player.ts b/src/lostcity/entity/Player.ts index 73e3ee4e4..995ac8d63 100644 --- a/src/lostcity/entity/Player.ts +++ b/src/lostcity/entity/Player.ts @@ -54,6 +54,7 @@ import Environment from '#lostcity/util/Environment.js'; import WordEnc from '#lostcity/cache/WordEnc.js'; import WordPack from '#jagex2/wordenc/WordPack.js'; import SpotanimType from '#lostcity/cache/SpotanimType.js'; +import { ZoneEvent } from '#lostcity/engine/zone/Zone.js'; const levelExperience = new Int32Array(99); @@ -399,8 +400,7 @@ export default class Player extends PathingEntity { // build area loadedX: number = -1; loadedZ: number = -1; - newlyTrackedZones: Set = new Set(); - allTrackedZones: Set = new Set(); + loadedZones: Record = {}; npcs: Set = new Set(); // observed npcs players: Set = new Set(); // observed players lastMovement: number = 0; // for p_arrivedelay @@ -554,12 +554,6 @@ export default class Player extends PathingEntity { this.graphicId = -1; this.graphicHeight = -1; this.graphicDelay = -1; - - for (const zone of this.newlyTrackedZones) { - this.allTrackedZones.add(zone); - } - - this.newlyTrackedZones = new Set(); } decodeIn() { @@ -2614,16 +2608,16 @@ export default class Player extends PathingEntity { this.loadedX = this.x; this.loadedZ = this.z; - - this.allTrackedZones = new Set(); - this.newlyTrackedZones = new Set(); + this.loadedZones = {}; } if (this.tele && this.jump) { - this.allTrackedZones = new Set(); - this.newlyTrackedZones = new Set(); + this.loadedZones = {}; } + } + updateZones() { + // check nearby zones for updates const centerX = Position.zone(this.x); const centerZ = Position.zone(this.z); @@ -2632,79 +2626,38 @@ export default class Player extends PathingEntity { const topZ = Position.zone(this.loadedZ) + 6; const bottomZ = Position.zone(this.loadedZ) - 6; - // update newly tracked zones + // update 3 zones around the player for (let x = centerX - 3; x <= centerX + 3; x++) { for (let z = centerZ - 3; z <= centerZ + 3; z++) { + // check if the zone is within the build area if (x < leftX || x > rightX || z > topZ || z < bottomZ) { continue; } - const zone = x | (z << 11); - if (!this.allTrackedZones.has(zone)) { - this.newlyTrackedZones.add(zone); - } - } - } - - // remove old zones from all tracked (outside of 3x3 area around player's current pos) - for (const zone of this.allTrackedZones) { - const x = zone & 2047; - const z = zone >> 11; - - if (x < centerX - 3 || x > centerX + 3 || z > centerZ + 3 || z < centerZ - 3) { - this.allTrackedZones.delete(zone); - } - } - } - - updateZones() { - // console.log('tracked', this.newlyTrackedZones.size, this.allTrackedZones.size); - - for (const zone of this.newlyTrackedZones) { - const x = zone & 2047; - const z = zone >> 11; - - this.write(ServerProt.UPDATE_ZONE_FULL_FOLLOWS, x, z, this.loadedX, this.loadedZ); - - const { locDelCached, locAddCached, staticObjAddCached, staticObjDelCached } = World.getZone(x << 3, z << 3, this.level); - - for (const [packed, buf] of locDelCached) { - this.netOut.push(buf.copy()); - } - - for (const [packed, buf] of locAddCached) { - this.netOut.push(buf.copy()); - } - - for (const buf of staticObjAddCached) { - this.netOut.push(buf.copy()); - } + const zone = World.getZone(x << 3, z << 3, this.level); - for (const buf of staticObjDelCached) { - this.netOut.push(buf.copy()); - } - - if (locDelCached.size > 0 || locAddCached.size > 0) { - World.getZone(x << 3, z << 3, this.level).debug(); - } - } + // todo: receiver/shared buffer logic + if (typeof this.loadedZones[zone.index] === 'undefined') { + // full update necessary to clear client zone memory + this.write(ServerProt.UPDATE_ZONE_FULL_FOLLOWS, x, z, this.loadedX, this.loadedZ); + this.loadedZones[zone.index] = -1; // note: flash appears when changing floors + } - for (const zone of this.allTrackedZones) { - const x = zone & 2047; - const z = zone >> 11; + const updates = World.getUpdates(zone.index).filter((event: ZoneEvent): boolean => { + return event.tick > this.loadedZones[zone.index]; + }); - const { buffer, locAddTimer, locDelTimer, locChangeTimer } = World.getZone(x << 3, z << 3, this.level); + if (updates.length) { + this.write(ServerProt.UPDATE_ZONE_PARTIAL_FOLLOWS, x, z, this.loadedX, this.loadedZ); - // shared events - if (buffer.length > 0) { - this.write(ServerProt.UPDATE_ZONE_PARTIAL_ENCLOSED, x, z, this.loadedX, this.loadedZ, buffer); + for (let i = 0; i < updates.length; i++) { + // have to copy because encryption will be applied to buffer + this.netOut.push(new Packet(updates[i].buffer)); + } + } - World.getZone(x << 3, z << 3, this.level).debug(); - } else if (locAddTimer.size > 0 || locDelTimer.size > 0 || locChangeTimer.size > 0) { - World.getZone(x << 3, z << 3, this.level).debug(); + this.loadedZones[zone.index] = World.currentTick; } - - // local events } }