@rpgjs/server 5.0.0-alpha.23 → 5.0.0-alpha.25
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/Player/BattleManager.d.ts +1 -1
- package/dist/Player/ClassManager.d.ts +1 -1
- package/dist/Player/ComponentManager.d.ts +1 -1
- package/dist/Player/EffectManager.d.ts +1 -1
- package/dist/Player/ElementManager.d.ts +1 -1
- package/dist/Player/GoldManager.d.ts +1 -1
- package/dist/Player/GuiManager.d.ts +1 -1
- package/dist/Player/ItemFixture.d.ts +1 -1
- package/dist/Player/ItemManager.d.ts +1 -1
- package/dist/Player/MoveManager.d.ts +1 -1
- package/dist/Player/ParameterManager.d.ts +1 -1
- package/dist/Player/Player.d.ts +1 -1
- package/dist/Player/SkillManager.d.ts +1 -1
- package/dist/Player/StateManager.d.ts +1 -1
- package/dist/Player/VariableManager.d.ts +1 -1
- package/dist/RpgServer.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7866 -9552
- package/dist/index.js.map +1 -1
- package/dist/rooms/map.d.ts +50 -6
- package/package.json +11 -9
- package/src/Player/ItemManager.ts +5 -1
- package/src/Player/ParameterManager.ts +6 -1
- package/src/Player/Player.ts +9 -19
- package/src/rooms/map.ts +81 -9
- package/tests/change-map.spec.ts +72 -0
- package/tests/item.spec.ts +577 -0
- package/tests/module.spec.ts +38 -0
- package/tests/player-param.spec.ts +28 -0
- package/tests/world-maps.spec.ts +852 -0
- package/vite.config.ts +16 -0
package/dist/rooms/map.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { MockConnection, RoomOnJoin } from '@signe/room';
|
|
2
|
-
import { Hooks, RpgCommonMap, RpgShape, WorldMapsManager, WorldMapConfig } from '
|
|
2
|
+
import { Hooks, RpgCommonMap, RpgShape, WorldMapsManager, WorldMapConfig } from '../../../common/src';
|
|
3
3
|
import { RpgPlayer, RpgEvent } from '../Player/Player';
|
|
4
4
|
import { BehaviorSubject } from 'rxjs';
|
|
5
5
|
import { MapOptions } from '../decorators/map';
|
|
@@ -162,6 +162,10 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
|
|
|
162
162
|
private _shapes;
|
|
163
163
|
/** Internal: Map of shape entity UUIDs to RpgShape instances */
|
|
164
164
|
private _shapeEntities;
|
|
165
|
+
/** Internal: Subscription for the input processing loop */
|
|
166
|
+
private _inputLoopSubscription?;
|
|
167
|
+
/** Enable/disable automatic tick processing (useful for unit tests) */
|
|
168
|
+
private _autoTickEnabled;
|
|
165
169
|
constructor();
|
|
166
170
|
/**
|
|
167
171
|
* Setup collision detection between players, events, and shapes
|
|
@@ -543,24 +547,43 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
|
|
|
543
547
|
/**
|
|
544
548
|
* Main game loop that processes player inputs
|
|
545
549
|
*
|
|
546
|
-
* This private method
|
|
547
|
-
* for all players on the map. It ensures inputs are
|
|
548
|
-
* prevents concurrent processing for the same player.
|
|
550
|
+
* This private method subscribes to tick$ and processes pending inputs
|
|
551
|
+
* for all players on the map with a throttle of 50ms. It ensures inputs are
|
|
552
|
+
* processed in order and prevents concurrent processing for the same player.
|
|
549
553
|
*
|
|
550
554
|
* ## Architecture
|
|
551
555
|
*
|
|
552
|
-
* -
|
|
556
|
+
* - Subscribes to tick$ with throttleTime(50ms) for responsive input processing
|
|
553
557
|
* - Processes inputs for each player with pending inputs
|
|
554
558
|
* - Uses a flag to prevent concurrent processing for the same player
|
|
555
559
|
* - Calls `processInput()` to handle anti-cheat validation and movement
|
|
556
560
|
*
|
|
557
561
|
* @example
|
|
558
562
|
* ```ts
|
|
559
|
-
* // This method is called automatically in the constructor
|
|
563
|
+
* // This method is called automatically in the constructor if autoTick is enabled
|
|
560
564
|
* // You typically don't call it directly
|
|
561
565
|
* ```
|
|
562
566
|
*/
|
|
563
567
|
private loop;
|
|
568
|
+
/**
|
|
569
|
+
* Enable or disable automatic tick processing
|
|
570
|
+
*
|
|
571
|
+
* When disabled, the input processing loop will not run automatically.
|
|
572
|
+
* This is useful for unit tests where you want manual control over when
|
|
573
|
+
* inputs are processed.
|
|
574
|
+
*
|
|
575
|
+
* @param enabled - Whether to enable automatic tick processing (default: true)
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* ```ts
|
|
579
|
+
* // Disable auto tick for testing
|
|
580
|
+
* map.setAutoTick(false);
|
|
581
|
+
*
|
|
582
|
+
* // Manually trigger tick processing
|
|
583
|
+
* await map.processInput('player1');
|
|
584
|
+
* ```
|
|
585
|
+
*/
|
|
586
|
+
setAutoTick(enabled: boolean): void;
|
|
564
587
|
/**
|
|
565
588
|
* Get a world manager by id
|
|
566
589
|
*
|
|
@@ -1244,6 +1267,27 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
|
|
|
1244
1267
|
frequency?: number;
|
|
1245
1268
|
direction?: 'x' | 'y' | 'both';
|
|
1246
1269
|
}): void;
|
|
1270
|
+
/**
|
|
1271
|
+
* Clear all server resources and reset state
|
|
1272
|
+
*
|
|
1273
|
+
* This method should be called to clean up all server-side resources when
|
|
1274
|
+
* shutting down or resetting the map. It stops the input processing loop
|
|
1275
|
+
* and ensures that all subscriptions are properly cleaned up.
|
|
1276
|
+
*
|
|
1277
|
+
* ## Design
|
|
1278
|
+
*
|
|
1279
|
+
* This method is used primarily in testing environments to ensure clean
|
|
1280
|
+
* state between tests. It stops the tick subscription to prevent memory leaks.
|
|
1281
|
+
*
|
|
1282
|
+
* @example
|
|
1283
|
+
* ```ts
|
|
1284
|
+
* // In test cleanup
|
|
1285
|
+
* afterEach(() => {
|
|
1286
|
+
* map.clear();
|
|
1287
|
+
* });
|
|
1288
|
+
* ```
|
|
1289
|
+
*/
|
|
1290
|
+
clear(): void;
|
|
1247
1291
|
}
|
|
1248
1292
|
export interface RpgMap {
|
|
1249
1293
|
$send: (conn: MockConnection, data: any) => void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpgjs/server",
|
|
3
|
-
"version": "5.0.0-alpha.
|
|
3
|
+
"version": "5.0.0-alpha.25",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"publishConfig": {
|
|
@@ -11,23 +11,25 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"description": "",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@rpgjs/common": "5.0.0-alpha.
|
|
15
|
-
"@rpgjs/physic": "5.0.0-alpha.
|
|
14
|
+
"@rpgjs/common": "5.0.0-alpha.25",
|
|
15
|
+
"@rpgjs/physic": "5.0.0-alpha.25",
|
|
16
|
+
"@rpgjs/testing": "5.0.0-alpha.25",
|
|
16
17
|
"@rpgjs/database": "^4.3.0",
|
|
17
|
-
"@signe/di": "^2.
|
|
18
|
-
"@signe/reactive": "^2.
|
|
19
|
-
"@signe/room": "^2.
|
|
20
|
-
"@signe/sync": "^2.
|
|
18
|
+
"@signe/di": "^2.6.0",
|
|
19
|
+
"@signe/reactive": "^2.6.0",
|
|
20
|
+
"@signe/room": "^2.6.0",
|
|
21
|
+
"@signe/sync": "^2.6.0",
|
|
21
22
|
"rxjs": "^7.8.2",
|
|
22
23
|
"zod": "^4.1.13"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
|
-
"vite": "^7.2.
|
|
26
|
+
"vite": "^7.2.7",
|
|
26
27
|
"vite-plugin-dts": "^4.5.4"
|
|
27
28
|
},
|
|
28
29
|
"type": "module",
|
|
29
30
|
"scripts": {
|
|
30
31
|
"dev": "vite build --watch",
|
|
31
|
-
"build": "vite build"
|
|
32
|
+
"build": "vite build",
|
|
33
|
+
"test": "vitest"
|
|
32
34
|
}
|
|
33
35
|
}
|
|
@@ -278,6 +278,7 @@ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
|
|
|
278
278
|
// Create new item instance
|
|
279
279
|
instance = new Item(data);
|
|
280
280
|
instance.id.set(itemId);
|
|
281
|
+
instance.quantity.set(nb);
|
|
281
282
|
|
|
282
283
|
// Attach hooks from class instance or object
|
|
283
284
|
if (itemInstance) {
|
|
@@ -294,6 +295,7 @@ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
|
|
|
294
295
|
|
|
295
296
|
// Call onAdd hook - use stored instance if available
|
|
296
297
|
const hookTarget = (instance as any)._itemInstance || instance;
|
|
298
|
+
// Only call onAdd if it exists and is a function
|
|
297
299
|
(this as any)["execMethod"]("onAdd", [this], hookTarget);
|
|
298
300
|
return instance;
|
|
299
301
|
}
|
|
@@ -315,7 +317,9 @@ export function WithItemManager<TBase extends PlayerCtor>(Base: TBase) {
|
|
|
315
317
|
}
|
|
316
318
|
// Call onRemove hook - use stored instance if available
|
|
317
319
|
const hookTarget = (item as any)._itemInstance || item;
|
|
318
|
-
|
|
320
|
+
if (hookTarget && typeof hookTarget.onRemove === 'function') {
|
|
321
|
+
this["execMethod"]("onRemove", [this], hookTarget);
|
|
322
|
+
}
|
|
319
323
|
return this.items()[itemIndex];
|
|
320
324
|
}
|
|
321
325
|
|
|
@@ -580,6 +580,9 @@ export function WithParameterManager<TBase extends PlayerCtor>(Base: TBase) {
|
|
|
580
580
|
this.hpSignal.set(val)
|
|
581
581
|
}
|
|
582
582
|
|
|
583
|
+
get hp(): number {
|
|
584
|
+
return this.hpSignal()
|
|
585
|
+
}
|
|
583
586
|
|
|
584
587
|
/**
|
|
585
588
|
* Changes the skill points
|
|
@@ -601,7 +604,9 @@ export function WithParameterManager<TBase extends PlayerCtor>(Base: TBase) {
|
|
|
601
604
|
this.spSignal.set(val)
|
|
602
605
|
}
|
|
603
606
|
|
|
604
|
-
|
|
607
|
+
get sp(): number {
|
|
608
|
+
return this.spSignal()
|
|
609
|
+
}
|
|
605
610
|
|
|
606
611
|
/**
|
|
607
612
|
* Changing the player's experience.
|
package/src/Player/Player.ts
CHANGED
|
@@ -185,7 +185,9 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
185
185
|
async execMethod(method: string, methodData: any[] = [], target?: any) {
|
|
186
186
|
let ret: any;
|
|
187
187
|
if (target) {
|
|
188
|
-
|
|
188
|
+
if (typeof target[method] === 'function') {
|
|
189
|
+
ret = await target[method](...methodData);
|
|
190
|
+
}
|
|
189
191
|
}
|
|
190
192
|
else {
|
|
191
193
|
ret = await lastValueFrom(this.hooks
|
|
@@ -268,13 +270,13 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
268
270
|
const worldPositionX = (map.worldX ?? 0) + this.x();
|
|
269
271
|
const worldPositionY = (map.worldY ?? 0) + this.y();
|
|
270
272
|
|
|
271
|
-
const changeMap = async (
|
|
273
|
+
const changeMap = async (directionNumber: number, positionCalculator: (nextMapInfo: any) => {x: number, y: number}) => {
|
|
272
274
|
if (this.touchSide) {
|
|
273
275
|
return false;
|
|
274
276
|
}
|
|
275
277
|
this.touchSide = true;
|
|
276
278
|
|
|
277
|
-
const [nextMap] = worldMaps.getAdjacentMaps(map,
|
|
279
|
+
const [nextMap] = worldMaps.getAdjacentMaps(map, directionNumber);
|
|
278
280
|
if (!nextMap) {
|
|
279
281
|
this.touchSide = false;
|
|
280
282
|
return false;
|
|
@@ -299,40 +301,28 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
299
301
|
};
|
|
300
302
|
// Check left border
|
|
301
303
|
if (nextPosition.x < marginLeftRight && direction === Direction.Left) {
|
|
302
|
-
ret = await changeMap({
|
|
303
|
-
x: (map.worldX ?? 0) - 1,
|
|
304
|
-
y: worldPositionY
|
|
305
|
-
}, nextMapInfo => ({
|
|
304
|
+
ret = await changeMap(2, nextMapInfo => ({
|
|
306
305
|
x: nextMapInfo.width - (this.hitbox().w) - marginLeftRight,
|
|
307
306
|
y: (map.worldY ?? 0) - (nextMapInfo.y ?? 0) + nextPosition.y
|
|
308
307
|
}));
|
|
309
308
|
}
|
|
310
309
|
// Check right border
|
|
311
310
|
else if (nextPosition.x > map.widthPx - this.hitbox().w - marginLeftRight && direction === Direction.Right) {
|
|
312
|
-
ret = await changeMap({
|
|
313
|
-
x: (map.worldX ?? 0) + map.widthPx + 1,
|
|
314
|
-
y: worldPositionY
|
|
315
|
-
}, nextMapInfo => ({
|
|
311
|
+
ret = await changeMap(3, nextMapInfo => ({
|
|
316
312
|
x: marginLeftRight,
|
|
317
313
|
y: (map.worldY ?? 0) - (nextMapInfo.y ?? 0) + nextPosition.y
|
|
318
314
|
}));
|
|
319
315
|
}
|
|
320
316
|
// Check top border
|
|
321
317
|
else if (nextPosition.y < marginTopDown && direction === Direction.Up) {
|
|
322
|
-
ret = await changeMap({
|
|
323
|
-
x: worldPositionX,
|
|
324
|
-
y: (map.worldY ?? 0) - 1
|
|
325
|
-
}, nextMapInfo => ({
|
|
318
|
+
ret = await changeMap(0, nextMapInfo => ({
|
|
326
319
|
x: (map.worldX ?? 0) - (nextMapInfo.x ?? 0) + nextPosition.x,
|
|
327
320
|
y: nextMapInfo.height - this.hitbox().h - marginTopDown
|
|
328
321
|
}));
|
|
329
322
|
}
|
|
330
323
|
// Check bottom border
|
|
331
324
|
else if (nextPosition.y > map.heightPx - this.hitbox().h - marginTopDown && direction === Direction.Down) {
|
|
332
|
-
ret = await changeMap({
|
|
333
|
-
x: worldPositionX,
|
|
334
|
-
y: (map.worldY ?? 0) + map.heightPx + 1
|
|
335
|
-
}, nextMapInfo => ({
|
|
325
|
+
ret = await changeMap(1, nextMapInfo => ({
|
|
336
326
|
x: (map.worldX ?? 0) - (nextMapInfo.x ?? 0) + nextPosition.x,
|
|
337
327
|
y: marginTopDown
|
|
338
328
|
}));
|
package/src/rooms/map.ts
CHANGED
|
@@ -6,7 +6,7 @@ import { generateShortUUID, sync, type, users } from "@signe/sync";
|
|
|
6
6
|
import { signal } from "@signe/reactive";
|
|
7
7
|
import { inject } from "@signe/di";
|
|
8
8
|
import { context } from "../core/context";;
|
|
9
|
-
import { finalize, lastValueFrom } from "rxjs";
|
|
9
|
+
import { finalize, lastValueFrom, throttleTime } from "rxjs";
|
|
10
10
|
import { Subject } from "rxjs";
|
|
11
11
|
import { BehaviorSubject } from "rxjs";
|
|
12
12
|
import { COEFFICIENT_ELEMENTS, DAMAGE_CRITICAL, DAMAGE_PHYSIC, DAMAGE_SKILL } from "../presets";
|
|
@@ -207,6 +207,10 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
207
207
|
private _shapes: Map<string, RpgShape> = new Map();
|
|
208
208
|
/** Internal: Map of shape entity UUIDs to RpgShape instances */
|
|
209
209
|
private _shapeEntities: Map<string, RpgShape> = new Map();
|
|
210
|
+
/** Internal: Subscription for the input processing loop */
|
|
211
|
+
private _inputLoopSubscription?: any;
|
|
212
|
+
/** Enable/disable automatic tick processing (useful for unit tests) */
|
|
213
|
+
private _autoTickEnabled: boolean = true;
|
|
210
214
|
|
|
211
215
|
constructor() {
|
|
212
216
|
super();
|
|
@@ -215,7 +219,9 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
215
219
|
this.throttleStorage = this.isStandalone ? 0 : 1000;
|
|
216
220
|
this.sessionExpiryTime = 1000 * 60 * 5; //5 minutes
|
|
217
221
|
this.setupCollisionDetection();
|
|
218
|
-
this.
|
|
222
|
+
if (this._autoTickEnabled) {
|
|
223
|
+
this.loop();
|
|
224
|
+
}
|
|
219
225
|
}
|
|
220
226
|
|
|
221
227
|
/**
|
|
@@ -1052,25 +1058,31 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1052
1058
|
/**
|
|
1053
1059
|
* Main game loop that processes player inputs
|
|
1054
1060
|
*
|
|
1055
|
-
* This private method
|
|
1056
|
-
* for all players on the map. It ensures inputs are
|
|
1057
|
-
* prevents concurrent processing for the same player.
|
|
1061
|
+
* This private method subscribes to tick$ and processes pending inputs
|
|
1062
|
+
* for all players on the map with a throttle of 50ms. It ensures inputs are
|
|
1063
|
+
* processed in order and prevents concurrent processing for the same player.
|
|
1058
1064
|
*
|
|
1059
1065
|
* ## Architecture
|
|
1060
1066
|
*
|
|
1061
|
-
* -
|
|
1067
|
+
* - Subscribes to tick$ with throttleTime(50ms) for responsive input processing
|
|
1062
1068
|
* - Processes inputs for each player with pending inputs
|
|
1063
1069
|
* - Uses a flag to prevent concurrent processing for the same player
|
|
1064
1070
|
* - Calls `processInput()` to handle anti-cheat validation and movement
|
|
1065
1071
|
*
|
|
1066
1072
|
* @example
|
|
1067
1073
|
* ```ts
|
|
1068
|
-
* // This method is called automatically in the constructor
|
|
1074
|
+
* // This method is called automatically in the constructor if autoTick is enabled
|
|
1069
1075
|
* // You typically don't call it directly
|
|
1070
1076
|
* ```
|
|
1071
1077
|
*/
|
|
1072
1078
|
private loop() {
|
|
1073
|
-
|
|
1079
|
+
if (this._inputLoopSubscription) {
|
|
1080
|
+
this._inputLoopSubscription.unsubscribe();
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
this._inputLoopSubscription = this.tick$.pipe(
|
|
1084
|
+
throttleTime(50) // Throttle to 50ms for input processing
|
|
1085
|
+
).subscribe(async ({ timestamp }) => {
|
|
1074
1086
|
for (const player of this.getPlayers()) {
|
|
1075
1087
|
if (player.pendingInputs.length > 0) {
|
|
1076
1088
|
const anyPlayer = player as any;
|
|
@@ -1082,7 +1094,35 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1082
1094
|
}
|
|
1083
1095
|
}
|
|
1084
1096
|
}
|
|
1085
|
-
}
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
/**
|
|
1101
|
+
* Enable or disable automatic tick processing
|
|
1102
|
+
*
|
|
1103
|
+
* When disabled, the input processing loop will not run automatically.
|
|
1104
|
+
* This is useful for unit tests where you want manual control over when
|
|
1105
|
+
* inputs are processed.
|
|
1106
|
+
*
|
|
1107
|
+
* @param enabled - Whether to enable automatic tick processing (default: true)
|
|
1108
|
+
*
|
|
1109
|
+
* @example
|
|
1110
|
+
* ```ts
|
|
1111
|
+
* // Disable auto tick for testing
|
|
1112
|
+
* map.setAutoTick(false);
|
|
1113
|
+
*
|
|
1114
|
+
* // Manually trigger tick processing
|
|
1115
|
+
* await map.processInput('player1');
|
|
1116
|
+
* ```
|
|
1117
|
+
*/
|
|
1118
|
+
setAutoTick(enabled: boolean): void {
|
|
1119
|
+
this._autoTickEnabled = enabled;
|
|
1120
|
+
if (enabled && !this._inputLoopSubscription) {
|
|
1121
|
+
this.loop();
|
|
1122
|
+
} else if (!enabled && this._inputLoopSubscription) {
|
|
1123
|
+
this._inputLoopSubscription.unsubscribe();
|
|
1124
|
+
this._inputLoopSubscription = undefined;
|
|
1125
|
+
}
|
|
1086
1126
|
}
|
|
1087
1127
|
|
|
1088
1128
|
/**
|
|
@@ -2036,6 +2076,38 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
2036
2076
|
},
|
|
2037
2077
|
});
|
|
2038
2078
|
}
|
|
2079
|
+
|
|
2080
|
+
/**
|
|
2081
|
+
* Clear all server resources and reset state
|
|
2082
|
+
*
|
|
2083
|
+
* This method should be called to clean up all server-side resources when
|
|
2084
|
+
* shutting down or resetting the map. It stops the input processing loop
|
|
2085
|
+
* and ensures that all subscriptions are properly cleaned up.
|
|
2086
|
+
*
|
|
2087
|
+
* ## Design
|
|
2088
|
+
*
|
|
2089
|
+
* This method is used primarily in testing environments to ensure clean
|
|
2090
|
+
* state between tests. It stops the tick subscription to prevent memory leaks.
|
|
2091
|
+
*
|
|
2092
|
+
* @example
|
|
2093
|
+
* ```ts
|
|
2094
|
+
* // In test cleanup
|
|
2095
|
+
* afterEach(() => {
|
|
2096
|
+
* map.clear();
|
|
2097
|
+
* });
|
|
2098
|
+
* ```
|
|
2099
|
+
*/
|
|
2100
|
+
clear(): void {
|
|
2101
|
+
try {
|
|
2102
|
+
// Stop input processing loop
|
|
2103
|
+
if (this._inputLoopSubscription) {
|
|
2104
|
+
this._inputLoopSubscription.unsubscribe();
|
|
2105
|
+
this._inputLoopSubscription = undefined;
|
|
2106
|
+
}
|
|
2107
|
+
} catch (error) {
|
|
2108
|
+
console.warn('Error during map cleanup:', error);
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2039
2111
|
}
|
|
2040
2112
|
|
|
2041
2113
|
export interface RpgMap {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { beforeEach, test, expect, afterEach } from 'vitest'
|
|
2
|
+
import { testing } from '@rpgjs/testing'
|
|
3
|
+
import { defineModule, createModule } from '@rpgjs/common'
|
|
4
|
+
import { RpgPlayer, RpgServer } from '../src'
|
|
5
|
+
import { RpgClient } from '../../client/src'
|
|
6
|
+
|
|
7
|
+
// Define server module with two maps
|
|
8
|
+
const serverModule = defineModule<RpgServer>({
|
|
9
|
+
maps: [
|
|
10
|
+
{
|
|
11
|
+
id: 'map1',
|
|
12
|
+
file: '',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'map2',
|
|
16
|
+
file: '',
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
player: {
|
|
20
|
+
async onConnected(player) {
|
|
21
|
+
// Start player on map1
|
|
22
|
+
await player.changeMap('map1', { x: 100, y: 100 })
|
|
23
|
+
},
|
|
24
|
+
onJoinMap(player) {
|
|
25
|
+
console.log('onJoinMap', player.getCurrentMap()?.id)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
// Define client module
|
|
31
|
+
const clientModule = defineModule<RpgClient>({
|
|
32
|
+
// Client-side logic
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
let player: RpgPlayer
|
|
36
|
+
let client: any
|
|
37
|
+
let fixture: any
|
|
38
|
+
|
|
39
|
+
beforeEach(async () => {
|
|
40
|
+
const myModule = createModule('TestModule', [{
|
|
41
|
+
server: serverModule,
|
|
42
|
+
client: clientModule
|
|
43
|
+
}])
|
|
44
|
+
|
|
45
|
+
fixture = await testing(myModule)
|
|
46
|
+
client = await fixture.createClient()
|
|
47
|
+
player = client.player
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
afterEach(() => {
|
|
51
|
+
fixture.clear()
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('Player can change map', async () => {
|
|
55
|
+
player = await client.waitForMapChange('map1')
|
|
56
|
+
|
|
57
|
+
const initialMap = player.getCurrentMap()
|
|
58
|
+
expect(initialMap).toBeDefined()
|
|
59
|
+
expect(initialMap?.id).toBe('map1')
|
|
60
|
+
|
|
61
|
+
const result = await player.changeMap('map2', { x: 200, y: 200 })
|
|
62
|
+
expect(result).toBe(true)
|
|
63
|
+
|
|
64
|
+
player = await client.waitForMapChange('map2')
|
|
65
|
+
|
|
66
|
+
const newMap = player.getCurrentMap()
|
|
67
|
+
expect(newMap).toBeDefined()
|
|
68
|
+
expect(newMap?.id).toBe('map2')
|
|
69
|
+
|
|
70
|
+
expect(player.x()).toBe(200 - player.hitbox().h / 2)
|
|
71
|
+
expect(player.y()).toBe(200 - player.hitbox().w / 2)
|
|
72
|
+
})
|