diff --git a/.env.example b/.env.example index 6a3c38904..5e1acfb95 100644 --- a/.env.example +++ b/.env.example @@ -23,7 +23,6 @@ # NODE_STAFF=pazaz # NODE_CLIENT_ROUTEFINDER=true # NODE_SOCKET_TIMEOUT=true -# NODE_WALKTRIGGER_SETTING=0 # NODE_PROFILE=main # NODE_MAX_PLAYERS=2047 # NODE_MAX_NPCS=8191 diff --git a/src/engine/GameMap.ts b/src/engine/GameMap.ts index 5fc561aa6..ef754dc32 100644 --- a/src/engine/GameMap.ts +++ b/src/engine/GameMap.ts @@ -19,6 +19,8 @@ import Packet from '#/io/Packet.js'; import Environment from '#/util/Environment.js'; import { printDebug, printFatalError, printWarning } from '#/util/Logger.js'; +export type RouteCoordinates = { x: number; z: number }; + export default class GameMap { private static readonly OPEN: number = 0x0; private static readonly BLOCK_MAP_SQUARE: number = 0x1; @@ -339,6 +341,9 @@ export function changeLocCollision(shape: number, angle: number, blockrange: boo } } } +export function findNaivePath(level: number, srcX: number, srcZ: number, destX: number, destZ: number, srcWidth: number, srcHeight: number, destWidth: number, destHeight: number, extraFlag: number, collision: CollisionType): Uint32Array { + return rsmod.findNaivePath(level, srcX, srcZ, destX, destZ, srcWidth, srcHeight, destWidth, destHeight, extraFlag, collision); +} /** * Change collision at a specified Position for npcs. @@ -387,10 +392,6 @@ export function findPathToLoc(level: number, srcX: number, srcZ: number, destX: return rsmod.findPath(level, srcX, srcZ, destX, destZ, srcSize, destWidth, destHeight, angle, shape, true, blockAccessFlags, 25, CollisionType.NORMAL); } -export function findNaivePath(level: number, srcX: number, srcZ: number, destX: number, destZ: number, srcWidth: number, srcHeight: number, destWidth: number, destHeight: number, extraFlag: number, collision: CollisionType): Uint32Array { - return rsmod.findNaivePath(level, srcX, srcZ, destX, destZ, srcWidth, srcHeight, destWidth, destHeight, extraFlag, collision); -} - export function reachedEntity(level: number, srcX: number, srcZ: number, destX: number, destZ: number, destWidth: number, destHeight: number, srcSize: number): boolean { return rsmod.reached(level, srcX, srcZ, destX, destZ, destWidth, destHeight, srcSize, 0, -2, 0); } diff --git a/src/engine/World.ts b/src/engine/World.ts index 11cb622fc..bffc8741d 100644 --- a/src/engine/World.ts +++ b/src/engine/World.ts @@ -93,8 +93,6 @@ import Environment from '#/util/Environment.js'; import { fromBase37, toBase37, toSafeName } from '#/util/JString.js'; import LinkList from '#/datastruct/LinkList.js'; import { printDebug, printError, printInfo } from '#/util/Logger.js'; -import { WalkTriggerSetting } from '#/engine/entity/WalkTriggerSetting.js'; - import OnDemand from './OnDemand.js'; import { ObjDelayedRequest } from './entity/ObjDelayedRequest.js'; import DbTableIndex from '#/cache/config/DbTableIndex.js'; @@ -612,7 +610,6 @@ class World { player.processInputTracking(); if (isClientConnected(player) && player.decodeIn()) { - const followingPlayer = player.targetOp === ServerTriggerType.APPLAYER3 || player.targetOp === ServerTriggerType.OPPLAYER3; if (player.userPath.length > 0 || player.opcalled) { if (player.delayed) { player.unsetMapFlag(); @@ -624,19 +621,6 @@ class World { } else { player.moveClickRequest = true; } - - if (!followingPlayer && player.opcalled && (player.userPath.length === 0 || !Environment.NODE_CLIENT_ROUTEFINDER)) { - player.pathToTarget(); - continue; - } - - if (Environment.NODE_WALKTRIGGER_SETTING !== WalkTriggerSetting.PLAYERPACKET) { - player.pathToMoveClick(player.userPath, !Environment.NODE_CLIENT_ROUTEFINDER); - - if (Environment.NODE_WALKTRIGGER_SETTING === WalkTriggerSetting.PLAYERSETUP && !player.opcalled && player.hasWaypoints()) { - player.processWalktrigger(); - } - } } } diff --git a/src/engine/entity/AllowRepath.ts b/src/engine/entity/AllowRepath.ts new file mode 100644 index 000000000..b7d3061be --- /dev/null +++ b/src/engine/entity/AllowRepath.ts @@ -0,0 +1,4 @@ +export const enum AllowRepath { + BEFOREDEST, + NONE +} diff --git a/src/engine/entity/Npc.ts b/src/engine/entity/Npc.ts index b30ea8161..56a8e619b 100644 --- a/src/engine/entity/Npc.ts +++ b/src/engine/entity/Npc.ts @@ -1,6 +1,6 @@ import { NpcInfoProt } from '@2004scape/rsbuf'; import * as rsbuf from '@2004scape/rsbuf'; -import { CollisionFlag, CollisionType } from '@2004scape/rsmod-pathfinder'; +import { CollisionFlag } from '@2004scape/rsmod-pathfinder'; import HuntType from '#/cache/config/HuntType.js'; import NpcType from '#/cache/config/NpcType.js'; @@ -26,7 +26,7 @@ import { NpcQueueRequest } from '#/engine/entity/NpcQueueRequest.js'; import { NpcStat } from '#/engine/entity/NpcStat.js'; import PathingEntity from '#/engine/entity/PathingEntity.js'; import Player from '#/engine/entity/Player.js'; -import { isFlagged, findNaivePath } from '#/engine/GameMap.js'; +import { isFlagged } from '#/engine/GameMap.js'; import ScriptFile from '#/engine/script/ScriptFile.js'; import { HuntIterator } from '#/engine/script/ScriptIterators.js'; import ScriptPointer from '#/engine/script/ScriptPointer.js'; @@ -329,7 +329,7 @@ export default class Npc extends PathingEntity { } if (CoordGrid.intersects(this.x, this.z, this.width, this.length, this.target.x, this.target.z, this.target.width, this.target.length)) { - this.queueWaypoints(findNaivePath(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.length, this.target.width, this.target.length, 0, CollisionType.NORMAL)); + this.randomWalk(); return; } @@ -683,7 +683,7 @@ export default class Npc extends PathingEntity { return true; } - private randomWalk(range: number) { + private wander(range: number) { const dx = Math.round(Math.random() * (range * 2) - range); const dz = Math.round(Math.random() * (range * 2) - range); const destX = this.startX + dx; @@ -703,7 +703,7 @@ export default class Npc extends PathingEntity { // 1/8 chance to move every tick (even if they already have a destination) if (type.moverestrict !== MoveRestrict.NOMOVE && Math.random() < 0.125) { - this.randomWalk(type.wanderrange); + this.wander(type.wanderrange); } this.updateMovement(); diff --git a/src/engine/entity/PathingEntity.ts b/src/engine/entity/PathingEntity.ts index a6eb31737..7982adbf7 100644 --- a/src/engine/entity/PathingEntity.ts +++ b/src/engine/entity/PathingEntity.ts @@ -7,6 +7,7 @@ import Entity from '#/engine/entity/Entity.js'; import { EntityLifeCycle } from '#/engine/entity/EntityLifeCycle.js'; import { Interaction } from '#/engine/entity/Interaction.js'; import Loc from '#/engine/entity/Loc.js'; +import { AllowRepath } from './AllowRepath.js'; import { MoveRestrict } from '#/engine/entity/MoveRestrict.js'; import { MoveSpeed } from '#/engine/entity/MoveSpeed.js'; import { MoveStrategy } from '#/engine/entity/MoveStrategy.js'; @@ -15,10 +16,9 @@ import Npc from '#/engine/entity/Npc.js'; import { NpcMode } from '#/engine/entity/NpcMode.js'; import Obj from '#/engine/entity/Obj.js'; import Player from '#/engine/entity/Player.js'; -import { canTravel, changeNpcCollision, changePlayerCollision, findNaivePath, findPath, findPathToEntity, findPathToLoc, isApproached, isZoneAllocated, reachedEntity, reachedLoc, reachedObj } from '#/engine/GameMap.js'; +import { canTravel, changeNpcCollision, changePlayerCollision, findPath, findPathToEntity, findPathToLoc, isApproached, isZoneAllocated, reachedEntity, reachedLoc, reachedObj, findNaivePath } from '#/engine/GameMap.js'; import ServerTriggerType from '#/engine/script/ServerTriggerType.js'; import World from '#/engine/World.js'; -import Environment from '#/util/Environment.js'; type TargetSubject = { type: number; @@ -54,6 +54,7 @@ export default abstract class PathingEntity extends Entity { lastInt: number = -1; // resume_p_countdialog, ai_queue lastCrawl: boolean = false; lastMovement: number = 0; + allowRepath: AllowRepath = AllowRepath.BEFOREDEST; walktrigger: number = -1; walktriggerArg: number = 0; // used for npcs @@ -161,25 +162,22 @@ export default abstract class PathingEntity extends Entity { * @param previousLevel Their previous recorded level position before movement. This one is important for teleport. */ private refreshZonePresence(previousX: number, previousZ: number, previousLevel: number): void { - // only update collision map when the entity moves. - if (this.x != previousX || this.z !== previousZ || this.level !== previousLevel) { - // update collision map - // players and npcs both can change this collision - switch (this.blockWalk) { - case BlockWalk.NPC: - changeNpcCollision(this.width, previousX, previousZ, previousLevel, false); - changeNpcCollision(this.width, this.x, this.z, this.level, true); - break; - case BlockWalk.ALL: - changeNpcCollision(this.width, previousX, previousZ, previousLevel, false); - changeNpcCollision(this.width, this.x, this.z, this.level, true); - changePlayerCollision(this.width, previousX, previousZ, previousLevel, false); - changePlayerCollision(this.width, this.x, this.z, this.level, true); - break; - } - this.lastStepX = previousX; - this.lastStepZ = previousZ; - } + // update collision map + // players and npcs both can change this collision + switch (this.blockWalk) { + case BlockWalk.NPC: + changeNpcCollision(this.width, previousX, previousZ, previousLevel, false); + changeNpcCollision(this.width, this.x, this.z, this.level, true); + break; + case BlockWalk.ALL: + changeNpcCollision(this.width, previousX, previousZ, previousLevel, false); + changeNpcCollision(this.width, this.x, this.z, this.level, true); + changePlayerCollision(this.width, previousX, previousZ, previousLevel, false); + changePlayerCollision(this.width, this.x, this.z, this.level, true); + break; + } + this.lastStepX = previousX; + this.lastStepZ = previousZ; if (CoordGrid.zone(previousX) !== CoordGrid.zone(this.x) || CoordGrid.zone(previousZ) !== CoordGrid.zone(this.z) || previousLevel != this.level) { World.gameMap.getZone(previousX, previousZ, previousLevel).leave(this); @@ -203,27 +201,33 @@ export default abstract class PathingEntity extends Entity { * Returns the final validated step direction. */ private validateAndAdvanceStep(): number { - const dir: number | null = this.takeStep(); - if (dir === null) { - return -1; + const collisionStrategy: CollisionType | null = this.getCollisionStrategy(); + const extraFlag: CollisionFlag = this.blockWalkFlag(); + + // Clear waypoints if no movement is allowed + if (collisionStrategy === null || extraFlag === CollisionFlag.NULL) { + this.waypointIndex = -1; } - if (dir === -1) { - this.waypointIndex--; - if (this.waypointIndex != -1) { - return this.validateAndAdvanceStep(); - } + + // If no waypoints, return + if (this.waypointIndex === -1) { return -1; } - const previousX: number = this.x; - const previousZ: number = this.z; - this.x = CoordGrid.moveX(this.x, dir); - this.z = CoordGrid.moveZ(this.z, dir); - const moveX: number = CoordGrid.moveX(this.x, dir); - const moveZ: number = CoordGrid.moveZ(this.z, dir); - this.focus(CoordGrid.fine(moveX, this.width), CoordGrid.fine(moveZ, this.length), false); - this.stepsTaken++; - this.refreshZonePresence(previousX, previousZ, this.level); + // If we have a valid collision strategy and extra flag, attempt to take step. + const delta: [number, number] = this.takeStep(); + + const srcX = this.x; + const srcZ = this.z; + + // Move entity + this.x = this.x + delta[0]; + this.z = this.z + delta[1]; + + // Refresh zone presence if we had a waypoint, even if we didn't move + this.refreshZonePresence(srcX, srcZ, this.level); + + // Update waypoint index if we reached the current waypoint if (this.waypointIndex !== -1) { const coord: CoordGrid = CoordGrid.unpackCoord(this.waypoints[this.waypointIndex]); if (coord.x === this.x && coord.z === this.z) { @@ -231,7 +235,17 @@ export default abstract class PathingEntity extends Entity { } } - return dir; + // If we actually moved, update orientation and steps taken. + if (this.x !== srcX || this.z !== srcZ) { + // Focus the tile in front + const focusX: number = this.x + delta[0]; + const focusZ: number = this.z + delta[1]; + this.focus(CoordGrid.fine(focusX, this.width), CoordGrid.fine(focusZ, this.length), false); + this.stepsTaken++; + return CoordGrid.face(srcX, srcZ, this.x, this.z); + } + + return -1; } /** @@ -242,6 +256,7 @@ export default abstract class PathingEntity extends Entity { queueWaypoint(x: number, z: number): void { this.waypoints[0] = CoordGrid.packCoord(0, x, z); // level doesn't matter here this.waypointIndex = 0; + this.setAllowRepath(AllowRepath.BEFOREDEST); } /** @@ -255,6 +270,11 @@ export default abstract class PathingEntity extends Entity { index++; } this.waypointIndex = index; + this.setAllowRepath(AllowRepath.BEFOREDEST); + } + + setAllowRepath(value: AllowRepath) { + this.allowRepath = value; } clearWaypoints(): void { @@ -374,7 +394,7 @@ export default abstract class PathingEntity extends Entity { /* * Returns if this PathingEntity is at the last waypoint or has no waypoint. */ - isLastOrNoWaypoint(): boolean { + isLastWaypoint(): boolean { return this.waypointIndex <= 0; } @@ -408,53 +428,31 @@ export default abstract class PathingEntity extends Entity { return CoordGrid.distanceTo(this, target) <= range && isApproached(this.level, this.x, this.z, target.x, target.z, this.width, this.length, target.width, target.length); } - pathToMoveClick(input: number[], needsfinding: boolean): void { - if (this.moveStrategy === MoveStrategy.SMART) { - if (needsfinding) { - const { x, z } = CoordGrid.unpackCoord(input[0]); - this.queueWaypoints(findPath(this.level, this.x, this.z, x, z)); - } else { - this.queueWaypoints(input); - } + randomWalk(): void { + let x = this.x, + z = this.z; + if (Math.random() < 0.5) { + x += Math.random() < 0.5 ? -1 : 1; } else { - const { x, z } = CoordGrid.unpackCoord(input[input.length - 1]); - this.queueWaypoint(x, z); + z += Math.random() < 0.5 ? -1 : 1; } + this.queueWaypoint(x, z); } - pathToPathingTarget(): void { + naivePathToTarget() { if (!this.target) { return; } - - if (!(this.target instanceof PathingEntity)) { - this.pathToTarget(); - return; + let angle = 0; + if (this.target instanceof Loc) { + angle = this.target.angle; } + const waypoints = findNaivePath(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.length, this.target.width, this.target.length, angle, CollisionType.NORMAL); - if ( - !(this.targetOp === ServerTriggerType.APPLAYER3 || this.targetOp === ServerTriggerType.OPPLAYER3) && - Environment.NODE_CLIENT_ROUTEFINDER && - CoordGrid.intersects(this.x, this.z, this.width, this.length, this.target.x, this.target.z, this.target.width, this.target.length) - ) { - this.queueWaypoints(findNaivePath(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.length, this.target.width, this.target.length, 0, CollisionType.NORMAL)); - return; + const { x, z } = CoordGrid.unpackCoord(waypoints[0]); + if (x !== this.x || z !== this.z) { + this.queueWaypoints(waypoints); } - - if (!this.isLastOrNoWaypoint()) { - return; - } - - if (this.targetOp === ServerTriggerType.APPLAYER3 || this.targetOp === ServerTriggerType.OPPLAYER3) { - this.queueWaypoint(this.target.followX, this.target.followZ); - return; - } - - /*if (this.targetX === this.target.x && this.targetZ === this.target.z && !Position.intersects(this.x, this.z, this.width, this.length, this.target.x, this.target.z, this.target.width, this.target.length)) { - return; - }*/ - - this.pathToTarget(); } pathToTarget(): void { @@ -464,11 +462,7 @@ export default abstract class PathingEntity extends Entity { if (this.moveStrategy === MoveStrategy.SMART) { if (this.target instanceof PathingEntity) { - if (Environment.NODE_CLIENT_ROUTEFINDER && CoordGrid.intersects(this.x, this.z, this.width, this.length, this.target.x, this.target.z, this.target.width, this.target.length)) { - this.queueWaypoints(findNaivePath(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.length, this.target.width, this.target.length, 0, CollisionType.NORMAL)); - } else { - this.queueWaypoints(findPathToEntity(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.target.width, this.target.length)); - } + this.queueWaypoints(findPathToEntity(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.target.width, this.target.length)); } else if (this.target instanceof Loc) { const forceapproach = LocType.get(this.target.type).forceapproach; this.queueWaypoints(findPathToLoc(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.target.width, this.target.length, this.target.angle, this.target.shape, forceapproach)); @@ -490,7 +484,7 @@ export default abstract class PathingEntity extends Entity { return; } if (this.target instanceof PathingEntity) { - this.queueWaypoints(findNaivePath(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.length, this.target.width, this.target.length, extraFlag, collisionStrategy)); + this.naivePathToTarget(); } else { this.queueWaypoint(this.target.x, this.target.z); } @@ -548,6 +542,11 @@ export default abstract class PathingEntity extends Entity { this.targetSubject.type = -1; } + if (interaction === Interaction.SCRIPT) { + // Allow repath + this.allowRepath = AllowRepath.BEFOREDEST; + } + this.focus(CoordGrid.fine(target.x, target.width), CoordGrid.fine(target.z, target.length), target instanceof NonPathingEntity && interaction === Interaction.ENGINE); if (target instanceof NonPathingEntity) { @@ -625,24 +624,20 @@ export default abstract class PathingEntity extends Entity { this.setFaceEntity(); } - private takeStep(): number | null { + private takeStep(): [number, number] { // dir -1 means we reached the destination. // dir null means nothing happened if (this.waypointIndex === -1) { // failsafe check - return null; + return [0, 0]; } const collisionStrategy: CollisionType | null = this.getCollisionStrategy(); - if (collisionStrategy === null) { - // nomove moverestrict returns as null = no walking allowed. - return -1; - } - const extraFlag: CollisionFlag = this.blockWalkFlag(); - if (extraFlag === CollisionFlag.NULL) { - // nomove moverestrict returns as null = no walking allowed. - return -1; + + if (collisionStrategy === null) { + // failsafe check + return [0, 0]; } const srcX: number = this.x; @@ -650,46 +645,30 @@ export default abstract class PathingEntity extends Entity { const { x, z } = CoordGrid.unpackCoord(this.waypoints[this.waypointIndex]); - if (this.width > 1) { - const tryDirX = CoordGrid.face(srcX, 0, x, 0); - if (canTravel(this.level, srcX, srcZ, CoordGrid.deltaX(tryDirX), 0, this.width, extraFlag, collisionStrategy)) { - return tryDirX; - } - const tryDirZ = CoordGrid.face(0, srcZ, 0, z); - if (canTravel(this.level, srcX, srcZ, 0, CoordGrid.deltaZ(tryDirZ), this.width, extraFlag, collisionStrategy)) { - return tryDirZ; - } - return null; - } - const dir: number = CoordGrid.face(srcX, srcZ, x, z); const dx: number = CoordGrid.deltaX(dir); const dz: number = CoordGrid.deltaZ(dir); - // check if moved off current pos. - if (dx == 0 && dz == 0) { - return -1; - } - + // Noclip stuff, I guess. God mode if (this.moveStrategy === MoveStrategy.FLY) { - return dir; + return [dx, dz]; } - // check current direction if can travel to chosen dest. - if (canTravel(this.level, this.x, this.z, dx, dz, this.width, extraFlag, collisionStrategy)) { - return dir; + // Move diagonal + if (this.width === 1 && canTravel(this.level, this.x, this.z, dx, dz, this.width, extraFlag, collisionStrategy)) { + return [dx, dz]; } - // check another direction if can travel to chosen dest on current z-axis. + // Move E/W if (dx != 0 && canTravel(this.level, this.x, this.z, dx, 0, this.width, extraFlag, collisionStrategy)) { - return CoordGrid.face(srcX, srcZ, x, srcZ); + return [dx, 0]; } - // check another direction if can travel to chosen dest on current x-axis. + // Move N/S if (dz != 0 && canTravel(this.level, this.x, this.z, 0, dz, this.width, extraFlag, collisionStrategy)) { - return CoordGrid.face(srcX, srcZ, srcX, z); + return [0, dz]; } // https://x.com/JagexAsh/status/1727609489954664502 - return null; + return [0, 0]; } } diff --git a/src/engine/entity/Player.ts b/src/engine/entity/Player.ts index ae5b07ea8..45d80bb83 100644 --- a/src/engine/entity/Player.ts +++ b/src/engine/entity/Player.ts @@ -1,7 +1,7 @@ import 'dotenv/config'; import { PlayerInfoProt, Visibility } from '@2004scape/rsbuf'; -import { CollisionType, CollisionFlag } from '@2004scape/rsmod-pathfinder'; +import { CollisionFlag, CollisionType } from '@2004scape/rsmod-pathfinder'; import Component from '#/cache/config/Component.js'; import FontType from '#/cache/config/FontType.js'; @@ -24,6 +24,7 @@ import { EntityTimer, PlayerTimerType } from '#/engine/entity/EntityTimer.js'; import HeroPoints from '#/engine/entity/HeroPoints.js'; import Loc from '#/engine/entity/Loc.js'; import { ModalState } from '#/engine/entity/ModalState.js'; +import { AllowRepath } from './AllowRepath.js'; import { MoveRestrict } from '#/engine/entity/MoveRestrict.js'; import { MoveSpeed } from '#/engine/entity/MoveSpeed.js'; import { MoveStrategy } from '#/engine/entity/MoveStrategy.js'; @@ -422,7 +423,7 @@ export default class Player extends PathingEntity { EntityLifeCycle.FOREVER, MoveRestrict.NORMAL, BlockWalk.NPC, - MoveStrategy.SMART, + Environment.NODE_CLIENT_ROUTEFINDER ? MoveStrategy.NAIVE : MoveStrategy.SMART, PlayerInfoProt.FACE_COORD, PlayerInfoProt.FACE_ENTITY ); @@ -1058,7 +1059,7 @@ export default class Player extends PathingEntity { return; } - if (this.isLastOrNoWaypoint() && (this.targetOp === ServerTriggerType.APPLAYER3 || this.targetOp === ServerTriggerType.OPPLAYER3)) { + if (this.isLastWaypoint() && (this.targetOp === ServerTriggerType.APPLAYER3 || this.targetOp === ServerTriggerType.OPPLAYER3)) { this.queueWaypoint(this.target.followX, this.target.followZ); return; } @@ -1067,12 +1068,39 @@ export default class Player extends PathingEntity { return; } - if (Environment.NODE_CLIENT_ROUTEFINDER && CoordGrid.intersects(this.x, this.z, this.width, this.length, this.target.x, this.target.z, this.target.width, this.target.length)) { - this.queueWaypoints(findNaivePath(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.length, this.target.width, this.target.length, 0, CollisionType.NORMAL)); + // Different mechanics for naive and smart paths + if (this.moveStrategy === MoveStrategy.NAIVE) { + // This logic is redundant with some stuff in pathToTarget and findNaivePath, + // But for maintainability it's nice to split it out... It's pretty hard to match correct mechanics + const underTarget = CoordGrid.intersects(this.x, this.z, this.width, this.length, this.target.x, this.target.z, this.target.width, this.target.length); + if (underTarget) { + this.randomWalk(); + return; + } + + if (this.isLastWaypoint() && this.allowRepath === AllowRepath.BEFOREDEST) { + this.naivePathToTarget(); + } + } else if (this.isLastWaypoint()) { + this.pathToTarget(); + } + } + + naivePathToTarget() { + if (!this.target) { return; } - if (this.isLastOrNoWaypoint()) { - this.pathToTarget(); + let angle = 0; + if (this.target instanceof Loc) { + angle = this.target.angle; + } + + const { x, z } = CoordGrid.unpackCoord(this.waypoints[0]); + + // If no waypoint, or waypoint is further than 1 tile from target, set new dest + if (this.waypointIndex === -1 || Math.abs(this.target.x - x) > 1 || Math.abs(this.target.z - z) > 1) { + const waypoints = findNaivePath(this.level, this.x, this.z, this.target.x, this.target.z, this.width, this.length, this.target.width, this.target.length, angle, CollisionType.NORMAL); + this.queueWaypoints(waypoints); } } @@ -1237,8 +1265,7 @@ export default class Player extends PathingEntity { return; } - // Run the optrigger, but applayer3 should not run this - if (!followOp) { + if (Environment.NODE_CLIENT_ROUTEFINDER && !followOp) { this.processWalktrigger(); } @@ -1261,13 +1288,12 @@ export default class Player extends PathingEntity { } this.updateMovement(); - // If there's a target and p_access is available, try to interact after moving if (this.target && this.canAccess() && !followOp) { interacted = this.tryInteract(this.stepsTaken === 0); // If Player did not interact, has no path, and did not move this cycle, terminate the interaction - if (!interacted && !this.hasWaypoints() && this.stepsTaken === 0) { + if (!interacted && !this.apRangeCalled && !this.hasWaypoints() && this.stepsTaken === 0) { this.messageGame("I can't reach that!"); this.clearInteraction(); } @@ -1959,6 +1985,7 @@ export default class Player extends PathingEntity { this.focus(CoordGrid.fine(x, 1), CoordGrid.fine(z, 1), true); } + // todo: make compiler do this at pack time playSong(id: number) { this.write(new MidiSong(id)); } diff --git a/src/network/game/client/handler/MoveClickHandler.ts b/src/network/game/client/handler/MoveClickHandler.ts index 20e39d2ea..fafca4360 100644 --- a/src/network/game/client/handler/MoveClickHandler.ts +++ b/src/network/game/client/handler/MoveClickHandler.ts @@ -1,10 +1,12 @@ import { CoordGrid } from '#/engine/CoordGrid.js'; import { NetworkPlayer } from '#/engine/entity/NetworkPlayer.js'; import ClientGameMessageHandler from '#/network/game/client/ClientGameMessageHandler.js'; +import { AllowRepath } from '#/engine/entity/AllowRepath.js'; import MoveClick from '#/network/game/client/model/MoveClick.js'; import UnsetMapFlag from '#/network/game/server/model/UnsetMapFlag.js'; import Environment from '#/util/Environment.js'; -import { WalkTriggerSetting } from '#/engine/entity/WalkTriggerSetting.js'; + +import { findPath } from '#/engine/GameMap.js'; export default class MoveClickHandler extends ClientGameMessageHandler { handle(message: MoveClick, player: NetworkPlayer): boolean { @@ -14,44 +16,41 @@ export default class MoveClickHandler extends ClientGameMessageHandler 1 || CoordGrid.distanceToSW(player, { x: start.x, z: start.z }) > 104) { player.unsetMapFlag(); player.userPath = []; return false; } + // Clear previous interaction + player.clearPendingAction(); + + // Handle ctrl run + if (player.runenergy < 100 && message.ctrlHeld === 1) { + player.tempRun = 0; + } else { + player.tempRun = message.ctrlHeld; + } + + // Set new path if (Environment.NODE_CLIENT_ROUTEFINDER) { + player.userPath = []; + // this check ignores setting the path when the player is clicking on their current tile if (message.path.length === 1 && start.x === player.x && start.z === player.z) { - // this check ignores setting the path when the player is clicking on their current tile - player.userPath = []; + player.queueWaypoints(player.userPath); + player.setAllowRepath(AllowRepath.NONE); } else { - player.userPath = []; - for (let i = 0; i < message.path.length; i++) { player.userPath[i] = CoordGrid.packCoord(player.level, message.path[i].x, message.path[i].z); } + player.queueWaypoints(player.userPath); } + player.processWalktrigger(); } else { const dest = message.path[message.path.length - 1]; - player.userPath = [CoordGrid.packCoord(player.level, dest.x, dest.z)]; - } - - if (Environment.NODE_WALKTRIGGER_SETTING === WalkTriggerSetting.PLAYERPACKET) { - player.pathToMoveClick(player.userPath, !Environment.NODE_CLIENT_ROUTEFINDER); - } - - if (!message.opClick) { - player.clearPendingAction(); - - if (player.runenergy < 100 && message.ctrlHeld === 1) { - player.tempRun = 0; - } else { - player.tempRun = message.ctrlHeld; - } - - if (Environment.NODE_WALKTRIGGER_SETTING === WalkTriggerSetting.PLAYERPACKET && player.hasWaypoints()) { - player.processWalktrigger(); - } + player.queueWaypoints(findPath(player.level, player.x, player.z, dest.x, dest.z)); } return true;