From a92b2eeef575dab531f0260484c2915459c4e7ac Mon Sep 17 00:00:00 2001 From: Mark Date: Sun, 9 Feb 2025 16:28:46 -0500 Subject: [PATCH] fix(engine): Follow pathing and walk triggers behave closer to pre-2007 behavior (#1238) * fix(engine): Player should not random walk while following another player * fix(engine): Match following behavior with pre-2007, fix edge cases * fix(engine): Move path recalc logic out of movement logic block * fix(engine): Remove unused 'interactWalkTrigger' property * fix(engine): Reorganize interactions and movement --- src/engine/World.ts | 166 ++++++++++-------- src/engine/entity/PathingEntity.ts | 4 +- src/engine/entity/Player.ts | 133 +++++++------- .../rs225/client/handler/MoveClickHandler.ts | 1 - 4 files changed, 165 insertions(+), 139 deletions(-) diff --git a/src/engine/World.ts b/src/engine/World.ts index e0f9e2f686..daa96315b6 100644 --- a/src/engine/World.ts +++ b/src/engine/World.ts @@ -33,7 +33,7 @@ import { CrcBuffer32, makeCrcs, makeCrcsAsync } from '#/cache/CrcTable.js'; import { preloadClient, preloadClientAsync } from '#/cache/PreloadedPacks.js'; import { CoordGrid } from '#/engine/CoordGrid.js'; -import GameMap, {changeLocCollision, changeNpcCollision, changePlayerCollision} from '#/engine/GameMap.js'; +import GameMap, { changeLocCollision, changeNpcCollision, changePlayerCollision } from '#/engine/GameMap.js'; import { Inventory } from '#/engine/Inventory.js'; import WorldStat from '#/engine/WorldStat.js'; @@ -72,7 +72,21 @@ import Environment from '#/util/Environment.js'; import { printDebug, printError, printInfo } from '#/util/Logger.js'; import { createWorker } from '#/util/WorkerFactory.js'; import HuntModeType from '#/engine/entity/hunt/HuntModeType.js'; -import { trackCycleBandwidthInBytes, trackCycleBandwidthOutBytes, trackCycleClientInTime, trackCycleClientOutTime, trackCycleLoginTime, trackCycleLogoutTime, trackCycleNpcTime, trackCyclePlayerTime, trackCycleTime, trackCycleWorldTime, trackCycleZoneTime, trackNpcCount, trackPlayerCount } from '#/server/Metrics.js'; +import { + trackCycleBandwidthInBytes, + trackCycleBandwidthOutBytes, + trackCycleClientInTime, + trackCycleClientOutTime, + trackCycleLoginTime, + trackCycleLogoutTime, + trackCycleNpcTime, + trackCyclePlayerTime, + trackCycleTime, + trackCycleWorldTime, + trackCycleZoneTime, + trackNpcCount, + trackPlayerCount +} from '#/server/Metrics.js'; import WalkTriggerSetting from '#/util/WalkTriggerSetting.js'; import LinkList from '#/util/LinkList.js'; import { fromBase37, toBase37, toSafeName } from '#/util/JString.js'; @@ -83,16 +97,12 @@ import LoggerEventType from '#/server/logger/LoggerEventType.js'; import MoveSpeed from '#/engine/entity/MoveSpeed.js'; import ScriptVarType from '#/cache/config/ScriptVarType.js'; -const priv = forge.pki.privateKeyFromPem( - Environment.STANDALONE_BUNDLE ? - (await (await fetch('data/config/private.pem')).text()) : - fs.readFileSync('data/config/private.pem', 'ascii') -); +const priv = forge.pki.privateKeyFromPem(Environment.STANDALONE_BUNDLE ? await (await fetch('data/config/private.pem')).text() : fs.readFileSync('data/config/private.pem', 'ascii')); type LogoutRequest = { save: Uint8Array; lastAttempt: number; -} +}; class World { private loginThread = createWorker(Environment.STANDALONE_BUNDLE ? 'LoginThread.js' : './server/login/LoginThread.ts'); @@ -286,33 +296,35 @@ class World { } async loadAsync(): Promise { - const count = (await Promise.all([ - NpcType.loadAsync('data/pack'), - ObjType.loadAsync('data/pack'), - LocType.loadAsync('data/pack'), - FontType.loadAsync('data/pack'), - WordEnc.loadAsync('data/pack'), - VarPlayerType.loadAsync('data/pack'), - ParamType.loadAsync('data/pack'), - IdkType.loadAsync('data/pack'), - SeqFrame.loadAsync('data/pack'), - SeqType.loadAsync('data/pack'), - SpotanimType.loadAsync('data/pack'), - CategoryType.loadAsync('data/pack'), - EnumType.loadAsync('data/pack'), - StructType.loadAsync('data/pack'), - InvType.loadAsync('data/pack'), - MesanimType.loadAsync('data/pack'), - DbTableType.loadAsync('data/pack'), - DbRowType.loadAsync('data/pack'), - HuntType.loadAsync('data/pack'), - VarNpcType.loadAsync('data/pack'), - VarSharedType.loadAsync('data/pack'), - Component.loadAsync('data/pack'), - makeCrcsAsync(), - preloadClientAsync(), - ScriptProvider.loadAsync('data/pack'), - ])).at(-1); + const count = ( + await Promise.all([ + NpcType.loadAsync('data/pack'), + ObjType.loadAsync('data/pack'), + LocType.loadAsync('data/pack'), + FontType.loadAsync('data/pack'), + WordEnc.loadAsync('data/pack'), + VarPlayerType.loadAsync('data/pack'), + ParamType.loadAsync('data/pack'), + IdkType.loadAsync('data/pack'), + SeqFrame.loadAsync('data/pack'), + SeqType.loadAsync('data/pack'), + SpotanimType.loadAsync('data/pack'), + CategoryType.loadAsync('data/pack'), + EnumType.loadAsync('data/pack'), + StructType.loadAsync('data/pack'), + InvType.loadAsync('data/pack'), + MesanimType.loadAsync('data/pack'), + DbTableType.loadAsync('data/pack'), + DbRowType.loadAsync('data/pack'), + HuntType.loadAsync('data/pack'), + VarNpcType.loadAsync('data/pack'), + VarSharedType.loadAsync('data/pack'), + Component.loadAsync('data/pack'), + makeCrcsAsync(), + preloadClientAsync(), + ScriptProvider.loadAsync('data/pack') + ]) + ).at(-1); this.invs.clear(); for (let i = 0; i < InvType.count; i++) { @@ -537,7 +549,9 @@ class World { if (Environment.NODE_DEBUG_PROFILE) { 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`); + 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.currentTick++; @@ -625,7 +639,6 @@ class World { npc.huntAll(); } } - } this.cycleStats[WorldStat.WORLD] = Date.now() - start; @@ -651,7 +664,7 @@ class World { } if (isClientConnected(player) && player.decodeIn()) { - const followingPlayer = (player.targetOp === ServerTriggerType.APPLAYER3 || player.targetOp === ServerTriggerType.OPPLAYER3); + const followingPlayer = player.targetOp === ServerTriggerType.APPLAYER3 || player.targetOp === ServerTriggerType.OPPLAYER3; if (player.userPath.length > 0 || player.opcalled) { if (player.delayed) { player.unsetMapFlag(); @@ -663,7 +676,8 @@ class World { player.masks |= InfoProt.PLAYER_FACE_ENTITY.id; } - if ((!player.busy() && player.opcalled) || player.opucalled) { // opu in osrs doesnt have a busy check + if ((!player.busy() && player.opcalled) || player.opucalled) { + // opu in osrs doesnt have a busy check player.moveClickRequest = false; } else { player.moveClickRequest = true; @@ -683,13 +697,13 @@ class World { } } - if (player.target instanceof Player && followingPlayer) { - if (CoordGrid.distanceToSW(player, player.target) <= 25) { - player.pathToPathingTarget(); - } else { - player.clearWaypoints(); - } - } + // if (player.target instanceof Player && followingPlayer) { + // if (CoordGrid.distanceToSW(player, player.target) <= 25) { + // player.pathToPathingTarget(); + // } else { + // player.clearWaypoints(); + // } + // } } if (this.currentTick - player.lastResponse >= World.TIMEOUT_SOCKET_LOGOUT) { @@ -833,6 +847,9 @@ class World { // - movement player.processInteraction(); + // - run energy + player.updateEnergy(); + if ((player.masks & InfoProt.PLAYER_EXACT_MOVE.id) == 0) { player.validateDistanceWalked(); } @@ -902,7 +919,7 @@ class World { player.addSessionLog(LoggerEventType.ENGINE, 'Tried to log in - old session is mid-logout'); if (isClientConnected(player)) { - player.client.send(Uint8Array.from([ 5 ])); + player.client.send(Uint8Array.from([5])); player.client.close(); } @@ -923,7 +940,7 @@ class World { if (other instanceof NetworkPlayer && player instanceof NetworkPlayer) { other.client = player.client; - other.client.send(Uint8Array.from([ 15 ])); + other.client.send(Uint8Array.from([15])); } other.onReconnect(); @@ -940,7 +957,7 @@ class World { if (player instanceof NetworkPlayer) { player.addSessionLog(LoggerEventType.ENGINE, 'Tried to log in - already logged in'); - player.client.send(Uint8Array.from([ 5 ])); + player.client.send(Uint8Array.from([5])); player.client.close(); } @@ -957,7 +974,6 @@ class World { continue; } - // normal login process let pid: number; try { @@ -982,9 +998,9 @@ class World { player.client.state = 1; if (player.staffModLevel >= 1) { - player.client.send(Uint8Array.from([ 18 ])); + player.client.send(Uint8Array.from([18])); } else { - player.client.send(Uint8Array.from([ 2 ])); + player.client.send(Uint8Array.from([2])); } } @@ -1395,7 +1411,7 @@ class World { zone.revealObj(obj, obj.receiver64); // objs next life cycle always starts from the last time they changed + the inputted duration. // accounting for reveal time here. - const nextLifecycle: number = (change !== -1 ? (Obj.REVEAL - (this.currentTick - change)) : 0) + this.currentTick + duration; + const nextLifecycle: number = (change !== -1 ? Obj.REVEAL - (this.currentTick - change) : 0) + this.currentTick + duration; obj.setLifeCycle(nextLifecycle); this.trackZone(nextLifecycle, zone); this.trackZone(this.currentTick, zone); @@ -1437,7 +1453,7 @@ class World { this.friendThread.postMessage({ type: 'player_friendslist_add', username: player.username, - target: targetUsername37, + target: targetUsername37 }); } @@ -1446,7 +1462,7 @@ class World { this.friendThread.postMessage({ type: 'player_friendslist_remove', username: player.username, - target: targetUsername37, + target: targetUsername37 }); } @@ -1455,7 +1471,7 @@ class World { this.friendThread.postMessage({ type: 'player_ignorelist_add', username: player.username, - target: targetUsername37, + target: targetUsername37 }); } @@ -1464,7 +1480,7 @@ class World { this.friendThread.postMessage({ type: 'player_ignorelist_remove', username: player.username, - target: targetUsername37, + target: targetUsername37 }); } @@ -1476,7 +1492,7 @@ class World { this.friendThread.postMessage({ type: 'player_chat_setmode', username: player.username, - chatModePrivate: player.privateChat, + chatModePrivate: player.privateChat }); } @@ -1510,7 +1526,7 @@ class World { this.friendThread.postMessage({ type: 'player_logout', - username: player.username, + username: player.username }); } @@ -1523,7 +1539,7 @@ class World { if (isClientConnected(player)) { if (response !== -1) { - player.client.send(Uint8Array.from([ response ])); + player.client.send(Uint8Array.from([response])); } player.client.close(); @@ -1537,7 +1553,7 @@ class World { type: 'private_message', username: player.username, staffLvl: player.staffModLevel, - pmId: (Environment.NODE_ID << 24) + (Math.random() * 0xFF << 16) + (this.pmCount++), + pmId: (Environment.NODE_ID << 24) + ((Math.random() * 0xff) << 16) + this.pmCount++, target: targetUsername37, message: message, coord: player.coord @@ -1639,7 +1655,7 @@ class World { return this.players.next(); } - scaleByPlayerCount(rate : number): number { + scaleByPlayerCount(rate: number): number { // not sure if it caps at 2k player count or not const playerCount = Math.min(this.getTotalPlayers(), 2000); return (((4000 - playerCount) * rate) / 4000) | 0; // assuming scale works the same way as the runescript one @@ -1738,22 +1754,22 @@ class World { if (reply === -1) { // login server offline - client.send(Uint8Array.from([ 8 ])); + client.send(Uint8Array.from([8])); client.close(); return; } else if (reply === 1) { // invalid username or password - client.send(Uint8Array.from([ 3 ])); + client.send(Uint8Array.from([3])); client.close(); return; } else if (reply === 3) { // already logged in (on another world) - client.send(Uint8Array.from([ 5 ])); + client.send(Uint8Array.from([5])); client.close(); return; } else if (reply === 5) { // account has been disabled (banned) - client.send(Uint8Array.from([ 4 ])); + client.send(Uint8Array.from([4])); client.close(); return; } @@ -1788,7 +1804,7 @@ class World { } // bad save :( the player won't be happy - client.send(Uint8Array.from([ 13 ])); + client.send(Uint8Array.from([13])); client.close(); // todo: maybe we can tell the login thread to swap for the last-good save? @@ -1809,7 +1825,7 @@ class World { } } - onFriendMessage({ opcode, data }: { opcode: FriendsServerOpcodes, data: any }) { + onFriendMessage({ opcode, data }: { opcode: FriendsServerOpcodes; data: any }) { try { if (opcode === FriendsServerOpcodes.UPDATE_FRIENDLIST) { const username37 = BigInt(data.username37); @@ -1912,7 +1928,7 @@ class World { if (client.opcode === 16 || client.opcode === 18) { const rev = World.loginBuf.g1(); if (rev !== 225) { - client.send(Uint8Array.from([ 6 ])); + client.send(Uint8Array.from([6])); client.close(); return; } @@ -1924,7 +1940,7 @@ class World { World.loginBuf.gdata(crcs, 0, crcs.length); if (CrcBuffer32 !== Packet.getcrc(crcs, 0, crcs.length)) { - client.send(Uint8Array.from([ 6 ])); + client.send(Uint8Array.from([6])); client.close(); return; } @@ -1934,7 +1950,7 @@ class World { if (World.loginBuf.g1() !== 10) { // RSA error // sending out of date intentionally - client.send(Uint8Array.from([ 6 ])); + client.send(Uint8Array.from([6])); client.close(); return; } @@ -1957,26 +1973,26 @@ class World { // todo: record login attempt? if (username.length < 1 || username.length > 12) { - client.send(Uint8Array.from([ 3 ])); + client.send(Uint8Array.from([3])); client.close(); return; } if (password.length < 1 || password.length > 20) { - client.send(Uint8Array.from([ 3 ])); + client.send(Uint8Array.from([3])); client.close(); return; } if (this.getTotalPlayers() > 2000) { - client.send(Uint8Array.from([ 7 ])); + client.send(Uint8Array.from([7])); client.close(); return; } if (this.logoutRequests.has(username)) { // still trying to log out from the last session on this world! - client.send(Uint8Array.from([ 5 ])); + client.send(Uint8Array.from([5])); client.close(); return; } diff --git a/src/engine/entity/PathingEntity.ts b/src/engine/entity/PathingEntity.ts index 150a5d9754..1024b3ecf5 100644 --- a/src/engine/entity/PathingEntity.ts +++ b/src/engine/entity/PathingEntity.ts @@ -53,6 +53,8 @@ export default abstract class PathingEntity extends Entity { jump: boolean = false; lastStepX: number = -1; lastStepZ: number = -1; + followX: number = -1; + followZ: number = -1; stepsTaken: number = 0; lastInt: number = -1; // resume_p_countdialog, ai_queue lastCrawl: boolean = false; @@ -476,7 +478,7 @@ export default abstract class PathingEntity extends Entity { } if (this.targetOp === ServerTriggerType.APPLAYER3 || this.targetOp === ServerTriggerType.OPPLAYER3) { - this.queueWaypoint(this.target.lastStepX, this.target.lastStepZ); + this.queueWaypoint(this.target.followX, this.target.followZ); return; } diff --git a/src/engine/entity/Player.ts b/src/engine/entity/Player.ts index e5036222a5..9e403f7ad9 100644 --- a/src/engine/entity/Player.ts +++ b/src/engine/entity/Player.ts @@ -256,7 +256,6 @@ export default class Player extends PathingEntity { }[] = []; allowDesign: boolean = false; afkEventReady: boolean = false; - interactWalkTrigger: boolean = false; moveClickRequest: boolean = false; loggedOut: boolean = false; // pending logout processing @@ -507,19 +506,12 @@ export default class Player extends PathingEntity { // ---- - updateMovement(repathAllowed: boolean = true): boolean { + updateMovement(): boolean { // players cannot walk if they have a modal open *and* something in their queue, confirmed as far back as 2005 - if (this.moveClickRequest && this.busy() && (this.queue.head() != null || this.engineQueue.head() != null || this.walktrigger !== -1)) { - this.recoverEnergy(false); + if (this.moveClickRequest && this.busy() && (this.queue.head() != null || this.engineQueue.head() != null)) { return false; } - if (Environment.NODE_WALKTRIGGER_SETTING === WalkTriggerSetting.PLAYERMOVEMENT && !this.target && this.hasWaypoints()) { - this.processWalktrigger(); - } - if (repathAllowed && this.target instanceof PathingEntity && !this.interacted && this.walktrigger === -1) { - this.pathToPathingTarget(); - } if (this.moveSpeed !== MoveSpeed.INSTANT) { this.moveSpeed = this.defaultMoveSpeed(); if (this.basRunning === -1) { @@ -534,9 +526,27 @@ export default class Player extends PathingEntity { this.tempRun = 0; } - const moved = this.lastTickX !== this.x || this.lastTickZ !== this.z; - this.drainEnergy(moved); - this.recoverEnergy(moved); + if (this.stepsTaken > 0) { + this.lastMovement = World.currentTick + 1; + } + + return this.stepsTaken > 0; + } + + updateEnergy() { + if (this.delayed) { + return; + } + if (this.stepsTaken < 2) { + const recovered = ((this.baseLevels[PlayerStat.AGILITY] / 9) | 0) + 8; + this.runenergy = Math.min(this.runenergy + recovered, 10000); + } else { + const weightKg = Math.floor(this.runweight / 1000); + const clampWeight = Math.min(Math.max(weightKg, 0), 64); + const loss = (67 + (67 * clampWeight) / 64) | 0; + this.runenergy = Math.max(this.runenergy - loss, 0); + } + if (this.runenergy === 0) { this.run = 0; // todo: better way to sync engine varp @@ -545,10 +555,6 @@ export default class Player extends PathingEntity { if (this.runenergy < 100) { this.tempRun = 0; } - if (moved) { - this.lastMovement = World.currentTick + 1; - } - return moved; } private drainEnergy(moved: boolean): void { @@ -894,18 +900,21 @@ export default class Player extends PathingEntity { } this.messageGame('Nothing interesting happens.'); - this.interacted = true; this.clearWaypoints(); } - tryInteract(allowOpScenery: boolean) { + tryInteract(allowOpScenery: boolean): boolean { if (this.target === null || !this.canAccess()) { - return; + return false; } const opTrigger = this.getOpTrigger(); const apTrigger = this.getApTrigger(); + // The follow interaction doesn't do anything, just continue + if (this.targetOp === ServerTriggerType.APPLAYER3 || this.targetOp === ServerTriggerType.OPPLAYER3) { + return false; + } // Run the opTrigger if it exists and Player is within range // allowOpScenery controls if Locs and Objs can be op'd if (opTrigger && (this.target instanceof PathingEntity || allowOpScenery) && this.inOperableDistance(this.target)) { @@ -915,12 +924,12 @@ export default class Player extends PathingEntity { this.clearWaypoints(); this.executeScript(ScriptRunner.init(opTrigger, this, target), true); - this.interacted = true; // If p_opnpc was called, remember it for later // For now, keep the current target this.nextTarget = this.target; this.target = target; + return true; } // Run the apTrigger if it exists and Player is within range @@ -937,7 +946,6 @@ export default class Player extends PathingEntity { this.clearWaypoints(); this.executeScript(ScriptRunner.init(apTrigger, this, target), true); - this.interacted = true; // If p_opnpc was called, remember it for later // For now, keep the current target @@ -953,19 +961,23 @@ export default class Player extends PathingEntity { this.waypoints = wayPoints; this.waypointIndex = waypointIndex; this.target = target; - this.interacted = false; + return false; } + return true; } // Run the default apTrigger. This is the ap analog to the "NIH" default op else if (this.inApproachDistance(this.apRange, this.target)) { this.apRange = -1; + return false; } // Run the default opTrigger if within range else if (this.target && (this.target instanceof PathingEntity || allowOpScenery) && this.inOperableDistance(this.target)) { this.defaultOp(); + return true; } + return false; } validateTarget(): boolean { @@ -1005,59 +1017,56 @@ export default class Player extends PathingEntity { } processInteraction() { - if (this.target === null || !this.canAccess()) { - this.updateMovement(false); - return false; - } + this.followX = this.lastStepX; + this.followZ = this.lastStepZ; + this.nextTarget = null; - if (!this.validateTarget()) { - this.clearInteraction(); - this.unsetMapFlag(); - return; - } + const followOp = this.targetOp === ServerTriggerType.APPLAYER3 || this.targetOp === ServerTriggerType.OPPLAYER3; - const opTrigger = this.getOpTrigger(); - this.nextTarget = null; + let interacted = false; - // opplayer3 is `Follow` - // Follow interaction behaves different than standard ones - if (this.targetOp === ServerTriggerType.APPLAYER3 || this.targetOp === ServerTriggerType.OPPLAYER3) { - const walktrigger: number = this.walktrigger; - if (this.hasWaypoints()) { - this.processWalktrigger(); - } - const moved: boolean = this.updateMovement(false); - if (!moved && walktrigger !== -1 && this.target instanceof Player && (this.x !== this.target.lastStepX || this.z !== this.target.lastStepZ)) { + // If there is a target and p_access is available, try to interact before movement + if (this.target && this.canAccess()) { + // Clear the interaction if target validation does not pass + if (!this.validateTarget()) { this.clearInteraction(); this.unsetMapFlag(); + return; } - return; - } - if (!this.interactWalkTrigger || this.hasWaypoints()) { - this.processWalktrigger(); - this.interactWalkTrigger = true; + // Run the optrigger, but applayer3 should not run this + if (!followOp) { + this.processWalktrigger(); + } + + interacted = this.tryInteract(false); } - // Try an interaction - this.tryInteract(false); + // This block won't run if the initial interaction attempt was successful + if (!interacted) { + // Recalc path + this.pathToPathingTarget(); + + // Process walktrigger if there is waypoints + if (this.hasWaypoints()) { + this.processWalktrigger(); + } - let moved = false; + // If a stun clears the Player's waypoints, clear the interaction + if (!this.hasWaypoints() && followOp) { + this.clearInteraction(); + } - // If interacted, recover energy because the Player did not move - if (this.interacted) { - this.recoverEnergy(false); - } + this.updateMovement(); - // Otherwise, a target still exists to try to interact with - else if (this.target) { - this.processWalktrigger(); - moved = this.updateMovement(); - this.tryInteract(!moved); + // If there's a target and p_access is available, try to interact after moving + if (this.target && this.canAccess()) { + interacted = this.tryInteract(this.stepsTaken === 0); + } } // If Player did not interact, has no path, and did not move this cycle, terminate the interaction - if (!this.interacted && !this.hasWaypoints() && !moved) { + if (!interacted && !this.hasWaypoints() && this.stepsTaken === 0 && this.target && !followOp) { this.messageGame("I can't reach that!"); this.clearInteraction(); } @@ -1068,7 +1077,7 @@ export default class Player extends PathingEntity { } // Otherwise, the interaction ran - else if (this.interacted && !this.apRangeCalled) { + else if (interacted && !this.apRangeCalled) { this.clearInteraction(); } diff --git a/src/network/rs225/client/handler/MoveClickHandler.ts b/src/network/rs225/client/handler/MoveClickHandler.ts index d6ed97f8e9..92c2fb2a44 100644 --- a/src/network/rs225/client/handler/MoveClickHandler.ts +++ b/src/network/rs225/client/handler/MoveClickHandler.ts @@ -38,7 +38,6 @@ export default class MoveClickHandler extends MessageHandler { if (Environment.NODE_WALKTRIGGER_SETTING === WalkTriggerSetting.PLAYERPACKET) { player.pathToMoveClick(player.userPath, !Environment.NODE_CLIENT_ROUTEFINDER); } - player.interactWalkTrigger = false; if (!message.opClick) { player.clearPendingAction(); if (player.runenergy < 100 && message.ctrlHeld === 1) {