@rpgjs/server 5.0.0-alpha.15 → 5.0.0-alpha.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +293 -37
- package/dist/index.js.map +1 -1
- package/dist/rooms/map.d.ts +10 -2
- package/package.json +3 -3
- package/src/Player/MoveManager.ts +1 -1
- package/src/Player/Player.ts +4 -1
- package/src/rooms/map.ts +14 -13
package/dist/index.js
CHANGED
|
@@ -11349,10 +11349,10 @@ __decorateClass$3([
|
|
|
11349
11349
|
sync()
|
|
11350
11350
|
], RpgCommonPlayer.prototype, "type");
|
|
11351
11351
|
__decorateClass$3([
|
|
11352
|
-
|
|
11352
|
+
sync()
|
|
11353
11353
|
], RpgCommonPlayer.prototype, "x");
|
|
11354
11354
|
__decorateClass$3([
|
|
11355
|
-
|
|
11355
|
+
sync()
|
|
11356
11356
|
], RpgCommonPlayer.prototype, "y");
|
|
11357
11357
|
__decorateClass$3([
|
|
11358
11358
|
sync()
|
|
@@ -12545,6 +12545,48 @@ class Entity {
|
|
|
12545
12545
|
this.force.set(0, 0);
|
|
12546
12546
|
this.torque = 0;
|
|
12547
12547
|
}
|
|
12548
|
+
/**
|
|
12549
|
+
* Stops all movement immediately
|
|
12550
|
+
*
|
|
12551
|
+
* Completely stops the entity's movement by:
|
|
12552
|
+
* - Setting velocity to zero
|
|
12553
|
+
* - Setting angular velocity to zero
|
|
12554
|
+
* - Clearing accumulated forces and torques
|
|
12555
|
+
* - Waking up the entity if it was sleeping
|
|
12556
|
+
* - Notifying movement state change
|
|
12557
|
+
*
|
|
12558
|
+
* Unlike `freeze()`, this method keeps the entity dynamic and does not
|
|
12559
|
+
* change its state. It's useful for stopping movement when changing maps,
|
|
12560
|
+
* teleporting, or when you need to halt an entity without making it static.
|
|
12561
|
+
*
|
|
12562
|
+
* @returns This entity for chaining
|
|
12563
|
+
*
|
|
12564
|
+
* @example
|
|
12565
|
+
* ```ts
|
|
12566
|
+
* // Stop movement when changing maps
|
|
12567
|
+
* if (mapChanged) {
|
|
12568
|
+
* entity.stopMovement();
|
|
12569
|
+
* }
|
|
12570
|
+
*
|
|
12571
|
+
* // Stop movement after teleporting
|
|
12572
|
+
* entity.position.set(100, 200);
|
|
12573
|
+
* entity.stopMovement();
|
|
12574
|
+
*
|
|
12575
|
+
* // Stop movement when player dies
|
|
12576
|
+
* if (player.isDead()) {
|
|
12577
|
+
* playerEntity.stopMovement();
|
|
12578
|
+
* }
|
|
12579
|
+
* ```
|
|
12580
|
+
*/
|
|
12581
|
+
stopMovement() {
|
|
12582
|
+
this.velocity.set(0, 0);
|
|
12583
|
+
this.angularVelocity = 0;
|
|
12584
|
+
this.clearForces();
|
|
12585
|
+
this.wakeUp();
|
|
12586
|
+
this.notifyMovementChange();
|
|
12587
|
+
this.notifyDirectionChange();
|
|
12588
|
+
return this;
|
|
12589
|
+
}
|
|
12548
12590
|
/**
|
|
12549
12591
|
* Clamps velocities to maximum values
|
|
12550
12592
|
*/
|
|
@@ -14232,6 +14274,11 @@ class CollisionResolver {
|
|
|
14232
14274
|
/**
|
|
14233
14275
|
* Separates two entities by moving them apart
|
|
14234
14276
|
*
|
|
14277
|
+
* This method applies position corrections to resolve penetration between
|
|
14278
|
+
* colliding entities. After applying corrections, it notifies position change
|
|
14279
|
+
* handlers to ensure proper synchronization with game logic (e.g., updating
|
|
14280
|
+
* owner.x/y signals for network sync).
|
|
14281
|
+
*
|
|
14235
14282
|
* @param entityA - First entity
|
|
14236
14283
|
* @param entityB - Second entity
|
|
14237
14284
|
* @param normal - Separation normal (from A to B)
|
|
@@ -14250,9 +14297,11 @@ class CollisionResolver {
|
|
|
14250
14297
|
const correctionB = normal.mul(correction * (entityB.invMass / totalInvMass));
|
|
14251
14298
|
if (!entityA.isStatic()) {
|
|
14252
14299
|
entityA.position.addInPlace(correctionA);
|
|
14300
|
+
entityA.notifyPositionChange();
|
|
14253
14301
|
}
|
|
14254
14302
|
if (!entityB.isStatic()) {
|
|
14255
14303
|
entityB.position.addInPlace(correctionB);
|
|
14304
|
+
entityB.notifyPositionChange();
|
|
14256
14305
|
}
|
|
14257
14306
|
}
|
|
14258
14307
|
/**
|
|
@@ -15404,6 +15453,47 @@ let MovementManager$1 = class MovementManager {
|
|
|
15404
15453
|
const body = this.resolveTarget(target);
|
|
15405
15454
|
this.entries.delete(body.id);
|
|
15406
15455
|
}
|
|
15456
|
+
/**
|
|
15457
|
+
* Stops all movement for an entity immediately
|
|
15458
|
+
*
|
|
15459
|
+
* This method completely stops an entity's movement by:
|
|
15460
|
+
* - Removing all active movement strategies (dash, linear moves, etc.)
|
|
15461
|
+
* - Stopping the entity's velocity and angular velocity
|
|
15462
|
+
* - Clearing accumulated forces
|
|
15463
|
+
* - Waking up the entity if it was sleeping
|
|
15464
|
+
*
|
|
15465
|
+
* This is useful when changing maps, teleporting, or when you need
|
|
15466
|
+
* to halt an entity's movement completely without making it static.
|
|
15467
|
+
*
|
|
15468
|
+
* @param target - Entity, MovementBody, or identifier
|
|
15469
|
+
*
|
|
15470
|
+
* @example
|
|
15471
|
+
* ```ts
|
|
15472
|
+
* // Stop movement when changing maps
|
|
15473
|
+
* if (mapChanged) {
|
|
15474
|
+
* movement.stopMovement(playerEntity);
|
|
15475
|
+
* }
|
|
15476
|
+
*
|
|
15477
|
+
* // Stop movement after teleporting
|
|
15478
|
+
* entity.position.set(100, 200);
|
|
15479
|
+
* movement.stopMovement(entity);
|
|
15480
|
+
*
|
|
15481
|
+
* // Stop movement when player dies
|
|
15482
|
+
* if (player.isDead()) {
|
|
15483
|
+
* movement.stopMovement(playerEntity);
|
|
15484
|
+
* }
|
|
15485
|
+
* ```
|
|
15486
|
+
*/
|
|
15487
|
+
stopMovement(target) {
|
|
15488
|
+
const body = this.resolveTarget(target);
|
|
15489
|
+
this.clear(target);
|
|
15490
|
+
if ("getEntity" in body && typeof body.getEntity === "function") {
|
|
15491
|
+
const entity = body.getEntity();
|
|
15492
|
+
if (entity && typeof entity.stopMovement === "function") {
|
|
15493
|
+
entity.stopMovement();
|
|
15494
|
+
}
|
|
15495
|
+
}
|
|
15496
|
+
}
|
|
15407
15497
|
/**
|
|
15408
15498
|
* Checks if an entity has active strategies.
|
|
15409
15499
|
*
|
|
@@ -17004,6 +17094,9 @@ class MovementManager {
|
|
|
17004
17094
|
clear(id) {
|
|
17005
17095
|
this.core.clear(id);
|
|
17006
17096
|
}
|
|
17097
|
+
stopMovement(id) {
|
|
17098
|
+
this.core.stopMovement(id);
|
|
17099
|
+
}
|
|
17007
17100
|
hasActiveStrategies(id) {
|
|
17008
17101
|
return this.core.hasActiveStrategies(id);
|
|
17009
17102
|
}
|
|
@@ -17037,12 +17130,33 @@ class RpgCommonMap {
|
|
|
17037
17130
|
* It's shared using the share() operator, meaning that all subscribers will receive
|
|
17038
17131
|
* events from a single interval rather than creating multiple intervals.
|
|
17039
17132
|
*
|
|
17133
|
+
* ## Physics Loop Architecture
|
|
17134
|
+
*
|
|
17135
|
+
* The physics simulation is centralized in this game loop:
|
|
17136
|
+
*
|
|
17137
|
+
* 1. **Input Processing** (`processInput`): Only updates entity velocities, does NOT step physics
|
|
17138
|
+
* 2. **Game Loop** (`tick$` -> `runFixedTicks`): Executes physics simulation with fixed timestep
|
|
17139
|
+
* 3. **Fixed Timestep Pattern**: Accumulator-based approach ensures deterministic physics
|
|
17140
|
+
*
|
|
17141
|
+
* ```
|
|
17142
|
+
* Input Events ─────────────────────────────────────────────────────────────►
|
|
17143
|
+
* │
|
|
17144
|
+
* ▼ (update velocity only)
|
|
17145
|
+
* ┌─────────────────────────────────────────────────────────────────────────┐
|
|
17146
|
+
* │ Game Loop (tick$) │
|
|
17147
|
+
* │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
|
|
17148
|
+
* │ │ updateMovements │ → │ stepOneTick │ → │ postTickUpdates │ │
|
|
17149
|
+
* │ │ (apply velocity)│ │ (physics step) │ │ (zones, sync) │ │
|
|
17150
|
+
* │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
|
|
17151
|
+
* └─────────────────────────────────────────────────────────────────────────┘
|
|
17152
|
+
* ```
|
|
17153
|
+
*
|
|
17040
17154
|
* @example
|
|
17041
17155
|
* ```ts
|
|
17042
|
-
* // Subscribe to the game tick
|
|
17043
|
-
* map.tick$.subscribe(timestamp => {
|
|
17044
|
-
* //
|
|
17045
|
-
* this.
|
|
17156
|
+
* // Subscribe to the game tick for custom updates
|
|
17157
|
+
* map.tick$.subscribe(({ delta, timestamp }) => {
|
|
17158
|
+
* // Custom game logic runs alongside physics
|
|
17159
|
+
* this.updateCustomEntities(delta);
|
|
17046
17160
|
* });
|
|
17047
17161
|
* ```
|
|
17048
17162
|
*/
|
|
@@ -17192,6 +17306,7 @@ class RpgCommonMap {
|
|
|
17192
17306
|
if (typeof player.autoChangeMap === "function") {
|
|
17193
17307
|
const mapChanged = await player.autoChangeMap({ x: nextX, y: nextY }, direction);
|
|
17194
17308
|
if (mapChanged) {
|
|
17309
|
+
this.stopMovement(player);
|
|
17195
17310
|
return;
|
|
17196
17311
|
}
|
|
17197
17312
|
}
|
|
@@ -17217,6 +17332,43 @@ class RpgCommonMap {
|
|
|
17217
17332
|
getObjectById(id) {
|
|
17218
17333
|
return this.players()[id] ?? this.events()[id];
|
|
17219
17334
|
}
|
|
17335
|
+
/**
|
|
17336
|
+
* Execute physics simulation with fixed timestep
|
|
17337
|
+
*
|
|
17338
|
+
* This method runs the physics engine using a fixed timestep accumulator pattern.
|
|
17339
|
+
* It ensures deterministic physics regardless of frame rate by:
|
|
17340
|
+
* 1. Accumulating delta time
|
|
17341
|
+
* 2. Running fixed-size physics steps until the accumulator is depleted
|
|
17342
|
+
* 3. Calling `updateMovements()` before each step to apply velocity changes
|
|
17343
|
+
* 4. Running post-tick updates (zones, callbacks) after each step
|
|
17344
|
+
*
|
|
17345
|
+
* ## Architecture
|
|
17346
|
+
*
|
|
17347
|
+
* The physics loop is centralized here and called from `tick$` subscription.
|
|
17348
|
+
* Input processing (`processInput`) only updates entity velocities - it does NOT
|
|
17349
|
+
* step the physics. This ensures:
|
|
17350
|
+
* - Consistent physics timing (60fps fixed timestep)
|
|
17351
|
+
* - No double-stepping when inputs are processed
|
|
17352
|
+
* - Proper accumulator-based interpolation support
|
|
17353
|
+
*
|
|
17354
|
+
* @param deltaMs - Time elapsed since last call in milliseconds
|
|
17355
|
+
* @param hooks - Optional callbacks for before/after each physics step
|
|
17356
|
+
* @returns Number of physics ticks executed
|
|
17357
|
+
*
|
|
17358
|
+
* @example
|
|
17359
|
+
* ```ts
|
|
17360
|
+
* // Called automatically by tick$ subscription
|
|
17361
|
+
* this.tickSubscription = this.tick$.subscribe(({ delta }) => {
|
|
17362
|
+
* this.runFixedTicks(delta);
|
|
17363
|
+
* });
|
|
17364
|
+
*
|
|
17365
|
+
* // Or manually with hooks for debugging
|
|
17366
|
+
* this.runFixedTicks(16, {
|
|
17367
|
+
* beforeStep: () => console.log('Before physics step'),
|
|
17368
|
+
* afterStep: (tick) => console.log(`Physics tick ${tick} completed`)
|
|
17369
|
+
* });
|
|
17370
|
+
* ```
|
|
17371
|
+
*/
|
|
17220
17372
|
runFixedTicks(deltaMs, hooks) {
|
|
17221
17373
|
if (!Number.isFinite(deltaMs) || deltaMs <= 0) {
|
|
17222
17374
|
return 0;
|
|
@@ -17227,12 +17379,46 @@ class RpgCommonMap {
|
|
|
17227
17379
|
while (this.physicsAccumulatorMs >= fixedStepMs) {
|
|
17228
17380
|
this.physicsAccumulatorMs -= fixedStepMs;
|
|
17229
17381
|
hooks?.beforeStep?.();
|
|
17382
|
+
this.physic.updateMovements();
|
|
17230
17383
|
const tick = this.physic.stepOneTick();
|
|
17231
17384
|
executed += 1;
|
|
17385
|
+
this.runPostTickUpdates();
|
|
17232
17386
|
hooks?.afterStep?.(tick);
|
|
17233
17387
|
}
|
|
17234
17388
|
return executed;
|
|
17235
17389
|
}
|
|
17390
|
+
/**
|
|
17391
|
+
* Force a single physics tick outside of the normal game loop
|
|
17392
|
+
*
|
|
17393
|
+
* This method is primarily used for **client-side prediction** where the client
|
|
17394
|
+
* needs to immediately simulate physics in response to local input, rather than
|
|
17395
|
+
* waiting for the next game loop tick.
|
|
17396
|
+
*
|
|
17397
|
+
* ## Use Cases
|
|
17398
|
+
*
|
|
17399
|
+
* - **Client-side prediction**: Immediately simulate player movement for responsive feel
|
|
17400
|
+
* - **Testing**: Force a physics step in unit tests
|
|
17401
|
+
* - **Special effects**: Immediate physics response for specific game events
|
|
17402
|
+
*
|
|
17403
|
+
* ## Important
|
|
17404
|
+
*
|
|
17405
|
+
* This method should NOT be used on the server for normal input processing.
|
|
17406
|
+
* Server-side physics is handled by `runFixedTicks` in the main game loop to ensure
|
|
17407
|
+
* deterministic simulation.
|
|
17408
|
+
*
|
|
17409
|
+
* @param hooks - Optional callbacks for before/after the physics step
|
|
17410
|
+
* @returns The physics tick number
|
|
17411
|
+
*
|
|
17412
|
+
* @example
|
|
17413
|
+
* ```ts
|
|
17414
|
+
* // Client-side: immediately simulate predicted movement
|
|
17415
|
+
* class RpgClientMap extends RpgCommonMap {
|
|
17416
|
+
* stepPredictionTick(): void {
|
|
17417
|
+
* this.forceSingleTick();
|
|
17418
|
+
* }
|
|
17419
|
+
* }
|
|
17420
|
+
* ```
|
|
17421
|
+
*/
|
|
17236
17422
|
forceSingleTick(hooks) {
|
|
17237
17423
|
hooks?.beforeStep?.();
|
|
17238
17424
|
this.physic.updateMovements();
|
|
@@ -17560,22 +17746,60 @@ class RpgCommonMap {
|
|
|
17560
17746
|
* Add a character to the physics world
|
|
17561
17747
|
* @private
|
|
17562
17748
|
*/
|
|
17749
|
+
/**
|
|
17750
|
+
* Add a character entity to the physics world
|
|
17751
|
+
*
|
|
17752
|
+
* Creates a physics entity for a character (player or NPC) with proper position handling.
|
|
17753
|
+
* The owner's x/y signals represent **top-left** coordinates, while the physics entity
|
|
17754
|
+
* uses **center** coordinates internally.
|
|
17755
|
+
*
|
|
17756
|
+
* ## Position System
|
|
17757
|
+
*
|
|
17758
|
+
* - `owner.x()` / `owner.y()` → **top-left** corner of the character's hitbox
|
|
17759
|
+
* - `entity.position` → **center** of the physics collider
|
|
17760
|
+
* - Conversion: `center = topLeft + (size / 2)`
|
|
17761
|
+
*
|
|
17762
|
+
* @param options - Character configuration
|
|
17763
|
+
* @returns The character's unique ID
|
|
17764
|
+
*
|
|
17765
|
+
* @example
|
|
17766
|
+
* ```ts
|
|
17767
|
+
* // Player at top-left position (100, 100) with 32x32 hitbox
|
|
17768
|
+
* // Physics entity will be at center (116, 116)
|
|
17769
|
+
* this.addCharacter({
|
|
17770
|
+
* owner: player,
|
|
17771
|
+
* x: 116, // center X (ignored, uses owner.x())
|
|
17772
|
+
* y: 116, // center Y (ignored, uses owner.y())
|
|
17773
|
+
* kind: "hero"
|
|
17774
|
+
* });
|
|
17775
|
+
* ```
|
|
17776
|
+
*
|
|
17777
|
+
* @private
|
|
17778
|
+
*/
|
|
17563
17779
|
addCharacter(options) {
|
|
17564
17780
|
if (!options || typeof options.owner?.id !== "string") {
|
|
17565
17781
|
throw new Error("Character requires an owner object with a string id");
|
|
17566
17782
|
}
|
|
17567
|
-
const
|
|
17568
|
-
const
|
|
17569
|
-
const
|
|
17570
|
-
const
|
|
17571
|
-
const
|
|
17572
|
-
const
|
|
17573
|
-
const
|
|
17783
|
+
const owner = options.owner;
|
|
17784
|
+
const id = owner.id;
|
|
17785
|
+
const hitbox = typeof owner.hitbox === "function" ? owner.hitbox() : owner.hitbox;
|
|
17786
|
+
const width = hitbox?.w ?? 32;
|
|
17787
|
+
const height = hitbox?.h ?? 32;
|
|
17788
|
+
const radius = Math.max(width, height) / 2;
|
|
17789
|
+
const topLeftX = owner.x();
|
|
17790
|
+
const topLeftY = owner.y();
|
|
17791
|
+
const centerX = topLeftX + width / 2;
|
|
17792
|
+
const centerY = topLeftY + height / 2;
|
|
17574
17793
|
const isStatic = !!options.isStatic;
|
|
17575
17794
|
const entity = this.physic.createEntity({
|
|
17576
17795
|
uuid: id,
|
|
17577
17796
|
position: { x: centerX, y: centerY },
|
|
17797
|
+
// Use radius for circular collision detection
|
|
17578
17798
|
radius: Math.max(radius, 1),
|
|
17799
|
+
// Also store explicit width/height for consistent position conversions
|
|
17800
|
+
// This ensures getBodyPosition/setBodyPosition use the same dimensions
|
|
17801
|
+
width,
|
|
17802
|
+
height,
|
|
17579
17803
|
mass: options.mass ?? (isStatic ? Infinity : 1),
|
|
17580
17804
|
friction: options.friction ?? 0.4,
|
|
17581
17805
|
linearDamping: isStatic ? 1 : 0.2,
|
|
@@ -17587,17 +17811,15 @@ class RpgCommonMap {
|
|
|
17587
17811
|
} else {
|
|
17588
17812
|
entity.unfreeze();
|
|
17589
17813
|
}
|
|
17590
|
-
entity.owner =
|
|
17814
|
+
entity.owner = owner;
|
|
17591
17815
|
entity.onDirectionChange(({ cardinalDirection }) => {
|
|
17592
17816
|
if (!("$send" in this)) return;
|
|
17593
|
-
const
|
|
17594
|
-
if (!
|
|
17817
|
+
const owner2 = entity.owner;
|
|
17818
|
+
if (!owner2) return;
|
|
17595
17819
|
if (cardinalDirection === "idle") return;
|
|
17596
|
-
|
|
17820
|
+
owner2.changeDirection(cardinalDirection);
|
|
17597
17821
|
});
|
|
17598
17822
|
entity.onMovementChange(({ isMoving, intensity }) => {
|
|
17599
|
-
const owner = entity.owner;
|
|
17600
|
-
if (!owner) return;
|
|
17601
17823
|
const LOW_INTENSITY_THRESHOLD = 10;
|
|
17602
17824
|
if (isMoving && intensity > LOW_INTENSITY_THRESHOLD) {
|
|
17603
17825
|
owner.animationName.set("walk");
|
|
@@ -17605,19 +17827,21 @@ class RpgCommonMap {
|
|
|
17605
17827
|
owner.animationName.set("stand");
|
|
17606
17828
|
}
|
|
17607
17829
|
});
|
|
17830
|
+
const entityWidth = width;
|
|
17831
|
+
const entityHeight = height;
|
|
17608
17832
|
entity.onPositionChange(({ x, y }) => {
|
|
17609
|
-
const
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
const height = entity.height || (entity.radius ? entity.radius * 2 : 32);
|
|
17613
|
-
const topLeftX2 = x - width / 2;
|
|
17614
|
-
const topLeftY2 = y - height / 2;
|
|
17833
|
+
const topLeftX2 = x - entityWidth / 2;
|
|
17834
|
+
const topLeftY2 = y - entityHeight / 2;
|
|
17835
|
+
let changed = false;
|
|
17615
17836
|
if (typeof owner.x === "function" && typeof owner.x.set === "function") {
|
|
17616
17837
|
owner.x.set(Math.round(topLeftX2));
|
|
17617
|
-
|
|
17838
|
+
changed = true;
|
|
17618
17839
|
}
|
|
17619
17840
|
if (typeof owner.y === "function" && typeof owner.y.set === "function") {
|
|
17620
17841
|
owner.y.set(Math.round(topLeftY2));
|
|
17842
|
+
changed = true;
|
|
17843
|
+
}
|
|
17844
|
+
if (changed) {
|
|
17621
17845
|
owner.applyFrames?.();
|
|
17622
17846
|
}
|
|
17623
17847
|
});
|
|
@@ -17700,15 +17924,40 @@ class RpgCommonMap {
|
|
|
17700
17924
|
}
|
|
17701
17925
|
/**
|
|
17702
17926
|
* Stop movement for a player
|
|
17927
|
+
*
|
|
17928
|
+
* Completely stops all movement for a player, including:
|
|
17929
|
+
* - Clearing all active movement strategies (dash, linear moves, etc.)
|
|
17930
|
+
* - Setting velocity to zero
|
|
17931
|
+
* - Resetting intended direction
|
|
17932
|
+
*
|
|
17933
|
+
* This method is particularly useful when changing maps to ensure
|
|
17934
|
+
* the player doesn't carry over movement from the previous map.
|
|
17935
|
+
*
|
|
17936
|
+
* @param player - The player to stop
|
|
17937
|
+
* @returns True if the player was found and movement was stopped
|
|
17938
|
+
*
|
|
17939
|
+
* @example
|
|
17940
|
+
* ```ts
|
|
17941
|
+
* // Stop player movement when changing maps
|
|
17942
|
+
* if (mapChanged) {
|
|
17943
|
+
* map.stopMovement(player);
|
|
17944
|
+
* }
|
|
17945
|
+
*
|
|
17946
|
+
* // Stop movement when player dies
|
|
17947
|
+
* if (player.isDead()) {
|
|
17948
|
+
* map.stopMovement(player);
|
|
17949
|
+
* }
|
|
17950
|
+
* ```
|
|
17703
17951
|
* @protected
|
|
17704
17952
|
*/
|
|
17705
17953
|
stopMovement(player) {
|
|
17706
17954
|
const entity = this.physic.getEntityByUUID(player.id);
|
|
17707
17955
|
if (!entity) return false;
|
|
17956
|
+
this.moveManager.stopMovement(player.id);
|
|
17708
17957
|
if (typeof player.setIntendedDirection === "function") {
|
|
17709
17958
|
player.setIntendedDirection(null);
|
|
17710
17959
|
}
|
|
17711
|
-
|
|
17960
|
+
player.pendingInputs = [];
|
|
17712
17961
|
return true;
|
|
17713
17962
|
}
|
|
17714
17963
|
/**
|
|
@@ -17808,11 +18057,11 @@ class RpgCommonMap {
|
|
|
17808
18057
|
setBodyPosition(id, x, y, mode = "center") {
|
|
17809
18058
|
const entity = this.physic.getEntityByUUID(id);
|
|
17810
18059
|
if (!entity) return;
|
|
18060
|
+
const width = entity.width || (entity.radius ? entity.radius * 2 : 32);
|
|
18061
|
+
const height = entity.height || (entity.radius ? entity.radius * 2 : 32);
|
|
17811
18062
|
let centerX = x;
|
|
17812
18063
|
let centerY = y;
|
|
17813
18064
|
if (mode === "top-left") {
|
|
17814
|
-
const width = entity.width || (entity.radius ? entity.radius * 2 : 32);
|
|
17815
|
-
const height = entity.height || (entity.radius ? entity.radius * 2 : 32);
|
|
17816
18065
|
centerX = x + width / 2;
|
|
17817
18066
|
centerY = y + height / 2;
|
|
17818
18067
|
}
|
|
@@ -19064,7 +19313,7 @@ function WithMoveManager(Base) {
|
|
|
19064
19313
|
staticTarget.freeze();
|
|
19065
19314
|
map.moveManager.add(
|
|
19066
19315
|
this.id,
|
|
19067
|
-
new SeekAvoid(engine, () => staticTarget,
|
|
19316
|
+
new SeekAvoid(engine, () => staticTarget, 80, 140, 80, 48)
|
|
19068
19317
|
);
|
|
19069
19318
|
}
|
|
19070
19319
|
/**
|
|
@@ -22376,7 +22625,10 @@ const _RpgPlayer = class _RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
22376
22625
|
async teleport(positions) {
|
|
22377
22626
|
if (!this.map) return false;
|
|
22378
22627
|
if (this.map.physic) {
|
|
22379
|
-
this.map.physic.
|
|
22628
|
+
const entity = this.map.physic.getEntityByUUID(this.id);
|
|
22629
|
+
if (entity) {
|
|
22630
|
+
this.map.physic.teleport(entity, { x: positions.x, y: positions.y });
|
|
22631
|
+
}
|
|
22380
22632
|
} else {
|
|
22381
22633
|
this.x.set(positions.x);
|
|
22382
22634
|
this.y.set(positions.y);
|
|
@@ -25957,7 +26209,6 @@ let RpgMap = class extends RpgCommonMap {
|
|
|
25957
26209
|
player._onInit();
|
|
25958
26210
|
this.dataIsReady$.pipe(
|
|
25959
26211
|
finalize$1(() => {
|
|
25960
|
-
player.applyFrames();
|
|
25961
26212
|
this.hooks.callHooks("server-player-onJoinMap", player, this).subscribe();
|
|
25962
26213
|
})
|
|
25963
26214
|
).subscribe();
|
|
@@ -26077,8 +26328,16 @@ let RpgMap = class extends RpgCommonMap {
|
|
|
26077
26328
|
* This method processes all pending inputs for a player while performing
|
|
26078
26329
|
* anti-cheat validation to prevent time manipulation and frame skipping.
|
|
26079
26330
|
* It validates the time deltas between inputs and ensures they are within
|
|
26080
|
-
* acceptable ranges.
|
|
26081
|
-
*
|
|
26331
|
+
* acceptable ranges.
|
|
26332
|
+
*
|
|
26333
|
+
* ## Architecture
|
|
26334
|
+
*
|
|
26335
|
+
* **Important**: This method only updates entity velocities - it does NOT step
|
|
26336
|
+
* the physics engine. Physics simulation is handled centrally by the game loop
|
|
26337
|
+
* (`tick$` -> `runFixedTicks`). This ensures:
|
|
26338
|
+
* - Consistent physics timing (60fps fixed timestep)
|
|
26339
|
+
* - No double-stepping when multiple inputs are processed
|
|
26340
|
+
* - Deterministic physics regardless of input frequency
|
|
26082
26341
|
*
|
|
26083
26342
|
* @param playerId - The ID of the player to process inputs for
|
|
26084
26343
|
* @param controls - Optional anti-cheat configuration
|
|
@@ -26160,7 +26419,6 @@ let RpgMap = class extends RpgCommonMap {
|
|
|
26160
26419
|
lastProcessedFrame = input.frame;
|
|
26161
26420
|
}
|
|
26162
26421
|
if (hasProcessedInputs) {
|
|
26163
|
-
this.forceSingleTick();
|
|
26164
26422
|
player.lastProcessedInputTs = lastProcessedTime;
|
|
26165
26423
|
} else {
|
|
26166
26424
|
const idleTimeout = Math.max(config.minTimeBetweenInputs * 4, 50);
|
|
@@ -26170,7 +26428,6 @@ let RpgMap = class extends RpgCommonMap {
|
|
|
26170
26428
|
player.lastProcessedInputTs = 0;
|
|
26171
26429
|
}
|
|
26172
26430
|
}
|
|
26173
|
-
player.applyFrames();
|
|
26174
26431
|
return {
|
|
26175
26432
|
player,
|
|
26176
26433
|
inputs: processedInputs
|
|
@@ -26307,7 +26564,6 @@ let RpgMap = class extends RpgCommonMap {
|
|
|
26307
26564
|
eventInstance.context = context$1;
|
|
26308
26565
|
eventInstance.x.set(x);
|
|
26309
26566
|
eventInstance.y.set(y);
|
|
26310
|
-
eventInstance.applyFrames();
|
|
26311
26567
|
if (event.name) eventInstance.name.set(event.name);
|
|
26312
26568
|
this.events()[id] = eventInstance;
|
|
26313
26569
|
await eventInstance.execMethod("onInit");
|