@rpgjs/server 5.0.0-alpha.29 → 5.0.0-alpha.30
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/Gui/DialogGui.d.ts +1 -0
- package/dist/Gui/GameoverGui.d.ts +23 -0
- package/dist/Gui/Gui.d.ts +6 -0
- package/dist/Gui/MenuGui.d.ts +22 -3
- package/dist/Gui/NotificationGui.d.ts +1 -2
- package/dist/Gui/SaveLoadGui.d.ts +13 -0
- package/dist/Gui/ShopGui.d.ts +24 -3
- package/dist/Gui/TitleGui.d.ts +23 -0
- package/dist/Gui/index.d.ts +9 -1
- package/dist/Player/GuiManager.d.ts +86 -3
- package/dist/Player/Player.d.ts +24 -2
- package/dist/RpgServer.d.ts +7 -0
- package/dist/RpgServerEngine.d.ts +2 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1621 -336
- package/dist/index.js.map +1 -1
- package/dist/presets/index.d.ts +0 -9
- package/dist/rooms/BaseRoom.d.ts +37 -0
- package/dist/rooms/lobby.d.ts +6 -1
- package/dist/rooms/map.d.ts +22 -1
- package/dist/services/save.d.ts +43 -0
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/localStorage.d.ts +23 -0
- package/package.json +10 -10
- package/src/Gui/DialogGui.ts +12 -2
- package/src/Gui/GameoverGui.ts +39 -0
- package/src/Gui/Gui.ts +23 -1
- package/src/Gui/MenuGui.ts +155 -6
- package/src/Gui/NotificationGui.ts +1 -2
- package/src/Gui/SaveLoadGui.ts +60 -0
- package/src/Gui/ShopGui.ts +145 -16
- package/src/Gui/TitleGui.ts +39 -0
- package/src/Gui/index.ts +13 -2
- package/src/Player/BattleManager.ts +1 -1
- package/src/Player/ClassManager.ts +57 -2
- package/src/Player/GuiManager.ts +125 -14
- package/src/Player/ItemManager.ts +160 -41
- package/src/Player/ParameterManager.ts +1 -1
- package/src/Player/Player.ts +87 -12
- package/src/Player/SkillManager.ts +145 -66
- package/src/Player/StateManager.ts +70 -1
- package/src/Player/VariableManager.ts +10 -7
- package/src/RpgServer.ts +8 -0
- package/src/index.ts +5 -2
- package/src/presets/index.ts +1 -10
- package/src/rooms/BaseRoom.ts +112 -0
- package/src/rooms/lobby.ts +13 -6
- package/src/rooms/map.ts +31 -4
- package/src/services/save.ts +147 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/localStorage.ts +76 -0
package/dist/presets/index.d.ts
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
1
|
-
export declare const MAXHP: string;
|
|
2
|
-
export declare const MAXSP: string;
|
|
3
|
-
export declare const ATK: string;
|
|
4
|
-
export declare const PDEF: string;
|
|
5
|
-
export declare const SDEF: string;
|
|
6
|
-
export declare const STR: string;
|
|
7
|
-
export declare const AGI: string;
|
|
8
|
-
export declare const INT: string;
|
|
9
|
-
export declare const DEX: string;
|
|
10
1
|
export declare const MAXHP_CURVE: {
|
|
11
2
|
start: number;
|
|
12
3
|
end: number;
|
package/dist/rooms/BaseRoom.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Hooks } from '../../../common/src';
|
|
2
|
+
import { RpgPlayer } from '../Player/Player';
|
|
1
3
|
/**
|
|
2
4
|
* Base class for rooms that need database functionality
|
|
3
5
|
*
|
|
@@ -35,6 +37,7 @@ export declare abstract class BaseRoom {
|
|
|
35
37
|
* ```
|
|
36
38
|
*/
|
|
37
39
|
database: import('@signe/reactive').WritableObjectSignal<{}>;
|
|
40
|
+
onStart(): Promise<void>;
|
|
38
41
|
/**
|
|
39
42
|
* Add data to the room's database
|
|
40
43
|
*
|
|
@@ -92,4 +95,38 @@ export declare abstract class BaseRoom {
|
|
|
92
95
|
* ```
|
|
93
96
|
*/
|
|
94
97
|
removeInDatabase(id: string): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Get the hooks system for this map
|
|
100
|
+
*
|
|
101
|
+
* Returns the dependency-injected Hooks instance that allows you to trigger
|
|
102
|
+
* and listen to various game events.
|
|
103
|
+
*
|
|
104
|
+
* @returns The Hooks instance for this map
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* // Trigger a custom hook
|
|
109
|
+
* map.hooks.callHooks('custom-event', data).subscribe();
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
get hooks(): Hooks;
|
|
113
|
+
/**
|
|
114
|
+
* Resolve complex snapshot entries (e.g. inventory items) before load.
|
|
115
|
+
*/
|
|
116
|
+
onSessionRestore({ userSnapshot, user }: {
|
|
117
|
+
userSnapshot: any;
|
|
118
|
+
user?: RpgPlayer;
|
|
119
|
+
}): Promise<any>;
|
|
120
|
+
listSaveSlots(player: RpgPlayer, value: {
|
|
121
|
+
requestId: string;
|
|
122
|
+
}): Promise<import('../../../common/src').SaveSlotList>;
|
|
123
|
+
saveSlot(player: RpgPlayer, value: {
|
|
124
|
+
requestId: string;
|
|
125
|
+
index: number;
|
|
126
|
+
meta?: any;
|
|
127
|
+
}): Promise<void>;
|
|
128
|
+
loadSlot(player: RpgPlayer, value: {
|
|
129
|
+
requestId: string;
|
|
130
|
+
index: number;
|
|
131
|
+
}): Promise<void>;
|
|
95
132
|
}
|
package/dist/rooms/lobby.d.ts
CHANGED
|
@@ -5,5 +5,10 @@ export declare class LobbyRoom extends BaseRoom {
|
|
|
5
5
|
players: import('@signe/reactive').WritableObjectSignal<{}>;
|
|
6
6
|
autoSync: boolean;
|
|
7
7
|
constructor(room: any);
|
|
8
|
-
onJoin(player: RpgPlayer, conn: MockConnection): void
|
|
8
|
+
onJoin(player: RpgPlayer, conn: MockConnection): Promise<void>;
|
|
9
|
+
guiInteraction(player: RpgPlayer, value: {
|
|
10
|
+
guiId: string;
|
|
11
|
+
name: string;
|
|
12
|
+
data: any;
|
|
13
|
+
}): Promise<void>;
|
|
9
14
|
}
|
package/dist/rooms/map.d.ts
CHANGED
|
@@ -168,6 +168,7 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
|
|
|
168
168
|
private _autoTickEnabled;
|
|
169
169
|
autoSync: boolean;
|
|
170
170
|
constructor(room: any);
|
|
171
|
+
onStart(): Promise<void>;
|
|
171
172
|
/**
|
|
172
173
|
* Setup collision detection between players, events, and shapes
|
|
173
174
|
*
|
|
@@ -301,6 +302,10 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
|
|
|
301
302
|
* ```
|
|
302
303
|
*/
|
|
303
304
|
get hooks(): Hooks;
|
|
305
|
+
onSessionRestore(payload: {
|
|
306
|
+
userSnapshot: any;
|
|
307
|
+
user?: RpgPlayer;
|
|
308
|
+
}): Promise<any>;
|
|
304
309
|
/**
|
|
305
310
|
* Handle GUI interaction from a player
|
|
306
311
|
*
|
|
@@ -316,7 +321,11 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
|
|
|
316
321
|
* // The interaction data is sent from the client
|
|
317
322
|
* ```
|
|
318
323
|
*/
|
|
319
|
-
guiInteraction(player: RpgPlayer, value:
|
|
324
|
+
guiInteraction(player: RpgPlayer, value: {
|
|
325
|
+
guiId: string;
|
|
326
|
+
name: string;
|
|
327
|
+
data: any;
|
|
328
|
+
}): Promise<void>;
|
|
320
329
|
/**
|
|
321
330
|
* Handle GUI exit from a player
|
|
322
331
|
*
|
|
@@ -382,6 +391,18 @@ export declare class RpgMap extends RpgCommonMap<RpgPlayer> implements RoomOnJoi
|
|
|
382
391
|
* ```
|
|
383
392
|
*/
|
|
384
393
|
onInput(player: RpgPlayer, input: any): Promise<void>;
|
|
394
|
+
saveSlot(player: RpgPlayer, value: {
|
|
395
|
+
requestId: string;
|
|
396
|
+
index: number;
|
|
397
|
+
meta?: any;
|
|
398
|
+
}): Promise<void>;
|
|
399
|
+
loadSlot(player: RpgPlayer, value: {
|
|
400
|
+
requestId: string;
|
|
401
|
+
index: number;
|
|
402
|
+
}): Promise<void>;
|
|
403
|
+
listSaveSlots(player: RpgPlayer, value: {
|
|
404
|
+
requestId: string;
|
|
405
|
+
}): Promise<import('../../../common/src').SaveSlotList>;
|
|
385
406
|
/**
|
|
386
407
|
* Update the map configuration and data
|
|
387
408
|
*
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { SaveSlot, SaveSlotList, SaveSlotMeta } from '../../../common/src';
|
|
2
|
+
import { RpgPlayer } from '../Player/Player';
|
|
3
|
+
export declare const SaveStorageToken = "SaveStorageToken";
|
|
4
|
+
export type SaveSlotIndex = number | "auto";
|
|
5
|
+
export interface SaveRequestContext {
|
|
6
|
+
reason?: "manual" | "auto" | "load";
|
|
7
|
+
source?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface AutoSaveStrategy {
|
|
10
|
+
canSave?: (player: RpgPlayer, context?: SaveRequestContext) => boolean;
|
|
11
|
+
canLoad?: (player: RpgPlayer, context?: SaveRequestContext) => boolean;
|
|
12
|
+
shouldAutoSave?: (player: RpgPlayer, context?: SaveRequestContext) => boolean;
|
|
13
|
+
getDefaultSlot?: (player: RpgPlayer, context?: SaveRequestContext) => number | null;
|
|
14
|
+
}
|
|
15
|
+
export interface SaveStorageStrategy {
|
|
16
|
+
list(player: RpgPlayer): Promise<SaveSlotList>;
|
|
17
|
+
get(player: RpgPlayer, index: number): Promise<SaveSlot | null>;
|
|
18
|
+
save(player: RpgPlayer, index: number, snapshot: string, meta: SaveSlotMeta): Promise<void>;
|
|
19
|
+
delete?(player: RpgPlayer, index: number): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
export declare class InMemorySaveStorageStrategy implements SaveStorageStrategy {
|
|
22
|
+
private slotsByPlayer;
|
|
23
|
+
list(player: RpgPlayer): Promise<SaveSlotList>;
|
|
24
|
+
get(player: RpgPlayer, index: number): Promise<SaveSlot | null>;
|
|
25
|
+
save(player: RpgPlayer, index: number, snapshot: string, meta: SaveSlotMeta): Promise<void>;
|
|
26
|
+
delete(player: RpgPlayer, index: number): Promise<void>;
|
|
27
|
+
private getSlots;
|
|
28
|
+
private stripSnapshots;
|
|
29
|
+
}
|
|
30
|
+
export declare const AutoSaveToken = "AutoSaveToken";
|
|
31
|
+
export declare function resolveSaveStorageStrategy(): SaveStorageStrategy;
|
|
32
|
+
export declare function resolveAutoSaveStrategy(): AutoSaveStrategy;
|
|
33
|
+
export declare function resolveSaveSlot(slot: SaveSlotIndex | undefined, policy: AutoSaveStrategy, player: RpgPlayer, context?: SaveRequestContext): number | null;
|
|
34
|
+
export declare function shouldAutoSave(player: RpgPlayer, context?: SaveRequestContext): boolean;
|
|
35
|
+
export declare function buildSaveSlotMeta(player: RpgPlayer, overrides?: SaveSlotMeta): SaveSlotMeta;
|
|
36
|
+
export declare function provideSaveStorage(strategy: SaveStorageStrategy): {
|
|
37
|
+
provide: string;
|
|
38
|
+
useValue: SaveStorageStrategy;
|
|
39
|
+
};
|
|
40
|
+
export declare function provideAutoSave(strategy: AutoSaveStrategy): {
|
|
41
|
+
provide: string;
|
|
42
|
+
useValue: AutoSaveStrategy;
|
|
43
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './localStorage';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { SaveSlot, SaveSlotList, SaveSlotMeta } from '../../../common/src';
|
|
2
|
+
import { SaveStorageStrategy } from '../services/save';
|
|
3
|
+
import { RpgPlayer } from '../Player/Player';
|
|
4
|
+
export interface LocalStorageSaveStorageOptions {
|
|
5
|
+
key?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Save storage strategy backed by browser localStorage.
|
|
9
|
+
*
|
|
10
|
+
* Intended for standalone mode where the server runs in the browser
|
|
11
|
+
* and localStorage is available.
|
|
12
|
+
*/
|
|
13
|
+
export declare class LocalStorageSaveStorageStrategy implements SaveStorageStrategy {
|
|
14
|
+
private key;
|
|
15
|
+
constructor(options?: LocalStorageSaveStorageOptions);
|
|
16
|
+
list(_player: RpgPlayer): Promise<SaveSlotList>;
|
|
17
|
+
get(_player: RpgPlayer, index: number): Promise<SaveSlot | null>;
|
|
18
|
+
save(_player: RpgPlayer, index: number, snapshot: string, meta: SaveSlotMeta): Promise<void>;
|
|
19
|
+
delete(_player: RpgPlayer, index: number): Promise<void>;
|
|
20
|
+
private readSlots;
|
|
21
|
+
private writeSlots;
|
|
22
|
+
private stripSnapshots;
|
|
23
|
+
}
|
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.30",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"types": "./dist/index.d.ts",
|
|
6
6
|
"publishConfig": {
|
|
@@ -11,19 +11,19 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"description": "",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@rpgjs/common": "5.0.0-alpha.
|
|
15
|
-
"@rpgjs/physic": "5.0.0-alpha.
|
|
16
|
-
"@rpgjs/testing": "5.0.0-alpha.
|
|
14
|
+
"@rpgjs/common": "5.0.0-alpha.30",
|
|
15
|
+
"@rpgjs/physic": "5.0.0-alpha.30",
|
|
16
|
+
"@rpgjs/testing": "5.0.0-alpha.30",
|
|
17
17
|
"@rpgjs/database": "^4.3.0",
|
|
18
|
-
"@signe/di": "^2.
|
|
19
|
-
"@signe/reactive": "^2.
|
|
20
|
-
"@signe/room": "^2.
|
|
21
|
-
"@signe/sync": "^2.
|
|
18
|
+
"@signe/di": "^2.8.2",
|
|
19
|
+
"@signe/reactive": "^2.8.2",
|
|
20
|
+
"@signe/room": "^2.8.2",
|
|
21
|
+
"@signe/sync": "^2.8.2",
|
|
22
22
|
"rxjs": "^7.8.2",
|
|
23
|
-
"zod": "^4.
|
|
23
|
+
"zod": "^4.3.5"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"vite": "^7.3.
|
|
26
|
+
"vite": "^7.3.1",
|
|
27
27
|
"vite-plugin-dts": "^4.5.4"
|
|
28
28
|
},
|
|
29
29
|
"type": "module",
|
package/src/Gui/DialogGui.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface DialogOptions {
|
|
|
19
19
|
tranparent?: boolean,
|
|
20
20
|
typewriterEffect?: boolean,
|
|
21
21
|
talkWith?: RpgPlayer,
|
|
22
|
+
speaker?: string,
|
|
22
23
|
face?: {
|
|
23
24
|
id: string,
|
|
24
25
|
expression: string
|
|
@@ -34,9 +35,17 @@ export class DialogGui extends Gui {
|
|
|
34
35
|
if (!options.choices) options.choices = []
|
|
35
36
|
if (options.autoClose == undefined) options.autoClose = false
|
|
36
37
|
if (!options.position) options.position = DialogPosition.Bottom
|
|
37
|
-
if (options.fullWidth == undefined) options.fullWidth =
|
|
38
|
+
if (options.fullWidth == undefined) options.fullWidth = false
|
|
38
39
|
if (options.typewriterEffect == undefined) options.typewriterEffect = true
|
|
39
40
|
const event = options.talkWith
|
|
41
|
+
const resolveName = (target?: RpgPlayer): string | undefined => {
|
|
42
|
+
if (!target) return undefined
|
|
43
|
+
const rawName = (target as any).name
|
|
44
|
+
if (typeof rawName === 'function') return rawName()
|
|
45
|
+
if (rawName && typeof rawName.get === 'function') return rawName.get()
|
|
46
|
+
return rawName
|
|
47
|
+
}
|
|
48
|
+
const speaker = options.speaker ?? resolveName(event)
|
|
40
49
|
let memoryDir
|
|
41
50
|
if (event) {
|
|
42
51
|
memoryDir = event.direction()
|
|
@@ -48,6 +57,7 @@ export class DialogGui extends Gui {
|
|
|
48
57
|
position: options.position,
|
|
49
58
|
fullWidth: options.fullWidth,
|
|
50
59
|
typewriterEffect: options.typewriterEffect,
|
|
60
|
+
speaker,
|
|
51
61
|
// remove value property. It is not useful to know this on the client side.
|
|
52
62
|
choices: options.choices.map(choice => ({
|
|
53
63
|
text: choice.text
|
|
@@ -68,4 +78,4 @@ export class DialogGui extends Gui {
|
|
|
68
78
|
return val
|
|
69
79
|
})
|
|
70
80
|
}
|
|
71
|
-
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { PrebuiltGui } from '@rpgjs/common'
|
|
2
|
+
import { Gui } from './Gui'
|
|
3
|
+
import { RpgPlayer } from '../Player/Player'
|
|
4
|
+
|
|
5
|
+
export interface GameoverEntry {
|
|
6
|
+
id: string
|
|
7
|
+
label: string
|
|
8
|
+
disabled?: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface GameoverGuiOptions {
|
|
12
|
+
entries?: GameoverEntry[]
|
|
13
|
+
title?: string
|
|
14
|
+
subtitle?: string
|
|
15
|
+
saveLoad?: Record<string, any>
|
|
16
|
+
localActions?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface GameoverGuiSelection {
|
|
20
|
+
id?: string
|
|
21
|
+
index?: number
|
|
22
|
+
entry?: GameoverEntry
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class GameoverGui extends Gui {
|
|
26
|
+
constructor(player: RpgPlayer) {
|
|
27
|
+
super(PrebuiltGui.Gameover, player)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
open(options: GameoverGuiOptions = {}): Promise<GameoverGuiSelection | null> {
|
|
31
|
+
this.on('select', (selection: GameoverGuiSelection) => {
|
|
32
|
+
this.close(selection)
|
|
33
|
+
})
|
|
34
|
+
return super.open(options, {
|
|
35
|
+
waitingAction: true,
|
|
36
|
+
blockPlayerInput: true
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/Gui/Gui.ts
CHANGED
|
@@ -4,6 +4,7 @@ export class Gui {
|
|
|
4
4
|
|
|
5
5
|
private _close: Function = () => {}
|
|
6
6
|
private _blockPlayerInput: boolean = false
|
|
7
|
+
private _events = new Map<string, (data: any) => void>()
|
|
7
8
|
|
|
8
9
|
constructor(
|
|
9
10
|
public id: string,
|
|
@@ -34,6 +35,19 @@ export class Gui {
|
|
|
34
35
|
})
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
on(event: string, callback: (data: any) => void) {
|
|
39
|
+
this._events.set(event, callback)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async emit(event: string, data: any): Promise<any> {
|
|
43
|
+
const callback = this._events.get(event)
|
|
44
|
+
if (callback) {
|
|
45
|
+
return await callback(data)
|
|
46
|
+
} else {
|
|
47
|
+
return null
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
close(data?) {
|
|
38
52
|
this.player.emit('gui.exit', this.id)
|
|
39
53
|
if (this._blockPlayerInput) {
|
|
@@ -41,4 +55,12 @@ export class Gui {
|
|
|
41
55
|
}
|
|
42
56
|
this._close(data)
|
|
43
57
|
}
|
|
44
|
-
|
|
58
|
+
|
|
59
|
+
update(data?, { clientActionId }: { clientActionId?: string } = {}) {
|
|
60
|
+
this.player.emit('gui.update', {
|
|
61
|
+
guiId: this.id,
|
|
62
|
+
data,
|
|
63
|
+
clientActionId
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/Gui/MenuGui.ts
CHANGED
|
@@ -1,15 +1,137 @@
|
|
|
1
1
|
import { PrebuiltGui } from '@rpgjs/common'
|
|
2
2
|
import { Gui } from './Gui'
|
|
3
3
|
import { RpgPlayer } from '../Player/Player'
|
|
4
|
-
import {
|
|
4
|
+
import { SaveLoadGui, SaveSlot } from './SaveLoadGui'
|
|
5
|
+
import { resolveAutoSaveStrategy } from '../services/save'
|
|
6
|
+
|
|
7
|
+
export type MenuEntryId = 'items' | 'skills' | 'equip' | 'options' | 'save' | 'exit'
|
|
8
|
+
|
|
9
|
+
export interface MenuEntry {
|
|
10
|
+
id: MenuEntryId
|
|
11
|
+
label: string
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface MenuGuiOptions {
|
|
16
|
+
menus?: MenuEntry[]
|
|
17
|
+
disabled?: MenuEntryId[]
|
|
18
|
+
saveSlots?: SaveSlot[]
|
|
19
|
+
saveMaxSlots?: number
|
|
20
|
+
saveShowAutoSlot?: boolean
|
|
21
|
+
saveAutoSlotIndex?: number
|
|
22
|
+
saveAutoSlotLabel?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class MenuGui extends Gui {
|
|
26
|
+
private menuOptions: MenuGuiOptions = {}
|
|
5
27
|
|
|
6
|
-
export class MenuGui extends Gui implements IGui {
|
|
7
28
|
constructor(player: RpgPlayer) {
|
|
8
29
|
super(PrebuiltGui.MainMenu, player)
|
|
9
30
|
}
|
|
10
31
|
|
|
11
|
-
|
|
12
|
-
|
|
32
|
+
private buildSaveLoad(options: MenuGuiOptions) {
|
|
33
|
+
const autoSave = resolveAutoSaveStrategy()
|
|
34
|
+
const canSave = autoSave.canSave ? autoSave.canSave(this.player, { reason: "manual", source: "menu" }) : true
|
|
35
|
+
const autoSlotIndex = options.saveAutoSlotIndex ?? autoSave.getDefaultSlot?.(this.player, { reason: "auto", source: "menu" }) ?? 0
|
|
36
|
+
return {
|
|
37
|
+
mode: 'save',
|
|
38
|
+
canSave,
|
|
39
|
+
showAutoSlot: options.saveShowAutoSlot === true,
|
|
40
|
+
autoSlotIndex,
|
|
41
|
+
autoSlotLabel: options.saveAutoSlotLabel
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private buildMenuData(options: MenuGuiOptions) {
|
|
46
|
+
const disabledSet = new Set(options.disabled || [])
|
|
47
|
+
const defaultMenus: MenuEntry[] = [
|
|
48
|
+
{ id: 'items', label: 'Items' },
|
|
49
|
+
{ id: 'skills', label: 'Skills' },
|
|
50
|
+
{ id: 'equip', label: 'Equip' },
|
|
51
|
+
{ id: 'options', label: 'Options' },
|
|
52
|
+
{ id: 'save', label: 'Save' },
|
|
53
|
+
{ id: 'exit', label: 'Exit' }
|
|
54
|
+
]
|
|
55
|
+
const menus = (options.menus && options.menus.length ? options.menus : defaultMenus)
|
|
56
|
+
.map(menu => ({
|
|
57
|
+
...menu,
|
|
58
|
+
disabled: menu.disabled || disabledSet.has(menu.id)
|
|
59
|
+
}))
|
|
60
|
+
|
|
61
|
+
const player = this.player as any
|
|
62
|
+
const databaseById = player.databaseById?.bind(player)
|
|
63
|
+
const equippedIds = new Set(
|
|
64
|
+
(player.equipments?.() || []).map((it) => it?.id?.() ?? it?.id ?? it?.name)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
const buildStats = () => {
|
|
68
|
+
const params = player.param || {}
|
|
69
|
+
const statKeys = [
|
|
70
|
+
'str',
|
|
71
|
+
'dex',
|
|
72
|
+
'int',
|
|
73
|
+
'agi',
|
|
74
|
+
'maxHp',
|
|
75
|
+
'maxSp'
|
|
76
|
+
]
|
|
77
|
+
const stats: Record<string, number> = {}
|
|
78
|
+
statKeys.forEach((key) => {
|
|
79
|
+
stats[key] = params[key] ?? 0
|
|
80
|
+
})
|
|
81
|
+
stats.pdef = player.pdef ?? params.pdef ?? 0
|
|
82
|
+
stats.sdef = player.sdef ?? params.sdef ?? 0
|
|
83
|
+
stats.atk = player.atk ?? params.atk ?? 0
|
|
84
|
+
return stats
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const items = (player.items?.() || []).map((item) => {
|
|
88
|
+
const id = item.id()
|
|
89
|
+
const data = databaseById ? databaseById(id) : {}
|
|
90
|
+
const type = data?._type ?? 'item'
|
|
91
|
+
const consumable = data?.consumable
|
|
92
|
+
const isConsumable = consumable !== undefined ? consumable : type === 'item'
|
|
93
|
+
const usable = isConsumable === false
|
|
94
|
+
? false
|
|
95
|
+
: consumable === undefined && type !== 'item'
|
|
96
|
+
? false
|
|
97
|
+
: true
|
|
98
|
+
return {
|
|
99
|
+
id,
|
|
100
|
+
name: item.name(),
|
|
101
|
+
description: item.description(),
|
|
102
|
+
quantity: item.quantity(),
|
|
103
|
+
icon: data?.icon ?? (item as any)?.icon,
|
|
104
|
+
atk: item.atk(),
|
|
105
|
+
pdef: item.pdef(),
|
|
106
|
+
sdef: item.sdef(),
|
|
107
|
+
consumable: isConsumable,
|
|
108
|
+
type,
|
|
109
|
+
usable,
|
|
110
|
+
equipped: equippedIds.has(id)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
const menuEquips = items.filter((item) => item.type === 'weapon' || item.type === 'armor')
|
|
114
|
+
const skills = (player.skills?.() || []).map((skill) => ({
|
|
115
|
+
id: skill?.id() ?? skill?.name(),
|
|
116
|
+
name: skill?.name() ?? skill?.id() ?? 'Skill',
|
|
117
|
+
description: skill?.description() ?? '',
|
|
118
|
+
spCost: skill?.spCost() ?? 0
|
|
119
|
+
}))
|
|
120
|
+
const saveLoad = this.buildSaveLoad(options)
|
|
121
|
+
|
|
122
|
+
return { menus, items, equips: menuEquips, skills, saveLoad, playerStats: buildStats() }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private refreshMenu(clientActionId?: string) {
|
|
126
|
+
const data = this.buildMenuData(this.menuOptions)
|
|
127
|
+
this.update(data, { clientActionId })
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
open(options: MenuGuiOptions = {}) {
|
|
131
|
+
this.menuOptions = options
|
|
132
|
+
const data = this.buildMenuData(options)
|
|
133
|
+
|
|
134
|
+
this.on('useItem', ({ id, clientActionId }) => {
|
|
13
135
|
try {
|
|
14
136
|
this.player.useItem(id)
|
|
15
137
|
this.player.syncChanges()
|
|
@@ -17,10 +139,37 @@ export class MenuGui extends Gui implements IGui {
|
|
|
17
139
|
catch (err: any) {
|
|
18
140
|
this.player.showNotification(err.msg)
|
|
19
141
|
}
|
|
142
|
+
finally {
|
|
143
|
+
this.refreshMenu(clientActionId)
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
this.on('equipItem', ({ id, equip, clientActionId }) => {
|
|
147
|
+
try {
|
|
148
|
+
this.player.equip(id, equip)
|
|
149
|
+
this.player.syncChanges()
|
|
150
|
+
}
|
|
151
|
+
catch (err: any) {
|
|
152
|
+
this.player.showNotification(err.msg)
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
this.refreshMenu(clientActionId)
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
this.on('openSave', async () => {
|
|
159
|
+
this.close()
|
|
160
|
+
const gui = new SaveLoadGui(this.player)
|
|
161
|
+
player._gui[gui.id] = gui
|
|
162
|
+
await gui.open(options.saveSlots || [], {
|
|
163
|
+
mode: 'save',
|
|
164
|
+
maxSlots: options.saveMaxSlots
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
this.on('exit', () => {
|
|
168
|
+
this.close('exit')
|
|
20
169
|
})
|
|
21
|
-
return super.open(
|
|
170
|
+
return super.open(data, {
|
|
22
171
|
waitingAction: true,
|
|
23
172
|
blockPlayerInput: true
|
|
24
173
|
})
|
|
25
174
|
}
|
|
26
|
-
}
|
|
175
|
+
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { PrebuiltGui } from '@rpgjs/common'
|
|
2
2
|
import { Gui } from './Gui'
|
|
3
3
|
import { RpgPlayer } from '../Player/Player'
|
|
4
|
-
import { IGui } from '../Interfaces/Gui'
|
|
5
4
|
|
|
6
|
-
export class NotificationGui extends Gui
|
|
5
|
+
export class NotificationGui extends Gui {
|
|
7
6
|
constructor(player: RpgPlayer) {
|
|
8
7
|
super(PrebuiltGui.Notification, player)
|
|
9
8
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { PrebuiltGui } from '@rpgjs/common'
|
|
2
|
+
import type { SaveSlot } from '@rpgjs/common'
|
|
3
|
+
import { Gui } from './Gui'
|
|
4
|
+
import { RpgPlayer } from '../Player/Player'
|
|
5
|
+
|
|
6
|
+
export type SaveLoadMode = 'save' | 'load'
|
|
7
|
+
|
|
8
|
+
export type { SaveSlot } from '@rpgjs/common'
|
|
9
|
+
|
|
10
|
+
export interface SaveLoadOptions {
|
|
11
|
+
mode?: SaveLoadMode
|
|
12
|
+
maxSlots?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class SaveLoadGui extends Gui {
|
|
16
|
+
constructor(player: RpgPlayer) {
|
|
17
|
+
super(PrebuiltGui.Save, player)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
open(slots: SaveSlot[] = [], options: SaveLoadOptions = {}): Promise<number | null> {
|
|
21
|
+
const mode = options.mode || 'load'
|
|
22
|
+
const maxSlots = options.maxSlots ?? slots.length
|
|
23
|
+
const normalizedSlots = Array.from({ length: maxSlots }, (_, index) => slots[index] ?? null)
|
|
24
|
+
const uiSlots = normalizedSlots.map((slot) => {
|
|
25
|
+
if (!slot) return null
|
|
26
|
+
const { snapshot, ...data } = slot
|
|
27
|
+
return data
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const onSelect = async ({ index }) => {
|
|
31
|
+
if (typeof index !== 'number') return
|
|
32
|
+
if (index < 0 || index >= normalizedSlots.length) return
|
|
33
|
+
const slot = normalizedSlots[index]
|
|
34
|
+
if (mode === 'load') {
|
|
35
|
+
const result = await this.player.load(index, { reason: "load", source: "gui" }, { changeMap: true })
|
|
36
|
+
if (!result.ok) return
|
|
37
|
+
this.close(index)
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
if (mode === 'save') {
|
|
41
|
+
const result = await this.player.save(index, {}, { reason: "manual", source: "gui" })
|
|
42
|
+
if (!result) return
|
|
43
|
+
const updatedSlot: SaveSlot = {
|
|
44
|
+
...(slot || {}),
|
|
45
|
+
...result.meta
|
|
46
|
+
}
|
|
47
|
+
normalizedSlots[index] = updatedSlot
|
|
48
|
+
slots[index] = updatedSlot
|
|
49
|
+
this.close(index)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.on('save', onSelect)
|
|
53
|
+
this.on('load', onSelect)
|
|
54
|
+
this.on('select', onSelect)
|
|
55
|
+
return super.open({ slots: uiSlots, mode }, {
|
|
56
|
+
waitingAction: true,
|
|
57
|
+
blockPlayerInput: true
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
}
|