@rpgjs/server 5.0.0-beta.5 → 5.0.0-beta.6
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 +11 -12
- package/dist/index.js.map +1 -1
- package/dist/{module-BmvXIvlE.js → module-Dy124Jyk.js} +432 -239
- package/dist/module-Dy124Jyk.js.map +1 -0
- package/dist/node/index.js +1 -1
- package/package.json +4 -4
- package/src/Player/Player.ts +1 -1
- package/src/rooms/map.ts +21 -15
- package/tests/action-interaction.spec.ts +97 -0
- package/tests/item.spec.ts +72 -1
- package/tests/move.spec.ts +65 -1
- package/tests/world-maps.spec.ts +4 -0
- package/dist/module-BmvXIvlE.js.map +0 -1
package/dist/node/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as isMapUpdateAuthorized, c as updateMap, h as injector, i as createMapUpdateHeaders, l as context, n as MAP_UPDATE_TOKEN_ENV, o as readMapUpdateToken, p as setInject, r as MAP_UPDATE_TOKEN_HEADER, s as resolveMapUpdateToken, t as provideServerModules } from "../module-
|
|
1
|
+
import { a as isMapUpdateAuthorized, c as updateMap, h as injector, i as createMapUpdateHeaders, l as context, n as MAP_UPDATE_TOKEN_ENV, o as readMapUpdateToken, p as setInject, r as MAP_UPDATE_TOKEN_HEADER, s as resolveMapUpdateToken, t as provideServerModules } from "../module-Dy124Jyk.js";
|
|
2
2
|
//#region src/node/connection.ts
|
|
3
3
|
function readEnvVariable(name) {
|
|
4
4
|
const value = globalThis.process?.env?.[name];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpgjs/server",
|
|
3
|
-
"version": "5.0.0-beta.
|
|
3
|
+
"version": "5.0.0-beta.6",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"exports": {
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
"description": "",
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@canvasengine/tiled": "2.0.0-beta.58",
|
|
25
|
-
"@rpgjs/common": "5.0.0-beta.
|
|
26
|
-
"@rpgjs/physic": "5.0.0-beta.
|
|
27
|
-
"@rpgjs/testing": "5.0.0-beta.
|
|
25
|
+
"@rpgjs/common": "5.0.0-beta.6",
|
|
26
|
+
"@rpgjs/physic": "5.0.0-beta.6",
|
|
27
|
+
"@rpgjs/testing": "5.0.0-beta.6",
|
|
28
28
|
"@rpgjs/database": "^4.3.0",
|
|
29
29
|
"@signe/di": "^2.9.0",
|
|
30
30
|
"@signe/reactive": "^2.9.0",
|
package/src/Player/Player.ts
CHANGED
|
@@ -461,7 +461,7 @@ export class RpgPlayer extends BasicPlayerMixins(RpgCommonPlayer) {
|
|
|
461
461
|
const centerX = positions.x + width / 2;
|
|
462
462
|
const centerY = positions.y + height / 2;
|
|
463
463
|
|
|
464
|
-
this.map.physic.
|
|
464
|
+
this.map.physic.teleportEntity(entity, { x: centerX, y: centerY });
|
|
465
465
|
}
|
|
466
466
|
}
|
|
467
467
|
this.x.set(positions.x)
|
package/src/rooms/map.ts
CHANGED
|
@@ -24,7 +24,6 @@ import { Subject } from "rxjs";
|
|
|
24
24
|
import { BehaviorSubject } from "rxjs";
|
|
25
25
|
import { COEFFICIENT_ELEMENTS, DAMAGE_CRITICAL, DAMAGE_PHYSIC, DAMAGE_SKILL } from "../presets";
|
|
26
26
|
import { z } from "zod";
|
|
27
|
-
import { EntityState } from "@rpgjs/physic";
|
|
28
27
|
import { MapOptions } from "../decorators/map";
|
|
29
28
|
import { EventMode } from "../decorators/event";
|
|
30
29
|
import { BaseRoom } from "./BaseRoom";
|
|
@@ -1054,10 +1053,20 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
1054
1053
|
*/
|
|
1055
1054
|
@Action('action')
|
|
1056
1055
|
onAction(player: RpgPlayer, action: any) {
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1056
|
+
const direction =
|
|
1057
|
+
typeof player.getDirection === "function"
|
|
1058
|
+
? player.getDirection()
|
|
1059
|
+
: typeof player.direction === "function"
|
|
1060
|
+
? player.direction()
|
|
1061
|
+
: undefined;
|
|
1062
|
+
const collisions = new Set<string>((this as any).getCollisions(player.id));
|
|
1063
|
+
const interactionCollisions = (this as any).getInteractionCollisions?.(player.id, direction);
|
|
1064
|
+
if (Array.isArray(interactionCollisions)) {
|
|
1065
|
+
interactionCollisions.forEach(id => collisions.add(id));
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
const events = Array.from(collisions)
|
|
1069
|
+
.map(id => this.getEvent<RpgEvent>(id))
|
|
1061
1070
|
.filter((event): event is RpgEvent => !!event && this.isEventVisibleForPlayer(event, player));
|
|
1062
1071
|
if (events.length > 0) {
|
|
1063
1072
|
events.forEach(event => {
|
|
@@ -2500,18 +2509,15 @@ export class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoin {
|
|
|
2500
2509
|
const centerX = x + width / 2;
|
|
2501
2510
|
const centerY = y + height / 2;
|
|
2502
2511
|
|
|
2503
|
-
// Create static
|
|
2512
|
+
// Create static obstacle in physics engine
|
|
2504
2513
|
const entityId = `shape-${name}`;
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
width
|
|
2509
|
-
height
|
|
2510
|
-
|
|
2511
|
-
state: EntityState.Static,
|
|
2512
|
-
restitution: 0, // No bounce
|
|
2514
|
+
this.physic.createStaticObstacle(entityId, {
|
|
2515
|
+
x: centerX,
|
|
2516
|
+
y: centerY,
|
|
2517
|
+
width,
|
|
2518
|
+
height,
|
|
2519
|
+
restitution: 0,
|
|
2513
2520
|
});
|
|
2514
|
-
entity.freeze(); // Ensure it's frozen
|
|
2515
2521
|
|
|
2516
2522
|
// Build properties object
|
|
2517
2523
|
const properties: Record<string, any> = {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import { testing, TestingFixture } from "@rpgjs/testing";
|
|
3
|
+
import { Control, createModule, defineModule, Direction } from "@rpgjs/common";
|
|
4
|
+
import { RpgClient } from "../../client/src";
|
|
5
|
+
import { RpgPlayer, RpgServer } from "../src";
|
|
6
|
+
|
|
7
|
+
let actionCount = 0;
|
|
8
|
+
|
|
9
|
+
const serverModule = defineModule<RpgServer>({
|
|
10
|
+
maps: [
|
|
11
|
+
{
|
|
12
|
+
id: "interaction-map",
|
|
13
|
+
file: "",
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
player: {
|
|
17
|
+
async onConnected(player) {
|
|
18
|
+
await player.changeMap("interaction-map", { x: 100, y: 100 });
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const clientModule = defineModule<RpgClient>({});
|
|
24
|
+
|
|
25
|
+
let fixture: TestingFixture;
|
|
26
|
+
let client: any;
|
|
27
|
+
let player: RpgPlayer;
|
|
28
|
+
|
|
29
|
+
beforeEach(async () => {
|
|
30
|
+
actionCount = 0;
|
|
31
|
+
const myModule = createModule("ActionInteractionTestModule", [
|
|
32
|
+
{
|
|
33
|
+
server: serverModule,
|
|
34
|
+
client: clientModule,
|
|
35
|
+
},
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
fixture = await testing(myModule);
|
|
39
|
+
client = await fixture.createClient();
|
|
40
|
+
player = await client.waitForMapChange("interaction-map");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(async () => {
|
|
44
|
+
await fixture.clear();
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("Action interactions", () => {
|
|
48
|
+
test("triggers onAction for an event just in front of the player", async () => {
|
|
49
|
+
const map = player.getCurrentMap() as any;
|
|
50
|
+
const hitbox = player.hitbox();
|
|
51
|
+
|
|
52
|
+
await map.createDynamicEvent({
|
|
53
|
+
id: "front-event",
|
|
54
|
+
x: player.x(),
|
|
55
|
+
y: player.y() + hitbox.h + 2,
|
|
56
|
+
event: {
|
|
57
|
+
onAction() {
|
|
58
|
+
actionCount += 1;
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
await fixture.nextTick();
|
|
63
|
+
|
|
64
|
+
player.changeDirection(Direction.Down);
|
|
65
|
+
|
|
66
|
+
expect(map.getCollisions(player.id)).not.toContain("front-event");
|
|
67
|
+
expect(map.getInteractionCollisions(player.id, Direction.Down)).toContain("front-event");
|
|
68
|
+
|
|
69
|
+
map.onAction(player, { action: Control.Action });
|
|
70
|
+
await fixture.wait(0);
|
|
71
|
+
|
|
72
|
+
expect(actionCount).toBe(1);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("ignores a nearby event behind the player", async () => {
|
|
76
|
+
const map = player.getCurrentMap() as any;
|
|
77
|
+
const hitbox = player.hitbox();
|
|
78
|
+
|
|
79
|
+
await map.createDynamicEvent({
|
|
80
|
+
id: "behind-event",
|
|
81
|
+
x: player.x(),
|
|
82
|
+
y: player.y() - hitbox.h - 2,
|
|
83
|
+
event: {
|
|
84
|
+
onAction() {
|
|
85
|
+
actionCount += 1;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
await fixture.nextTick();
|
|
90
|
+
|
|
91
|
+
player.changeDirection(Direction.Down);
|
|
92
|
+
map.onAction(player, { action: Control.Action });
|
|
93
|
+
await fixture.wait(0);
|
|
94
|
+
|
|
95
|
+
expect(actionCount).toBe(0);
|
|
96
|
+
});
|
|
97
|
+
});
|
package/tests/item.spec.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { beforeEach, test, expect, afterEach, describe, vi } from "vitest";
|
|
2
2
|
import { testing, waitForSyncComplete, TestingFixture } from "@rpgjs/testing";
|
|
3
3
|
import { defineModule, createModule } from "@rpgjs/common";
|
|
4
|
-
import { RpgPlayer, RpgServer } from "../src";
|
|
4
|
+
import { RpgEvent, RpgPlayer, RpgServer } from "../src";
|
|
5
5
|
import { RpgClient } from "../../client/src";
|
|
6
6
|
import { ItemLog } from "../src/logs";
|
|
7
7
|
import type { ItemObject } from "../src/Player/ItemManager";
|
|
8
|
+
import { syncClass } from "@signe/sync";
|
|
8
9
|
|
|
9
10
|
// Define test items as objects for database
|
|
10
11
|
const TestPotion = {
|
|
@@ -124,6 +125,76 @@ describe("Item Management - Basic Operations", () => {
|
|
|
124
125
|
expect(item.quantity()).toBe(5);
|
|
125
126
|
expect(item.name()).toBe("Test Potion");
|
|
126
127
|
});
|
|
128
|
+
|
|
129
|
+
test("should sync cloneable event items and equipments initialized before sync", () => {
|
|
130
|
+
const event = new RpgEvent();
|
|
131
|
+
event.id = "enemy-event";
|
|
132
|
+
event.setMap(player.getCurrentMap()!);
|
|
133
|
+
event.execMethod = vi.fn() as any;
|
|
134
|
+
event.addItem("TestSword", 1);
|
|
135
|
+
event.equip("TestSword", true);
|
|
136
|
+
|
|
137
|
+
const packets: any[] = [];
|
|
138
|
+
const memory: Record<string, any> = {};
|
|
139
|
+
const setPath = (target: Record<string, any>, path: string, value: any) => {
|
|
140
|
+
const parts = path.split(".");
|
|
141
|
+
let current = target;
|
|
142
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
143
|
+
current = current[parts[i]] ??= {};
|
|
144
|
+
}
|
|
145
|
+
current[parts[parts.length - 1]] = value;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
syncClass(event, {
|
|
149
|
+
onSync(values) {
|
|
150
|
+
const packet: Record<string, any> = {};
|
|
151
|
+
for (const [path, value] of values) {
|
|
152
|
+
setPath(packet, path, value);
|
|
153
|
+
setPath(memory, path, value);
|
|
154
|
+
}
|
|
155
|
+
structuredClone({ type: "sync", value: packet });
|
|
156
|
+
packets.push(packet);
|
|
157
|
+
values.clear();
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
expect(packets).toContainEqual({
|
|
162
|
+
items: {
|
|
163
|
+
"0": expect.objectContaining({
|
|
164
|
+
id: "TestSword",
|
|
165
|
+
name: "Test Sword",
|
|
166
|
+
quantity: 1,
|
|
167
|
+
}),
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
expect(packets).toContainEqual({
|
|
171
|
+
equipments: {
|
|
172
|
+
"0": expect.objectContaining({
|
|
173
|
+
id: "TestSword",
|
|
174
|
+
name: "Test Sword",
|
|
175
|
+
quantity: 1,
|
|
176
|
+
}),
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
expect(memory).toEqual(
|
|
180
|
+
expect.objectContaining({
|
|
181
|
+
items: {
|
|
182
|
+
"0": expect.objectContaining({
|
|
183
|
+
id: "TestSword",
|
|
184
|
+
name: "Test Sword",
|
|
185
|
+
quantity: 1,
|
|
186
|
+
}),
|
|
187
|
+
},
|
|
188
|
+
equipments: {
|
|
189
|
+
"0": expect.objectContaining({
|
|
190
|
+
id: "TestSword",
|
|
191
|
+
name: "Test Sword",
|
|
192
|
+
quantity: 1,
|
|
193
|
+
}),
|
|
194
|
+
},
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
});
|
|
127
198
|
|
|
128
199
|
|
|
129
200
|
test("should add item using object", () => {
|
package/tests/move.spec.ts
CHANGED
|
@@ -48,6 +48,17 @@ afterEach(async () => {
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
describe("Move Routes - Basic Movements", () => {
|
|
51
|
+
test("should create player physics body with rectangular RPG hitbox and speed", async () => {
|
|
52
|
+
const map = player.getCurrentMap() as any;
|
|
53
|
+
const entity = map.getBody(player.id);
|
|
54
|
+
const hitbox = player.hitbox();
|
|
55
|
+
|
|
56
|
+
expect(entity).toBeDefined();
|
|
57
|
+
expect(entity.width).toBe(hitbox.w);
|
|
58
|
+
expect(entity.height).toBe(hitbox.h);
|
|
59
|
+
expect(entity.radius).toBe(0);
|
|
60
|
+
expect(entity.maxLinearVelocity).toBe(player.speed() * 50);
|
|
61
|
+
});
|
|
51
62
|
|
|
52
63
|
test("should move right using Direction enum", async () => {
|
|
53
64
|
const initialX = player.x();
|
|
@@ -117,6 +128,59 @@ describe("Move Routes - Basic Movements", () => {
|
|
|
117
128
|
});
|
|
118
129
|
});
|
|
119
130
|
|
|
131
|
+
describe("Map Shapes", () => {
|
|
132
|
+
test("should create static obstacle body for map shape", async () => {
|
|
133
|
+
const map = player.getCurrentMap() as any;
|
|
134
|
+
const shape = map.createShape({
|
|
135
|
+
x: 40,
|
|
136
|
+
y: 56,
|
|
137
|
+
width: 24,
|
|
138
|
+
height: 32,
|
|
139
|
+
name: "test-obstacle",
|
|
140
|
+
});
|
|
141
|
+
const entity = map.physic.getEntityByUUID("shape-test-obstacle");
|
|
142
|
+
|
|
143
|
+
expect(shape.name).toBe("test-obstacle");
|
|
144
|
+
expect(entity).toBeDefined();
|
|
145
|
+
expect(entity.position.x).toBe(52);
|
|
146
|
+
expect(entity.position.y).toBe(72);
|
|
147
|
+
expect(entity.width).toBe(24);
|
|
148
|
+
expect(entity.height).toBe(32);
|
|
149
|
+
expect(entity.isStatic()).toBe(true);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("should detect entities with moving hitbox sensor", async () => {
|
|
153
|
+
const map = player.getCurrentMap() as any;
|
|
154
|
+
const hitbox = player.hitbox();
|
|
155
|
+
const hitsPromise = new Promise<any[]>((resolve, reject) => {
|
|
156
|
+
let subscription: { unsubscribe: () => void };
|
|
157
|
+
const timeout = setTimeout(() => {
|
|
158
|
+
subscription.unsubscribe();
|
|
159
|
+
reject(new Error("moving hitbox did not detect player"));
|
|
160
|
+
}, 500);
|
|
161
|
+
subscription = map.createMovingHitbox([
|
|
162
|
+
{
|
|
163
|
+
x: player.x(),
|
|
164
|
+
y: player.y(),
|
|
165
|
+
width: hitbox.w,
|
|
166
|
+
height: hitbox.h,
|
|
167
|
+
},
|
|
168
|
+
]).subscribe({
|
|
169
|
+
next: (hits: any[]) => {
|
|
170
|
+
clearTimeout(timeout);
|
|
171
|
+
subscription.unsubscribe();
|
|
172
|
+
resolve(hits);
|
|
173
|
+
},
|
|
174
|
+
error: reject,
|
|
175
|
+
});
|
|
176
|
+
map.physic.getZoneManager().update();
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const hits = await hitsPromise;
|
|
180
|
+
expect(hits.map((hit) => hit.id)).toContain(player.id);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
120
184
|
describe("Move Routes - Move Helper Functions", () => {
|
|
121
185
|
test("should move right using Move.right()", async () => {
|
|
122
186
|
const initialX = player.x();
|
|
@@ -598,4 +662,4 @@ describe("Move Routes - Stuck Detection", () => {
|
|
|
598
662
|
// Player should have moved successfully
|
|
599
663
|
expect(player.x()).toBeGreaterThan(initialX);
|
|
600
664
|
});
|
|
601
|
-
});
|
|
665
|
+
});
|
package/tests/world-maps.spec.ts
CHANGED
|
@@ -396,6 +396,10 @@ describe('Map WorldMapsManager Integration', () => {
|
|
|
396
396
|
const worldY = player.worldPositionY()
|
|
397
397
|
expect(worldX).toBe(100)
|
|
398
398
|
expect(worldY).toBe(200)
|
|
399
|
+
const entity = map?.physic.getEntityByUUID(player.id)
|
|
400
|
+
const hitbox = player.hitbox()
|
|
401
|
+
expect(entity?.position.x).toBe(100 + hitbox.w / 2)
|
|
402
|
+
expect(entity?.position.y).toBe(200 + hitbox.h / 2)
|
|
399
403
|
|
|
400
404
|
// Change to map2 which is at worldX: 1024, worldY: 0
|
|
401
405
|
await player.changeMap('map2', { x: 50, y: 100 })
|