From aacdfe3eae696f10fce196ead4e2a106b9756931 Mon Sep 17 00:00:00 2001 From: Lorenzune Date: Sat, 4 Apr 2026 15:58:57 +0200 Subject: [PATCH] feat/wired-improvements-apr04 --- packages/room/src/RoomMessageHandler.ts | 78 ++++++++---- .../src/object/logic/MovingObjectLogic.ts | 113 +++++++++++++++++- 2 files changed, 168 insertions(+), 23 deletions(-) diff --git a/packages/room/src/RoomMessageHandler.ts b/packages/room/src/RoomMessageHandler.ts index 3acf68a..b97c6b6 100644 --- a/packages/room/src/RoomMessageHandler.ts +++ b/packages/room/src/RoomMessageHandler.ts @@ -31,7 +31,7 @@ export class RoomMessageHandler private _planeParser = new RoomPlaneParser(); private _latestEntryTileEvent: RoomEntryTileMessageEvent = null; private _messageEvents: IMessageEvent[] = []; - private _activeWiredUserMovements = new Map(); + private _activeWiredUserMovements = new Map(); private _activeRoomUserWalks = new Map(); private _activeConfInvisHiddenItemIds = new Set(); private _confInvisReapplyTimeouts: ReturnType[] = []; @@ -618,6 +618,9 @@ export class RoomMessageHandler { this._activeWiredUserMovements.set(movement.id, { expiresAt: Date.now() + Math.max(movement.duration, 1) + RoomMessageHandler.WIRED_MOVEMENT_STATUS_GRACE, + sourceX: movement.location.x, + sourceY: movement.location.y, + sourceZ: movement.location.z, targetX: movement.targetLocation.x, targetY: movement.targetLocation.y, targetZ: movement.targetLocation.z @@ -637,7 +640,8 @@ export class RoomMessageHandler return false; } - if(this.shouldReleaseWiredStatusLocation(status, activeMovement)) + if(this.shouldDiscardWiredStatusLocation(status, activeMovement) + || !!this.getMatchedWiredStatusTargetLocation(status, activeMovement)) { this._activeWiredUserMovements.delete(status.id); @@ -655,42 +659,74 @@ export class RoomMessageHandler if(activeMovement.expiresAt <= Date.now()) return null; - if(!this.shouldReleaseWiredStatusLocation(status, activeMovement)) return null; + if(this.shouldDiscardWiredStatusLocation(status, activeMovement)) return null; - return new Vector3d(activeMovement.targetX, activeMovement.targetY, activeMovement.targetZ); + return this.getMatchedWiredStatusTargetLocation(status, activeMovement); } - private shouldReleaseWiredStatusLocation(status: RoomUnitStatusMessage, activeMovement: { expiresAt: number, targetX: number, targetY: number, targetZ: number }): boolean + private getMatchedWiredStatusTargetLocation(status: RoomUnitStatusMessage, activeMovement: { expiresAt: number, sourceX: number, sourceY: number, sourceZ: number, targetX: number, targetY: number, targetZ: number }): IVector3D + { + if(this.matchesWiredMovementTarget(status.x, status.y, (status.z + status.height), activeMovement)) + { + return new Vector3d(activeMovement.targetX, activeMovement.targetY, activeMovement.targetZ); + } + + if(status.didMove && this.matchesWiredMovementTarget(status.targetX, status.targetY, status.targetZ, activeMovement)) + { + return new Vector3d(activeMovement.targetX, activeMovement.targetY, activeMovement.targetZ); + } + + return null; + } + + private shouldDiscardWiredStatusLocation(status: RoomUnitStatusMessage, activeMovement: { expiresAt: number, sourceX: number, sourceY: number, sourceZ: number, targetX: number, targetY: number, targetZ: number }): boolean { if(!status.didMove) { - return this.matchesWiredMovementTarget(status.x, status.y, (status.z + status.height), activeMovement); + return !this.matchesWiredMovementSource(status.x, status.y, (status.z + status.height), activeMovement) + && !this.matchesWiredMovementTarget(status.x, status.y, (status.z + status.height), activeMovement); } - return !this.matchesWiredMovementTarget(status.targetX, status.targetY, status.targetZ, activeMovement); + return !this.matchesWiredMovementSource(status.x, status.y, (status.z + status.height), activeMovement) + && !this.matchesWiredMovementTarget(status.x, status.y, (status.z + status.height), activeMovement) + && !this.matchesWiredMovementSource(status.targetX, status.targetY, status.targetZ, activeMovement) + && !this.matchesWiredMovementTarget(status.targetX, status.targetY, status.targetZ, activeMovement); } - private matchesWiredMovementTarget(x: number, y: number, z: number, activeMovement: { expiresAt: number, targetX: number, targetY: number, targetZ: number }): boolean + private matchesWiredMovementSource(x: number, y: number, z: number, activeMovement: { expiresAt: number, sourceX: number, sourceY: number, sourceZ: number, targetX: number, targetY: number, targetZ: number }): boolean { if(!activeMovement) return false; - return ((x === activeMovement.targetX) - && (y === activeMovement.targetY) - && (Math.abs(z - activeMovement.targetZ) <= RoomMessageHandler.WIRED_MOVEMENT_Z_EPSILON)); + return this.matchesWiredMovementLocation(x, y, z, activeMovement.sourceX, activeMovement.sourceY, activeMovement.sourceZ); + } + + private matchesWiredMovementTarget(x: number, y: number, z: number, activeMovement: { expiresAt: number, sourceX: number, sourceY: number, sourceZ: number, targetX: number, targetY: number, targetZ: number }): boolean + { + if(!activeMovement) return false; + + return this.matchesWiredMovementLocation(x, y, z, activeMovement.targetX, activeMovement.targetY, activeMovement.targetZ); + } + + private matchesWiredMovementLocation(x: number, y: number, z: number, movementX: number, movementY: number, movementZ: number): boolean + { + return ((x === movementX) + && (y === movementY) + && (Math.abs(z - movementZ) <= RoomMessageHandler.WIRED_MOVEMENT_Z_EPSILON)); } private applyWiredUserDirectionUpdate(update: WiredUserDirectionUpdateData): void { - this._roomEngine.updateRoomObjectUserLocation( - this._currentRoomId, - update.id, - null, - null, - false, - 0, - new Vector3d(update.bodyDirection), - update.headDirection, - true); + const userObject = this._roomEngine.getRoomObjectUser(this._currentRoomId, update.id); + + if(!userObject) return; + + userObject.setDirection(new Vector3d(update.bodyDirection)); + + const model = userObject.model; + + if(!model) return; + + model.setValue(RoomObjectVariable.HEAD_DIRECTION, update.headDirection); } private onObjectsDataUpdateEvent(event: ObjectsDataUpdateEvent): void diff --git a/packages/room/src/object/logic/MovingObjectLogic.ts b/packages/room/src/object/logic/MovingObjectLogic.ts index 2d90134..9507ea3 100644 --- a/packages/room/src/object/logic/MovingObjectLogic.ts +++ b/packages/room/src/object/logic/MovingObjectLogic.ts @@ -6,6 +6,7 @@ import { RoomObjectLogicBase } from './RoomObjectLogicBase'; export class MovingObjectLogic extends RoomObjectLogicBase { public static DEFAULT_UPDATE_INTERVAL: number = 500; + private static LOCATION_EPSILON: number = 0.01; private static TEMP_VECTOR: Vector3d = new Vector3d(); private _liftAmount: number; @@ -17,6 +18,7 @@ export class MovingObjectLogic extends RoomObjectLogicBase private _lastUpdateTime: number; private _changeTime: number; private _updateInterval: number; + private _queuedMoveMessages: ObjectMoveUpdateMessage[]; constructor() { @@ -31,11 +33,13 @@ export class MovingObjectLogic extends RoomObjectLogicBase this._lastUpdateTime = 0; this._changeTime = 0; this._updateInterval = MovingObjectLogic.DEFAULT_UPDATE_INTERVAL; + this._queuedMoveMessages = []; } public dispose(): void { this._liftAmount = 0; + this._queuedMoveMessages = []; super.dispose(); } @@ -46,6 +50,7 @@ export class MovingObjectLogic extends RoomObjectLogicBase const locationOffset = this.getLocationOffset(); const model = this.object && this.object.model; + let completedInterpolation = false; if(model) { @@ -111,10 +116,13 @@ export class MovingObjectLogic extends RoomObjectLogicBase this._locationDelta.x = 0; this._locationDelta.y = 0; this._locationDelta.z = 0; + completedInterpolation = true; } } this._lastUpdateTime = this.time; + + if(completedInterpolation) this.processQueuedMoveMessage(); } public setObject(object: IRoomObjectController): void @@ -130,6 +138,17 @@ export class MovingObjectLogic extends RoomObjectLogicBase if(message instanceof ObjectMoveUpdateMessage) { + if(this.shouldApplyInstantMoveMessage(message)) + { + super.processUpdateMessage(message); + + if(message.location) this._location.assign(message.location); + + this.resetInterpolationState(); + + return; + } + const requiresCustomMoveHandling = !!message.anchorObject || (message.elapsed > 0); if(requiresCustomMoveHandling) @@ -147,15 +166,30 @@ export class MovingObjectLogic extends RoomObjectLogicBase if(message instanceof ObjectMoveUpdateMessage) return this.processMoveMessage(message); } + private shouldApplyInstantMoveMessage(message: ObjectMoveUpdateMessage): boolean + { + if(!message || !message.location || message.isSlide || !!message.anchorObject || (message.elapsed > 0)) return false; + + return this.matchesLocation(message.location, message.targetLocation); + } + private processMoveMessage(message: ObjectMoveUpdateMessage): void { if(!message || !this.object || !message.location) return; + if(this.shouldQueueMoveMessage(message)) + { + this.queueMoveMessage(message); + + return; + } + const hadActiveInterpolation = this.isInterpolating(); + const duration = ((message.duration > 0) ? message.duration : ObjectMoveUpdateMessage.DEFAULT_DURATION); const startLocation = hadActiveInterpolation ? this.object.getLocation() : message.location; - const elapsed = Math.max(0, Math.min(message.duration, message.elapsed)); + const elapsed = Math.max(0, Math.min(duration, message.elapsed)); this._location.assign(startLocation); this.object.setLocation(this._location); @@ -165,7 +199,7 @@ export class MovingObjectLogic extends RoomObjectLogicBase else this._followOffset.assign(new Vector3d()); this._changeTime = (this._lastUpdateTime - elapsed); - this.updateInterval = message.duration; + this.updateInterval = duration; this._locationDelta.assign(message.targetLocation); this._locationDelta.subtract(this._location); @@ -203,11 +237,86 @@ export class MovingObjectLogic extends RoomObjectLogicBase } } + private resetInterpolationState(): void + { + this._locationDelta.x = 0; + this._locationDelta.y = 0; + this._locationDelta.z = 0; + this._followObject = null; + this._followOffset.assign(new Vector3d()); + this._queuedMoveMessages = []; + this._changeTime = this._lastUpdateTime; + } + private isInterpolating(): boolean { return (this._locationDelta.length > 0) && ((this.time - this._changeTime) < this._updateInterval); } + private shouldQueueMoveMessage(message: ObjectMoveUpdateMessage): boolean + { + if(!message.isSlide || !!message.anchorObject || !this.isInterpolating() || !message.location || !message.targetLocation) return false; + + const expectedStartLocation = this.getQueuedMovementTailLocation(); + + if(!expectedStartLocation) return false; + + return this.matchesLocation(message.location, expectedStartLocation) + && !this.matchesLocation(message.targetLocation, expectedStartLocation); + } + + private queueMoveMessage(message: ObjectMoveUpdateMessage): void + { + this._queuedMoveMessages.push(new ObjectMoveUpdateMessage( + message.location, + message.targetLocation, + message.direction, + message.isSlide, + message.duration, + message.elapsed, + message.anchorObject, + message.anchorOffset)); + } + + private processQueuedMoveMessage(): void + { + if(!this._queuedMoveMessages.length) return; + + const nextMoveMessage = this._queuedMoveMessages.shift(); + + if(!nextMoveMessage) return; + + this.processMoveMessage(nextMoveMessage); + } + + private getQueuedMovementTailLocation(): IVector3D + { + if(this._queuedMoveMessages.length) + { + const queuedMoveMessage = this._queuedMoveMessages[this._queuedMoveMessages.length - 1]; + + if(queuedMoveMessage?.targetLocation) return queuedMoveMessage.targetLocation; + } + + if(this._locationDelta.length <= 0) return null; + + const targetLocation = new Vector3d(); + + targetLocation.assign(this._location); + targetLocation.add(this._locationDelta); + + return targetLocation; + } + + private matchesLocation(first: IVector3D, second: IVector3D): boolean + { + if(!first || !second) return false; + + return (Math.abs(first.x - second.x) <= MovingObjectLogic.LOCATION_EPSILON) + && (Math.abs(first.y - second.y) <= MovingObjectLogic.LOCATION_EPSILON) + && (Math.abs(first.z - second.z) <= MovingObjectLogic.LOCATION_EPSILON); + } + protected getLocationOffset(): IVector3D { return null;