@rpgjs/server 5.0.0-alpha.24 → 5.0.0-alpha.26
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 +32 -23
- 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 +8267 -10267
- package/dist/index.js.map +1 -1
- package/dist/rooms/BaseRoom.d.ts +95 -0
- package/dist/rooms/lobby.d.ts +4 -1
- package/dist/rooms/map.d.ts +67 -81
- package/package.json +10 -8
- package/src/Player/ItemManager.ts +55 -16
- package/src/Player/ParameterManager.ts +6 -1
- package/src/Player/Player.ts +164 -148
- package/src/module.ts +13 -0
- package/src/rooms/BaseRoom.ts +120 -0
- package/src/rooms/lobby.ts +11 -1
- package/src/rooms/map.ts +148 -152
- package/tests/change-map.spec.ts +72 -0
- package/tests/item.spec.ts +591 -0
- package/tests/module.spec.ts +38 -0
- package/tests/player-param.spec.ts +28 -0
- package/tests/world-maps.spec.ts +814 -0
- package/vite.config.ts +16 -0
package/src/Player/Player.ts
CHANGED
|
@@ -91,10 +91,69 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
91
91
|
context?: Context;
|
|
92
92
|
conn: MockConnection | null = null;
|
|
93
93
|
touchSide: boolean = false; // Protection against map change loops
|
|
94
|
-
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Computed signal for world X position
|
|
97
|
+
*
|
|
98
|
+
* Calculates the absolute world X position from the map's world position
|
|
99
|
+
* plus the player's local X position. Returns 0 if no map is assigned.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* const worldX = player.worldX();
|
|
104
|
+
* console.log(`Player is at world X: ${worldX}`);
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
get worldPositionX() {
|
|
108
|
+
return this._getComputedWorldPosition('x');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Computed signal for world Y position
|
|
113
|
+
*
|
|
114
|
+
* Calculates the absolute world Y position from the map's world position
|
|
115
|
+
* plus the player's local Y position. Returns 0 if no map is assigned.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const worldY = player.worldY();
|
|
120
|
+
* console.log(`Player is at world Y: ${worldY}`);
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
get worldPositionY() {
|
|
124
|
+
return this._getComputedWorldPosition('y');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
private _worldPositionSignals = new WeakMap<any, any>();
|
|
128
|
+
|
|
129
|
+
private _getComputedWorldPosition(axis: 'x' | 'y') {
|
|
130
|
+
// We use a WeakMap to cache the computed signal per instance
|
|
131
|
+
// This ensures that if the player object is copied (e.g. in tests),
|
|
132
|
+
// the new instance gets its own signal bound to itself.
|
|
133
|
+
if (!this._worldPositionSignals) {
|
|
134
|
+
this._worldPositionSignals = new WeakMap();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const key = axis;
|
|
138
|
+
let signals = this._worldPositionSignals.get(this);
|
|
139
|
+
if (!signals) {
|
|
140
|
+
signals = {};
|
|
141
|
+
this._worldPositionSignals.set(this, signals);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!signals[key]) {
|
|
145
|
+
signals[key] = computed(() => {
|
|
146
|
+
const map = this.map as RpgMap | null;
|
|
147
|
+
const mapWorldPos = map ? (map[axis === 'x' ? 'worldX' : 'worldY'] ?? 0) : 0;
|
|
148
|
+
return mapWorldPos + (this[axis] as any)();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return signals[key];
|
|
152
|
+
}
|
|
153
|
+
|
|
95
154
|
/** Internal: Shapes attached to this player */
|
|
96
155
|
private _attachedShapes: Map<string, RpgShape> = new Map();
|
|
97
|
-
|
|
156
|
+
|
|
98
157
|
/** Internal: Shapes where this player is currently located */
|
|
99
158
|
private _inShapes: Set<RpgShape> = new Set();
|
|
100
159
|
/** Last processed client input timestamp for reconciliation */
|
|
@@ -118,10 +177,10 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
118
177
|
super();
|
|
119
178
|
// Use type assertion to access mixin properties
|
|
120
179
|
(this as any).expCurve = {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
180
|
+
basis: 30,
|
|
181
|
+
extra: 20,
|
|
182
|
+
accelerationA: 30,
|
|
183
|
+
accelerationB: 30
|
|
125
184
|
};
|
|
126
185
|
|
|
127
186
|
(this as any).addParameter(MAXHP, MAXHP_CURVE);
|
|
@@ -139,7 +198,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
139
198
|
combineLatest([this.x.observable, this.y.observable])
|
|
140
199
|
.subscribe(([x, y]) => {
|
|
141
200
|
pendingUpdate = { x, y };
|
|
142
|
-
|
|
201
|
+
|
|
143
202
|
// Schedule a synchronous update using queueMicrotask
|
|
144
203
|
// This groups multiple rapid changes (x and y in the same tick) into a single frame
|
|
145
204
|
if (!updateScheduled) {
|
|
@@ -163,7 +222,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
163
222
|
}
|
|
164
223
|
})
|
|
165
224
|
}
|
|
166
|
-
|
|
225
|
+
|
|
167
226
|
_onInit() {
|
|
168
227
|
this.hooks.callHooks("server-playerProps-load", this).subscribe();
|
|
169
228
|
}
|
|
@@ -177,6 +236,10 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
177
236
|
return this.map
|
|
178
237
|
}
|
|
179
238
|
|
|
239
|
+
setMap(map: RpgMap) {
|
|
240
|
+
this.map = map;
|
|
241
|
+
}
|
|
242
|
+
|
|
180
243
|
applyFrames() {
|
|
181
244
|
this._frames.set(this.frames)
|
|
182
245
|
this.frames = []
|
|
@@ -185,7 +248,9 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
185
248
|
async execMethod(method: string, methodData: any[] = [], target?: any) {
|
|
186
249
|
let ret: any;
|
|
187
250
|
if (target) {
|
|
188
|
-
|
|
251
|
+
if (typeof target[method] === 'function') {
|
|
252
|
+
ret = await target[method](...methodData);
|
|
253
|
+
}
|
|
189
254
|
}
|
|
190
255
|
else {
|
|
191
256
|
ret = await lastValueFrom(this.hooks
|
|
@@ -217,12 +282,12 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
217
282
|
): Promise<any | null | boolean> {
|
|
218
283
|
const realMapId = 'map-' + mapId;
|
|
219
284
|
const room = this.getCurrentMap();
|
|
220
|
-
|
|
285
|
+
|
|
221
286
|
const canChange: boolean[] = await lastValueFrom(this.hooks.callHooks("server-player-canChangeMap", this, {
|
|
222
287
|
id: mapId,
|
|
223
288
|
}));
|
|
224
289
|
if (canChange.some(v => v === false)) return false;
|
|
225
|
-
|
|
290
|
+
|
|
226
291
|
if (positions && typeof positions === 'object') {
|
|
227
292
|
this.teleport(positions)
|
|
228
293
|
}
|
|
@@ -234,130 +299,81 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
234
299
|
return true;
|
|
235
300
|
}
|
|
236
301
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
* and automatically performs a change to the adjacent map if it exists.
|
|
242
|
-
*
|
|
243
|
-
* @param nextPosition - The next position of the player
|
|
244
|
-
* @returns Promise<boolean> - true if a map change occurred
|
|
245
|
-
*
|
|
246
|
-
* @example
|
|
247
|
-
* ```ts
|
|
248
|
-
* // Called automatically by the movement system
|
|
249
|
-
* const changed = await player.autoChangeMap({ x: newX, y: newY });
|
|
250
|
-
* if (changed) {
|
|
251
|
-
* console.log('Player changed map automatically');
|
|
252
|
-
* }
|
|
253
|
-
* ```
|
|
254
|
-
*/
|
|
255
|
-
async autoChangeMap(nextPosition: { x: number; y: number }, forcedDirection?: any): Promise<boolean> {
|
|
256
|
-
const map = this.getCurrentMap() as RpgMap; // Cast to access extended properties
|
|
257
|
-
if (!map) return false;
|
|
258
|
-
|
|
259
|
-
const worldMaps = map.getWorldMapsManager?.();
|
|
260
|
-
let ret: boolean = false;
|
|
261
|
-
|
|
302
|
+
async autoChangeMap(nextPosition: Vector2): Promise<boolean> {
|
|
303
|
+
const map = this.getCurrentMap()
|
|
304
|
+
const worldMaps = map?.getInWorldMaps()
|
|
305
|
+
let ret: boolean = false
|
|
262
306
|
if (worldMaps && map) {
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
// Current world position of the player
|
|
268
|
-
const worldPositionX = (map.worldX ?? 0) + this.x();
|
|
269
|
-
const worldPositionY = (map.worldY ?? 0) + this.y();
|
|
307
|
+
const direction = this.getDirection()
|
|
308
|
+
const marginLeftRight = map.tileWidth / 2
|
|
309
|
+
const marginTopDown = map.tileHeight / 2
|
|
270
310
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
311
|
+
const changeMap = async (adjacent, to) => {
|
|
312
|
+
if (this.touchSide) {
|
|
313
|
+
return false
|
|
314
|
+
}
|
|
315
|
+
this.touchSide = true
|
|
316
|
+
const [nextMap] = worldMaps.getAdjacentMaps(map, adjacent)
|
|
317
|
+
if (!nextMap) return false
|
|
318
|
+
const id = nextMap.id as string
|
|
319
|
+
const nextMapInfo = worldMaps.getMapInfo(id)
|
|
320
|
+
return !!(await this.changeMap(id, to(nextMapInfo)))
|
|
274
321
|
}
|
|
275
|
-
this.touchSide = true;
|
|
276
322
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
323
|
+
if (nextPosition.x < marginLeftRight && direction == Direction.Left) {
|
|
324
|
+
ret = await changeMap({
|
|
325
|
+
x: map.worldX - 1,
|
|
326
|
+
y: this.worldPositionY() + 1
|
|
327
|
+
}, nextMapInfo => ({
|
|
328
|
+
x: (nextMapInfo.width) - this.hitbox().w - marginLeftRight,
|
|
329
|
+
y: map.worldY - nextMapInfo.y + nextPosition.y
|
|
330
|
+
}))
|
|
281
331
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
332
|
+
else if (nextPosition.x > map.widthPx - this.hitbox().w - marginLeftRight && direction == Direction.Right) {
|
|
333
|
+
ret = await changeMap({
|
|
334
|
+
x: map.worldX + map.widthPx + 1,
|
|
335
|
+
y: this.worldPositionY() + 1
|
|
336
|
+
}, nextMapInfo => ({
|
|
337
|
+
x: marginLeftRight,
|
|
338
|
+
y: map.worldY - nextMapInfo.y + nextPosition.y
|
|
339
|
+
}))
|
|
340
|
+
}
|
|
341
|
+
else if (nextPosition.y < marginTopDown && direction == Direction.Up) {
|
|
342
|
+
ret = await changeMap({
|
|
343
|
+
x: this.worldPositionX() + 1,
|
|
344
|
+
y: map.worldY - 1
|
|
345
|
+
}, nextMapInfo => ({
|
|
346
|
+
x: map.worldX - nextMapInfo.x + nextPosition.x,
|
|
347
|
+
y: (nextMapInfo.height) - this.hitbox().h - marginTopDown,
|
|
348
|
+
}))
|
|
349
|
+
}
|
|
350
|
+
else if (nextPosition.y > map.heightPx - this.hitbox().h - marginTopDown && direction == Direction.Down) {
|
|
351
|
+
ret = await changeMap({
|
|
352
|
+
x: this.worldPositionX() + 1,
|
|
353
|
+
y: map.worldY + map.heightPx + 1
|
|
354
|
+
}, nextMapInfo => ({
|
|
355
|
+
x: map.worldX - nextMapInfo.x + nextPosition.x,
|
|
356
|
+
y: marginTopDown,
|
|
357
|
+
}))
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
this.touchSide = false
|
|
288
361
|
}
|
|
289
|
-
|
|
290
|
-
const newPosition = positionCalculator(nextMapInfo);
|
|
291
|
-
const success = await this.changeMap(id, newPosition);
|
|
292
|
-
|
|
293
|
-
// Reset touchSide after a delay to allow the change
|
|
294
|
-
setTimeout(() => {
|
|
295
|
-
this.touchSide = false;
|
|
296
|
-
}, 100);
|
|
297
|
-
|
|
298
|
-
return !!success;
|
|
299
|
-
};
|
|
300
|
-
// Check left border
|
|
301
|
-
if (nextPosition.x < marginLeftRight && direction === Direction.Left) {
|
|
302
|
-
ret = await changeMap({
|
|
303
|
-
x: (map.worldX ?? 0) - 1,
|
|
304
|
-
y: worldPositionY
|
|
305
|
-
}, nextMapInfo => ({
|
|
306
|
-
x: nextMapInfo.width - (this.hitbox().w) - marginLeftRight,
|
|
307
|
-
y: (map.worldY ?? 0) - (nextMapInfo.y ?? 0) + nextPosition.y
|
|
308
|
-
}));
|
|
309
|
-
}
|
|
310
|
-
// Check right border
|
|
311
|
-
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 => ({
|
|
316
|
-
x: marginLeftRight,
|
|
317
|
-
y: (map.worldY ?? 0) - (nextMapInfo.y ?? 0) + nextPosition.y
|
|
318
|
-
}));
|
|
319
|
-
}
|
|
320
|
-
// Check top border
|
|
321
|
-
else if (nextPosition.y < marginTopDown && direction === Direction.Up) {
|
|
322
|
-
ret = await changeMap({
|
|
323
|
-
x: worldPositionX,
|
|
324
|
-
y: (map.worldY ?? 0) - 1
|
|
325
|
-
}, nextMapInfo => ({
|
|
326
|
-
x: (map.worldX ?? 0) - (nextMapInfo.x ?? 0) + nextPosition.x,
|
|
327
|
-
y: nextMapInfo.height - this.hitbox().h - marginTopDown
|
|
328
|
-
}));
|
|
329
|
-
}
|
|
330
|
-
// Check bottom border
|
|
331
|
-
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 => ({
|
|
336
|
-
x: (map.worldX ?? 0) - (nextMapInfo.x ?? 0) + nextPosition.x,
|
|
337
|
-
y: marginTopDown
|
|
338
|
-
}));
|
|
339
|
-
}
|
|
340
|
-
else {
|
|
341
|
-
this.touchSide = false;
|
|
342
|
-
}
|
|
343
362
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
363
|
+
return ret
|
|
364
|
+
}
|
|
347
365
|
|
|
348
366
|
async teleport(positions: { x: number; y: number }) {
|
|
349
367
|
if (!this.map) return false;
|
|
350
|
-
if (this.map.physic) {
|
|
368
|
+
if (this.map && this.map.physic) {
|
|
351
369
|
// Skip collision check for teleportation (allow teleporting through walls)
|
|
352
370
|
const entity = this.map.physic.getEntityByUUID(this.id);
|
|
353
371
|
if (entity) {
|
|
354
372
|
this.map.physic.teleport(entity, { x: positions.x, y: positions.y });
|
|
355
373
|
}
|
|
356
374
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.y.set(positions.y)
|
|
360
|
-
}
|
|
375
|
+
this.x.set(positions.x)
|
|
376
|
+
this.y.set(positions.y)
|
|
361
377
|
// Wait for the frame to be added before applying frames
|
|
362
378
|
// This ensures the frame is added before applyFrames() is called
|
|
363
379
|
queueMicrotask(() => {
|
|
@@ -450,8 +466,9 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
450
466
|
}
|
|
451
467
|
|
|
452
468
|
databaseById(id: string) {
|
|
453
|
-
|
|
454
|
-
|
|
469
|
+
// Use this.map directly to support both RpgMap and LobbyRoom
|
|
470
|
+
const map = this.map as any;
|
|
471
|
+
if (!map || !map.database) return;
|
|
455
472
|
const data = map.database()[id];
|
|
456
473
|
if (!data)
|
|
457
474
|
throw new Error(
|
|
@@ -514,7 +531,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
514
531
|
// Handle overloaded signature: attachShape(options) or attachShape(id, options)
|
|
515
532
|
let zoneId: string;
|
|
516
533
|
let shapeOptions: AttachShapeOptions;
|
|
517
|
-
|
|
534
|
+
|
|
518
535
|
if (typeof idOrOptions === 'string') {
|
|
519
536
|
zoneId = idOrOptions;
|
|
520
537
|
if (!options) {
|
|
@@ -552,7 +569,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
552
569
|
if (shapeOptions.positioning) {
|
|
553
570
|
const playerWidth = playerEntity.width || playerEntity.radius * 2 || 32;
|
|
554
571
|
const playerHeight = playerEntity.height || playerEntity.radius * 2 || 32;
|
|
555
|
-
|
|
572
|
+
|
|
556
573
|
switch (shapeOptions.positioning) {
|
|
557
574
|
case 'top':
|
|
558
575
|
offset = new Vector2(0, -playerHeight / 2);
|
|
@@ -575,7 +592,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
575
592
|
|
|
576
593
|
// Get zone manager and create attached zone
|
|
577
594
|
const zoneManager = map.physic.getZoneManager();
|
|
578
|
-
|
|
595
|
+
|
|
579
596
|
// Convert direction from Direction enum to string if needed
|
|
580
597
|
// Direction enum values are already strings ("up", "down", "left", "right")
|
|
581
598
|
let direction: 'up' | 'down' | 'left' | 'right' = 'down';
|
|
@@ -616,7 +633,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
616
633
|
entities.forEach((entity) => {
|
|
617
634
|
const event = map.getEvent<RpgEvent>(entity.uuid);
|
|
618
635
|
const player = map.getPlayer(entity.uuid);
|
|
619
|
-
|
|
636
|
+
|
|
620
637
|
if (event) {
|
|
621
638
|
event.execMethod("onInShape", [shape, this]);
|
|
622
639
|
// Track that this event is in the shape
|
|
@@ -637,7 +654,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
637
654
|
entities.forEach((entity) => {
|
|
638
655
|
const event = map.getEvent<RpgEvent>(entity.uuid);
|
|
639
656
|
const player = map.getPlayer(entity.uuid);
|
|
640
|
-
|
|
657
|
+
|
|
641
658
|
if (event) {
|
|
642
659
|
event.execMethod("onOutShape", [shape, this]);
|
|
643
660
|
// Remove from tracking
|
|
@@ -674,10 +691,10 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
674
691
|
// Store mapping from zoneId to physicZoneId for future reference
|
|
675
692
|
(this as any)._zoneIdMap = (this as any)._zoneIdMap || new Map();
|
|
676
693
|
(this as any)._zoneIdMap.set(zoneId, physicZoneId);
|
|
677
|
-
|
|
694
|
+
|
|
678
695
|
// Store the shape
|
|
679
696
|
this._attachedShapes.set(zoneId, shape);
|
|
680
|
-
|
|
697
|
+
|
|
681
698
|
// Update shape position when player moves
|
|
682
699
|
const updateShapePosition = () => {
|
|
683
700
|
const currentEntity = map.physic.getEntityByUUID(this.id);
|
|
@@ -688,7 +705,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
688
705
|
}
|
|
689
706
|
}
|
|
690
707
|
};
|
|
691
|
-
|
|
708
|
+
|
|
692
709
|
// Listen to position changes to update shape position
|
|
693
710
|
playerEntity.onPositionChange(() => {
|
|
694
711
|
updateShapePosition();
|
|
@@ -696,7 +713,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
696
713
|
|
|
697
714
|
return shape;
|
|
698
715
|
}
|
|
699
|
-
|
|
716
|
+
|
|
700
717
|
/**
|
|
701
718
|
* Get all shapes attached to this player
|
|
702
719
|
*
|
|
@@ -716,7 +733,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
716
733
|
getShapes(): RpgShape[] {
|
|
717
734
|
return Array.from(this._attachedShapes.values());
|
|
718
735
|
}
|
|
719
|
-
|
|
736
|
+
|
|
720
737
|
/**
|
|
721
738
|
* Get all shapes where this player is currently located
|
|
722
739
|
*
|
|
@@ -1063,13 +1080,13 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
1063
1080
|
if (typeof height !== 'number' || height <= 0) {
|
|
1064
1081
|
throw new Error('setHitbox: height must be a positive number');
|
|
1065
1082
|
}
|
|
1066
|
-
|
|
1083
|
+
|
|
1067
1084
|
// Update hitbox signal
|
|
1068
1085
|
this.hitbox.set({
|
|
1069
1086
|
w: width,
|
|
1070
1087
|
h: height,
|
|
1071
1088
|
});
|
|
1072
|
-
|
|
1089
|
+
|
|
1073
1090
|
// Update physics entity if map exists
|
|
1074
1091
|
const map = this.getCurrentMap();
|
|
1075
1092
|
if (map && map.physic) {
|
|
@@ -1118,18 +1135,17 @@ export class RpgEvent extends RpgPlayer {
|
|
|
1118
1135
|
* Extends the RpgPlayer class with additional interfaces from mixins.
|
|
1119
1136
|
* This provides proper TypeScript support for all mixin methods and properties.
|
|
1120
1137
|
*/
|
|
1121
|
-
export interface RpgPlayer extends
|
|
1122
|
-
IVariableManager,
|
|
1123
|
-
IMoveManager,
|
|
1124
|
-
IGoldManager,
|
|
1125
|
-
IComponentManager,
|
|
1126
|
-
IGuiManager,
|
|
1127
|
-
IItemManager,
|
|
1128
|
-
IEffectManager,
|
|
1129
|
-
IParameterManager,
|
|
1130
|
-
IElementManager,
|
|
1131
|
-
ISkillManager,
|
|
1132
|
-
IBattleManager,
|
|
1133
|
-
IClassManager,
|
|
1134
|
-
IStateManager
|
|
1135
|
-
{}
|
|
1138
|
+
export interface RpgPlayer extends
|
|
1139
|
+
IVariableManager,
|
|
1140
|
+
IMoveManager,
|
|
1141
|
+
IGoldManager,
|
|
1142
|
+
IComponentManager,
|
|
1143
|
+
IGuiManager,
|
|
1144
|
+
IItemManager,
|
|
1145
|
+
IEffectManager,
|
|
1146
|
+
IParameterManager,
|
|
1147
|
+
IElementManager,
|
|
1148
|
+
ISkillManager,
|
|
1149
|
+
IBattleManager,
|
|
1150
|
+
IClassManager,
|
|
1151
|
+
IStateManager { }
|
package/src/module.ts
CHANGED
|
@@ -127,6 +127,19 @@ export function provideServerModules(modules: RpgServerModule[]): FactoryProvide
|
|
|
127
127
|
}
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
|
+
if (module.database && typeof module.database === 'object') {
|
|
131
|
+
const database = {...module.database};
|
|
132
|
+
module = {
|
|
133
|
+
...module,
|
|
134
|
+
databaseHooks: {
|
|
135
|
+
load: (engine: RpgMap) => {
|
|
136
|
+
for (const key in database) {
|
|
137
|
+
engine.addInDatabase(key, database[key]);
|
|
138
|
+
}
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
}
|
|
130
143
|
return module;
|
|
131
144
|
})
|
|
132
145
|
return modules
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { signal } from "@signe/reactive";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base class for rooms that need database functionality
|
|
5
|
+
*
|
|
6
|
+
* This class provides common database management functionality that is shared
|
|
7
|
+
* between RpgMap and LobbyRoom. It includes methods for adding and managing
|
|
8
|
+
* items, classes, and other game data in the room's database.
|
|
9
|
+
*
|
|
10
|
+
* ## Architecture
|
|
11
|
+
*
|
|
12
|
+
* Both RpgMap and LobbyRoom need to store game entities (items, classes, skills, etc.)
|
|
13
|
+
* in a database. This base class provides the common implementation to avoid code duplication.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* class MyCustomRoom extends BaseRoom {
|
|
18
|
+
* // Your custom room implementation
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export abstract class BaseRoom {
|
|
23
|
+
/**
|
|
24
|
+
* Signal containing the room's database of items, classes, and other game data
|
|
25
|
+
*
|
|
26
|
+
* This database can be dynamically populated using `addInDatabase()` and
|
|
27
|
+
* `removeInDatabase()` methods. It's used to store game entities like items,
|
|
28
|
+
* classes, skills, etc. that are available in this room.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* // Add data to database
|
|
33
|
+
* room.addInDatabase('Potion', PotionClass);
|
|
34
|
+
*
|
|
35
|
+
* // Access database
|
|
36
|
+
* const potion = room.database()['Potion'];
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
database = signal({});
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Add data to the room's database
|
|
43
|
+
*
|
|
44
|
+
* Adds an item, class, or other game entity to the room's database.
|
|
45
|
+
* If the ID already exists and `force` is not enabled, the addition is ignored.
|
|
46
|
+
*
|
|
47
|
+
* ## Architecture
|
|
48
|
+
*
|
|
49
|
+
* This method is used by the item management system to store item definitions
|
|
50
|
+
* in the room's database. When a player adds an item, the system first checks
|
|
51
|
+
* if the item exists in the database, and if not, adds it using this method.
|
|
52
|
+
*
|
|
53
|
+
* @param id - Unique identifier for the data
|
|
54
|
+
* @param data - The data to add (can be a class, object, etc.)
|
|
55
|
+
* @param options - Optional configuration
|
|
56
|
+
* @param options.force - If true, overwrites existing data with the same ID
|
|
57
|
+
* @returns `true` if data was added, `false` if it was ignored (ID already exists)
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```ts
|
|
61
|
+
* // Add a class to the database
|
|
62
|
+
* room.addInDatabase('Potion', PotionClass);
|
|
63
|
+
*
|
|
64
|
+
* // Add an item object to the database
|
|
65
|
+
* room.addInDatabase('custom-item', {
|
|
66
|
+
* name: 'Custom Item',
|
|
67
|
+
* price: 100
|
|
68
|
+
* });
|
|
69
|
+
*
|
|
70
|
+
* // Force overwrite existing data
|
|
71
|
+
* room.addInDatabase('Potion', UpdatedPotionClass, { force: true });
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
addInDatabase(id: string, data: any, options?: { force?: boolean }): boolean {
|
|
75
|
+
const database = this.database();
|
|
76
|
+
|
|
77
|
+
// Check if ID already exists
|
|
78
|
+
if (database[id] !== undefined && !options?.force) {
|
|
79
|
+
// Ignore the addition if ID exists and force is not enabled
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Add or overwrite the data
|
|
84
|
+
database[id] = data;
|
|
85
|
+
this.database.set(database);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Remove data from the room's database
|
|
91
|
+
*
|
|
92
|
+
* This method allows you to remove items or data from the room's database.
|
|
93
|
+
*
|
|
94
|
+
* @param id - Unique identifier of the data to remove
|
|
95
|
+
* @returns `true` if data was removed, `false` if ID didn't exist
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* // Remove an item from the database
|
|
100
|
+
* room.removeInDatabase('Potion');
|
|
101
|
+
*
|
|
102
|
+
* // Check if removal was successful
|
|
103
|
+
* const removed = room.removeInDatabase('custom-item');
|
|
104
|
+
* if (removed) {
|
|
105
|
+
* console.log('Item removed successfully');
|
|
106
|
+
* }
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
removeInDatabase(id: string): boolean {
|
|
110
|
+
const database = this.database();
|
|
111
|
+
|
|
112
|
+
if (database[id] === undefined) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
delete database[id];
|
|
117
|
+
this.database.set(database);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
package/src/rooms/lobby.ts
CHANGED
|
@@ -5,12 +5,22 @@ import { context } from "../core/context";
|
|
|
5
5
|
import { users } from "@signe/sync";
|
|
6
6
|
import { signal } from "@signe/reactive";
|
|
7
7
|
import { RpgPlayer } from "../Player/Player";
|
|
8
|
+
import { BaseRoom } from "./BaseRoom";
|
|
8
9
|
|
|
9
10
|
@Room({
|
|
10
11
|
path: "lobby-{id}",
|
|
11
12
|
})
|
|
12
|
-
export class LobbyRoom {
|
|
13
|
+
export class LobbyRoom extends BaseRoom {
|
|
13
14
|
@users(RpgPlayer) players = signal({});
|
|
15
|
+
autoSync: boolean = true;
|
|
16
|
+
|
|
17
|
+
constructor(room) {
|
|
18
|
+
super();
|
|
19
|
+
const isTest = room.env.TEST === 'true' ? true : false;
|
|
20
|
+
if (isTest) {
|
|
21
|
+
this.autoSync = false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
14
24
|
|
|
15
25
|
onJoin(player: RpgPlayer, conn: MockConnection) {
|
|
16
26
|
player.map = this;
|