From 9a0da6c3f5b588fe8a171d0d29d68cc9270e052b Mon Sep 17 00:00:00 2001 From: Jordan Abraham Date: Tue, 15 Jul 2025 12:25:28 -0400 Subject: [PATCH 1/5] feat: engine improvements --- src/engine/World.ts | 84 +++++++++---------- src/engine/WorldStat.ts | 1 + src/engine/entity/NetworkPlayer.ts | 15 +--- src/engine/entity/Npc.ts | 21 ++--- src/engine/entity/PathingEntity.ts | 11 ++- src/engine/entity/Player.ts | 3 + .../game/client/handler/OpHeldHandler.ts | 3 +- .../game/client/handler/OpHeldTHandler.ts | 3 +- .../game/client/handler/OpHeldUHandler.ts | 3 +- src/server/Metrics.ts | 6 ++ 10 files changed, 72 insertions(+), 78 deletions(-) diff --git a/src/engine/World.ts b/src/engine/World.ts index 0cb782633..11c0550d8 100644 --- a/src/engine/World.ts +++ b/src/engine/World.ts @@ -79,6 +79,7 @@ import { trackCycleBandwidthOutBytes, trackCycleClientInTime, trackCycleClientOutTime, + trackCycleInfoTime, trackCycleLoginTime, trackCycleLogoutTime, trackCycleNpcTime, @@ -145,8 +146,8 @@ class World { readonly npcEventQueue: LinkList; // debug data - readonly lastCycleStats: number[]; - readonly cycleStats: number[]; + readonly lastCycleStats: Uint8Array; + readonly cycleStats: Uint8Array; tickRate: number = World.TICKRATE; // speeds up when we're processing server shutdown currentTick: number = 0; // the current tick of the game world. @@ -171,8 +172,8 @@ class World { this.locObjTracker = new LinkList(); this.queue = new LinkList(); this.npcEventQueue = new LinkList(); - this.lastCycleStats = new Array(12).fill(0); - this.cycleStats = new Array(12).fill(0); + this.lastCycleStats = new Uint8Array(13); + this.cycleStats = new Uint8Array(13); if (Environment.STANDALONE_BUNDLE) { if (this.loginThread instanceof Worker) { @@ -274,10 +275,7 @@ class World { for (let i = 0; i < this.vars.length; i++) { const varsh = VarSharedType.get(i); - if (varsh.type === ScriptVarType.STRING) { - // todo: "null"? another value? - continue; - } else { + if (varsh.type !== ScriptVarType.STRING) { this.vars[i] = varsh.type === ScriptVarType.INT ? 0 : -1; } } @@ -358,13 +356,9 @@ class World { cycle(): void { try { const start: number = Date.now(); + // the deviation the thread took from sleeping last tick until now. const drift: number = Math.max(0, start - this.nextTick); - // world processing - // - world queue - // - npc hunt - this.processWorld(); - // client input // - calculate afk event readiness // - process packets @@ -432,20 +426,33 @@ class World { // - reset invs this.processCleanup(); - // ---- - - const tick: number = this.currentTick; - if (this.shutdown) { this.processShutdown(); } - if (tick % World.PLAYER_SAVERATE === 0 && tick > 0) { + // ---- + // - increment tick before processing world queues. + // - world queues can't happen on tick 0. + // - logging is technically tick+1 but that's ok idc. + // - tick will ALWAYS be > 0 from here on... + + this.currentTick++; + this.nextTick += this.tickRate; + + // world processing + // - world queue + // - npc hunt + this.processWorld(); + + // ---- + // - do logging and stuff from here on, no more cycle stuff. + + if (this.currentTick % World.PLAYER_SAVERATE === 0) { // auto-save players every 15 mins this.savePlayers(); } - if (tick % World.PLAYER_COORDLOGRATE === 0 && tick > 0) { + if (this.currentTick % World.PLAYER_COORDLOGRATE === 0) { for (const player of this.players) { player.addSessionLog(LoggerEventType.MODERATOR, 'Server check in'); } @@ -486,6 +493,7 @@ class World { this.lastCycleStats[WorldStat.LOGOUT] = this.cycleStats[WorldStat.LOGOUT]; this.lastCycleStats[WorldStat.LOGIN] = this.cycleStats[WorldStat.LOGIN]; this.lastCycleStats[WorldStat.ZONE] = this.cycleStats[WorldStat.ZONE]; + this.lastCycleStats[WorldStat.INFO] = this.cycleStats[WorldStat.INFO]; this.lastCycleStats[WorldStat.CLIENT_OUT] = this.cycleStats[WorldStat.CLIENT_OUT]; this.lastCycleStats[WorldStat.CLEANUP] = this.cycleStats[WorldStat.CLEANUP]; this.lastCycleStats[WorldStat.BANDWIDTH_IN] = this.cycleStats[WorldStat.BANDWIDTH_IN]; @@ -503,6 +511,7 @@ class World { trackCycleNpcTime.observe(this.cycleStats[WorldStat.NPC]); trackCyclePlayerTime.observe(this.cycleStats[WorldStat.PLAYER]); trackCycleZoneTime.observe(this.cycleStats[WorldStat.ZONE]); + trackCycleInfoTime.observe(this.cycleStats[WorldStat.INFO]); trackCycleLoginTime.observe(this.cycleStats[WorldStat.LOGIN]); trackCycleLogoutTime.observe(this.cycleStats[WorldStat.LOGOUT]); @@ -514,13 +523,10 @@ class World { printInfo(`tick ${this.currentTick}: ${this.cycleStats[WorldStat.CYCLE]}/${this.tickRate} ms, ${Math.trunc(process.memoryUsage().heapTotal / 1024 / 1024)} MB heap`); printDebug(`${this.getTotalPlayers()}/${World.PLAYERS} players | ${this.getTotalNpcs()}/${World.NPCS} npcs | ${this.gameMap.getTotalZones()} zones | ${this.gameMap.getTotalLocs()} locs | ${this.gameMap.getTotalObjs()} objs`); printDebug( - `${this.cycleStats[WorldStat.WORLD]} ms world | ${this.cycleStats[WorldStat.CLIENT_IN]} ms client in | ${this.cycleStats[WorldStat.NPC]} ms npcs | ${this.cycleStats[WorldStat.PLAYER]} ms players | ${this.cycleStats[WorldStat.LOGOUT]} ms logout | ${this.cycleStats[WorldStat.LOGIN]} ms login | ${this.cycleStats[WorldStat.ZONE]} ms zones | ${this.cycleStats[WorldStat.CLIENT_OUT]} ms client out | ${this.cycleStats[WorldStat.CLEANUP]} ms cleanup` + `${this.cycleStats[WorldStat.WORLD]} ms world | ${this.cycleStats[WorldStat.CLIENT_IN]} ms client in | ${this.cycleStats[WorldStat.NPC]} ms npcs | ${this.cycleStats[WorldStat.PLAYER]} ms players | ${this.cycleStats[WorldStat.LOGOUT]} ms logout | ${this.cycleStats[WorldStat.LOGIN]} ms login | ${this.cycleStats[WorldStat.ZONE]} ms zones | ${this.cycleStats[WorldStat.INFO]} ms infos | ${this.cycleStats[WorldStat.CLIENT_OUT]} ms client out | ${this.cycleStats[WorldStat.CLEANUP]} ms cleanup` ); } - this.currentTick++; - this.nextTick += this.tickRate; - // ---- setTimeout(this.cycle.bind(this), Math.max(0, this.tickRate - (Date.now() - start) - drift)); @@ -589,7 +595,7 @@ class World { const hunt = HuntType.get(npc.huntMode); if (hunt && hunt.type === HuntModeType.PLAYER) { - npc.huntAll(); + npc.huntAll(hunt); } } } @@ -626,15 +632,10 @@ class World { } if ((!player.target || player.target instanceof Loc || player.target instanceof Obj) && player.faceEntity !== -1) { - player.faceEntity = -1; - player.masks |= player.entitymask; + player.unfocusTargetEntity(); } - if (!player.busy() && player.opcalled) { - player.moveClickRequest = false; - } else { - player.moveClickRequest = true; - } + player.moveClickRequest = !(!player.busy() && player.opcalled); if (!followingPlayer && player.opcalled && (player.userPath.length === 0 || !Environment.NODE_CLIENT_ROUTEFINDER)) { player.pathToTarget(); @@ -988,13 +989,11 @@ class World { // - convert npc movements // - compute npc info private processInfo(): void { - // TODO: benchmark this? + const start: number = Date.now(); for (const player of this.players) { player.reorient(); player.buildArea.rebuildNormal(); // set origin before compute player is why this is above. - - const appearance = player.masks & PlayerInfoProt.APPEARANCE ? player.generateAppearance() : (player.lastAppearanceBytes ?? player.generateAppearance()); - + // TODO possible optimization to only compute when actually needed? rsbuf.computePlayer( player.x, player.level, @@ -1009,7 +1008,7 @@ class World { player.visibility, player.isActive, player.masks, - appearance, + player.masks & PlayerInfoProt.APPEARANCE ? player.generateAppearance() : (player.lastAppearanceBytes ?? player.generateAppearance()), player.lastAppearance, player.faceEntity, player.faceX, @@ -1042,6 +1041,7 @@ class World { for (const npc of this.npcs) { npc.reorient(); + // TODO possible optimization to only compute when actually needed? rsbuf.computeNpc( npc.x, npc.level, @@ -1070,6 +1070,7 @@ class World { npc.graphicDelay ); } + this.cycleStats[WorldStat.INFO] = Date.now() - start; } // - map update @@ -1168,13 +1169,13 @@ class World { } // Item stock is under min if (item.count < invType.stockcount[index] && tick % invType.stockrate[index] === 0) { - inv.add(item?.id, 1, index, true, false, false); + inv.add(item.id, 1, index, true, false, false); inv.update = true; continue; } // Item stock is over min if (item.count > invType.stockcount[index] && tick % invType.stockrate[index] === 0) { - inv.remove(item?.id, 1, index, true); + inv.remove(item.id, 1, index, true); inv.update = true; continue; } @@ -1182,7 +1183,7 @@ class World { // Item stock is not listed, such as general stores // Tested on low and high player count worlds, ever 1 minute stock decreases. if (invType.allstock && !invType.stockcount[index] && tick % World.INV_STOCKRATE === 0) { - inv.remove(item?.id, 1, index, true); + inv.remove(item.id, 1, index, true); inv.update = true; } } @@ -1579,11 +1580,6 @@ class World { }); } - addPlayer(player: Player): void { - this.newPlayers.add(player); - player.isActive = true; - } - sendPrivateChatModeToFriendsServer(player: Player): void { this.friendThread.postMessage({ type: 'player_chat_setmode', diff --git a/src/engine/WorldStat.ts b/src/engine/WorldStat.ts index d60dfc6a2..d1aa9f7ee 100644 --- a/src/engine/WorldStat.ts +++ b/src/engine/WorldStat.ts @@ -7,6 +7,7 @@ export const enum WorldStat { LOGOUT, LOGIN, ZONE, + INFO, CLIENT_OUT, CLEANUP, BANDWIDTH_IN, diff --git a/src/engine/entity/NetworkPlayer.ts b/src/engine/entity/NetworkPlayer.ts index b2c463bdb..84806239d 100644 --- a/src/engine/entity/NetworkPlayer.ts +++ b/src/engine/entity/NetworkPlayer.ts @@ -187,10 +187,7 @@ export class NetworkPlayer extends Player { } writeInner(message: OutgoingMessage): void { - const client = this.client; - if (!client) { - return; - } + const client: ClientSocket = this.client; const encoder: MessageEncoder | undefined = ServerProtProvider.ServerProtRepository.getEncoder(message); if (!encoder) { @@ -207,16 +204,12 @@ export class NetworkPlayer extends Player { buf.pos = 0; - if (client.encryptor) { - buf.p1(prot.id + client.encryptor.nextInt()); - } else { - buf.p1(prot.id); - } + buf.p1(prot.id + (client.encryptor?.nextInt() ?? 0)); if (prot.length === -1) { - buf.p1(0); + buf.pos += 1; } else if (prot.length === -2) { - buf.p2(0); + buf.pos += 2; } const start: number = buf.pos; diff --git a/src/engine/entity/Npc.ts b/src/engine/entity/Npc.ts index 0adad5d43..d90079de6 100644 --- a/src/engine/entity/Npc.ts +++ b/src/engine/entity/Npc.ts @@ -64,7 +64,6 @@ export default class Npc extends PathingEntity { huntMode: number = -1; huntTarget: Entity | null = null; huntrange: number = 0; - spawnTriggerPending: boolean = true; nextPatrolTick: number = -1; nextPatrolPoint: number = 0; @@ -160,7 +159,7 @@ export default class Npc extends PathingEntity { if (hunt.nobodyNear !== HuntNobodyNear.PAUSEHUNT || rsbuf.getNpcObservers(this.nid) > 0 || hunt.type === HuntModeType.PLAYER) { // - hunt npc/obj/loc if (hunt && hunt.type !== HuntModeType.PLAYER) { - this.huntAll(); + this.huntAll(hunt); } // Increment huntclock @@ -244,11 +243,9 @@ export default class Npc extends PathingEntity { // https://x.com/JagexAsh/status/1821236327150710829 // https://x.com/JagexAsh/status/1799793914595131463 - huntAll(): void { + huntAll(hunt: HuntType): void { this.huntTarget = null; - const hunt: HuntType = HuntType.get(this.huntMode); - // If a huntrate is defined, this acts as a throttle if (this.huntClock < hunt.rate - 1) { return; @@ -272,8 +269,7 @@ export default class Npc extends PathingEntity { // Pick randomly from the hunted entities if (hunted.length > 0) { - const entity: Entity = hunted[Math.floor(Math.random() * hunted.length)]; - this.huntTarget = entity; + this.huntTarget = hunted[Math.floor(Math.random() * hunted.length)]; } } @@ -292,10 +288,7 @@ export default class Npc extends PathingEntity { for (let i = 0; i < this.vars.length; i++) { const varn = VarNpcType.get(i); - if (varn.type === ScriptVarType.STRING) { - // todo: "null"? another value? - continue; - } else { + if (varn.type !== ScriptVarType.STRING) { this.vars[i] = varn.type === ScriptVarType.INT ? 0 : -1; } } @@ -402,16 +395,14 @@ export default class Npc extends PathingEntity { clearInteraction(): void { super.clearInteraction(); this.targetOp = NpcMode.NONE; - this.faceEntity = -1; - this.masks |= NpcInfoProt.FACE_ENTITY; + this.unfocusTargetEntity(); } resetDefaults(): void { this.clearInteraction(); const type: NpcType = NpcType.get(this.type); this.targetOp = type.defaultmode; - this.faceEntity = -1; - this.masks |= this.entitymask; + this.unfocusTargetEntity(); const npcType: NpcType = NpcType.get(this.type); this.huntMode = npcType.huntmode; diff --git a/src/engine/entity/PathingEntity.ts b/src/engine/entity/PathingEntity.ts index fff5d2812..c1954863b 100644 --- a/src/engine/entity/PathingEntity.ts +++ b/src/engine/entity/PathingEntity.ts @@ -338,6 +338,14 @@ export default abstract class PathingEntity extends Entity { this.orientationZ = CoordGrid.fine(this.z - 1, this.length); } + /** + * Unfocus from a possible targeted pathing entity. + */ + unfocusTargetEntity(): void { + this.masks |= this.entitymask; + this.faceEntity = -1; + } + /** * Try to focus back on a possible target. * This is needed because the target can move. @@ -623,8 +631,7 @@ export default abstract class PathingEntity extends Entity { this.faceZ = -1; if (!this.target && this.faceEntity !== -1) { - this.masks |= this.entitymask; - this.faceEntity = -1; + this.unfocusTargetEntity(); } } diff --git a/src/engine/entity/Player.ts b/src/engine/entity/Player.ts index 8cedcad55..0e0ef28fc 100644 --- a/src/engine/entity/Player.ts +++ b/src/engine/entity/Player.ts @@ -543,6 +543,9 @@ export default class Player extends PathingEntity { } this.write(new UpdateRunEnergy(this.runenergy)); this.write(new ResetAnims()); + if (!this.target) { + this.unfocusTargetEntity(); + } this.moveSpeed = MoveSpeed.INSTANT; this.tele = true; this.jump = true; diff --git a/src/network/game/client/handler/OpHeldHandler.ts b/src/network/game/client/handler/OpHeldHandler.ts index 53cc3a4ba..8541ed5f1 100644 --- a/src/network/game/client/handler/OpHeldHandler.ts +++ b/src/network/game/client/handler/OpHeldHandler.ts @@ -50,8 +50,7 @@ export default class OpHeldHandler extends MessageHandler { } player.moveClickRequest = false; // uses the dueling ring op to move whilst busy & queue pending: https://youtu.be/GPfN3Isl2rM - player.faceEntity = -1; - player.masks |= player.entitymask; + player.unfocusTargetEntity(); let trigger: ServerTriggerType; if (message.op === 1) { diff --git a/src/network/game/client/handler/OpHeldTHandler.ts b/src/network/game/client/handler/OpHeldTHandler.ts index 2dd3534ef..ec8e2079e 100644 --- a/src/network/game/client/handler/OpHeldTHandler.ts +++ b/src/network/game/client/handler/OpHeldTHandler.ts @@ -46,8 +46,7 @@ export default class OpHeldTHandler extends MessageHandler { player.lastSlot = slot; player.clearPendingAction(); - player.faceEntity = -1; - player.masks |= player.entitymask; + player.unfocusTargetEntity(); player.addSessionLog(LoggerEventType.MODERATOR, `Cast ${spellCom.comName} on ${ObjType.get(item).debugname}`); diff --git a/src/network/game/client/handler/OpHeldUHandler.ts b/src/network/game/client/handler/OpHeldUHandler.ts index 38e430124..e7adf169f 100644 --- a/src/network/game/client/handler/OpHeldUHandler.ts +++ b/src/network/game/client/handler/OpHeldUHandler.ts @@ -68,8 +68,7 @@ export default class OpHeldUHandler extends MessageHandler { const useObjType = ObjType.get(player.lastUseItem); player.clearPendingAction(); - player.faceEntity = -1; - player.masks |= player.entitymask; + player.unfocusTargetEntity(); if ((objType.members || useObjType.members) && !Environment.NODE_MEMBERS) { player.messageGame("To use this item please login to a members' server."); diff --git a/src/server/Metrics.ts b/src/server/Metrics.ts index d1fe72169..b6a144a5d 100644 --- a/src/server/Metrics.ts +++ b/src/server/Metrics.ts @@ -45,6 +45,12 @@ export const trackCycleZoneTime = new Histogram({ buckets: [10, 20, 30, 40, 50, 100] }); +export const trackCycleInfoTime = new Histogram({ + name: 'lostcity_cycle_info_ms', + help: 'Info processing duration in milliseconds.', + buckets: [10, 20, 30, 40, 50, 100] +}); + export const trackCycleLoginTime = new Histogram({ name: 'lostcity_cycle_login_ms', help: 'Login processing duration in milliseconds.', From 21dd0e1b69b51e7cfb58876cdd088ede491cb978 Mon Sep 17 00:00:00 2001 From: Jordan Abraham Date: Tue, 15 Jul 2025 12:45:41 -0400 Subject: [PATCH 2/5] feat: engine improvements --- src/engine/entity/Player.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/engine/entity/Player.ts b/src/engine/entity/Player.ts index 0e0ef28fc..054ab8160 100644 --- a/src/engine/entity/Player.ts +++ b/src/engine/entity/Player.ts @@ -543,9 +543,7 @@ export default class Player extends PathingEntity { } this.write(new UpdateRunEnergy(this.runenergy)); this.write(new ResetAnims()); - if (!this.target) { - this.unfocusTargetEntity(); - } + this.masks |= this.entitymask; this.moveSpeed = MoveSpeed.INSTANT; this.tele = true; this.jump = true; From ffaa3237573274a33e3e536ae59297b218b2d2a4 Mon Sep 17 00:00:00 2001 From: Jordan Abraham Date: Tue, 15 Jul 2025 13:00:51 -0400 Subject: [PATCH 3/5] fix: Uint8Array -> Uint16Array for cycle stats --- src/engine/World.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/World.ts b/src/engine/World.ts index 11c0550d8..726bfe91b 100644 --- a/src/engine/World.ts +++ b/src/engine/World.ts @@ -146,8 +146,8 @@ class World { readonly npcEventQueue: LinkList; // debug data - readonly lastCycleStats: Uint8Array; - readonly cycleStats: Uint8Array; + readonly lastCycleStats: Uint16Array; + readonly cycleStats: Uint16Array; tickRate: number = World.TICKRATE; // speeds up when we're processing server shutdown currentTick: number = 0; // the current tick of the game world. @@ -172,8 +172,8 @@ class World { this.locObjTracker = new LinkList(); this.queue = new LinkList(); this.npcEventQueue = new LinkList(); - this.lastCycleStats = new Uint8Array(13); - this.cycleStats = new Uint8Array(13); + this.lastCycleStats = new Uint16Array(13); + this.cycleStats = new Uint16Array(13); if (Environment.STANDALONE_BUNDLE) { if (this.loginThread instanceof Worker) { From cb49d6f9d9953ac5d2935df10e23376c04d0f766 Mon Sep 17 00:00:00 2001 From: Jordan Abraham Date: Tue, 15 Jul 2025 14:05:00 -0400 Subject: [PATCH 4/5] feat: stop unnecessarily constructing npcs and objs on world launch for free worlds --- src/engine/GameMap.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/GameMap.ts b/src/engine/GameMap.ts index 13f3de0b2..2c060fa5d 100644 --- a/src/engine/GameMap.ts +++ b/src/engine/GameMap.ts @@ -112,9 +112,9 @@ export default class GameMap { continue; } const npcType: NpcType = NpcType.get(id); - const size: number = npcType.size; - const npc: Npc = new Npc(level, absoluteX, absoluteZ, size, size, EntityLifeCycle.RESPAWN, World.getNextNid(), npcType.id, npcType.moverestrict, npcType.blockwalk); if ((npcType.members && this.members) || !npcType.members) { + const size: number = npcType.size; + const npc: Npc = new Npc(level, absoluteX, absoluteZ, size, size, EntityLifeCycle.RESPAWN, World.getNextNid(), npcType.id, npcType.moverestrict, npcType.blockwalk); World.addNpc(npc, -1); } } @@ -134,8 +134,8 @@ export default class GameMap { continue; } const objType: ObjType = ObjType.get(id); - const obj: Obj = new Obj(level, absoluteX, absoluteZ, EntityLifeCycle.RESPAWN, objType.id, count); if ((objType.members && this.members) || !objType.members) { + const obj: Obj = new Obj(level, absoluteX, absoluteZ, EntityLifeCycle.RESPAWN, objType.id, count); this.getZone(obj.x, obj.z, obj.level).addStaticObj(obj); } } From 66221eef8da248ec3d14919a687f58a4b6c50ff6 Mon Sep 17 00:00:00 2001 From: Jordan Abraham Date: Wed, 16 Jul 2025 21:34:21 -0400 Subject: [PATCH 5/5] fix: player appearances not refreshing during onreconnect. --- src/engine/entity/Player.ts | 9 ++++----- src/network/game/client/handler/IfPlayerDesignHandler.ts | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/engine/entity/Player.ts b/src/engine/entity/Player.ts index 054ab8160..a41feff1c 100644 --- a/src/engine/entity/Player.ts +++ b/src/engine/entity/Player.ts @@ -315,7 +315,7 @@ export default class Player extends PathingEntity { webClient: boolean = false; combatLevel: number = 3; headicons: number = 0; - appearance: number = -1; + appearance: number = InvType.WORN; lastAppearance: number = 0; lastAppearanceBytes: Uint8Array | null = null; baseLevels = new Uint8Array(21); @@ -444,7 +444,6 @@ export default class Player extends PathingEntity { this.timers.clear(); this.heroPoints.clear(); this.buildArea.clear(false); - this.appearance = -1; this.lastAppearance = 0; this.lastAppearanceBytes = null; this.isActive = false; @@ -462,7 +461,6 @@ export default class Player extends PathingEntity { this.messageType = null; this.message = null; this.logMessage = null; - this.appearance = -1; this.socialProtect = false; this.reportAbuseProtect = false; } @@ -544,6 +542,7 @@ export default class Player extends PathingEntity { this.write(new UpdateRunEnergy(this.runenergy)); this.write(new ResetAnims()); this.masks |= this.entitymask; + this.masks |= PlayerInfoProt.APPEARANCE; this.moveSpeed = MoveSpeed.INSTANT; this.tele = true; this.jump = true; @@ -1751,7 +1750,7 @@ export default class Player extends PathingEntity { if (this.combatLevel != this.getCombatLevel()) { this.combatLevel = this.getCombatLevel(); - this.buildAppearance(InvType.WORN); + this.buildAppearance(this.appearance); } } @@ -1771,7 +1770,7 @@ export default class Player extends PathingEntity { if (this.getCombatLevel() != this.combatLevel) { this.combatLevel = this.getCombatLevel(); - this.buildAppearance(InvType.WORN); + this.buildAppearance(this.appearance); } } diff --git a/src/network/game/client/handler/IfPlayerDesignHandler.ts b/src/network/game/client/handler/IfPlayerDesignHandler.ts index a3ba32480..619702a52 100644 --- a/src/network/game/client/handler/IfPlayerDesignHandler.ts +++ b/src/network/game/client/handler/IfPlayerDesignHandler.ts @@ -1,5 +1,4 @@ import IdkType from '#/cache/config/IdkType.js'; -import InvType from '#/cache/config/InvType.js'; import Player from '#/engine/entity/Player.js'; import MessageHandler from '#/network/game/client/handler/MessageHandler.js'; import IfPlayerDesign from '#/network/game/client/model/IfPlayerDesign.js'; @@ -53,7 +52,7 @@ export default class IfPlayerDesignHandler extends MessageHandler