@rpgjs/server 5.0.0-alpha.8 → 5.0.0-beta.1

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.
Files changed (116) hide show
  1. package/dist/Gui/DialogGui.d.ts +5 -0
  2. package/dist/Gui/GameoverGui.d.ts +23 -0
  3. package/dist/Gui/Gui.d.ts +6 -0
  4. package/dist/Gui/MenuGui.d.ts +22 -3
  5. package/dist/Gui/NotificationGui.d.ts +1 -2
  6. package/dist/Gui/SaveLoadGui.d.ts +13 -0
  7. package/dist/Gui/ShopGui.d.ts +28 -3
  8. package/dist/Gui/TitleGui.d.ts +23 -0
  9. package/dist/Gui/index.d.ts +10 -1
  10. package/dist/Player/BattleManager.d.ts +44 -32
  11. package/dist/Player/ClassManager.d.ts +24 -4
  12. package/dist/Player/ComponentManager.d.ts +100 -7
  13. package/dist/Player/Components.d.ts +345 -0
  14. package/dist/Player/EffectManager.d.ts +50 -4
  15. package/dist/Player/ElementManager.d.ts +77 -4
  16. package/dist/Player/GoldManager.d.ts +1 -1
  17. package/dist/Player/GuiManager.d.ts +233 -5
  18. package/dist/Player/ItemFixture.d.ts +1 -1
  19. package/dist/Player/ItemManager.d.ts +431 -4
  20. package/dist/Player/MoveManager.d.ts +301 -34
  21. package/dist/Player/ParameterManager.d.ts +364 -28
  22. package/dist/Player/Player.d.ts +558 -14
  23. package/dist/Player/SkillManager.d.ts +187 -13
  24. package/dist/Player/StateManager.d.ts +75 -4
  25. package/dist/Player/VariableManager.d.ts +62 -4
  26. package/dist/RpgServer.d.ts +278 -63
  27. package/dist/RpgServerEngine.d.ts +2 -1
  28. package/dist/decorators/event.d.ts +46 -0
  29. package/dist/decorators/map.d.ts +299 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.js +17920 -29866
  32. package/dist/index.js.map +1 -1
  33. package/dist/logs/log.d.ts +2 -3
  34. package/dist/module-CaCW1SDh.js +11018 -0
  35. package/dist/module-CaCW1SDh.js.map +1 -0
  36. package/dist/module.d.ts +43 -1
  37. package/dist/node/connection.d.ts +51 -0
  38. package/dist/node/index.d.ts +5 -0
  39. package/dist/node/index.js +551 -0
  40. package/dist/node/index.js.map +1 -0
  41. package/dist/node/map.d.ts +16 -0
  42. package/dist/node/room.d.ts +21 -0
  43. package/dist/node/transport.d.ts +28 -0
  44. package/dist/node/types.d.ts +47 -0
  45. package/dist/presets/index.d.ts +0 -9
  46. package/dist/rooms/BaseRoom.d.ts +132 -0
  47. package/dist/rooms/lobby.d.ts +10 -2
  48. package/dist/rooms/map.d.ts +1359 -32
  49. package/dist/services/save.d.ts +43 -0
  50. package/dist/storage/index.d.ts +1 -0
  51. package/dist/storage/localStorage.d.ts +23 -0
  52. package/package.json +25 -10
  53. package/src/Gui/DialogGui.ts +19 -4
  54. package/src/Gui/GameoverGui.ts +39 -0
  55. package/src/Gui/Gui.ts +23 -1
  56. package/src/Gui/MenuGui.ts +155 -6
  57. package/src/Gui/NotificationGui.ts +1 -2
  58. package/src/Gui/SaveLoadGui.ts +60 -0
  59. package/src/Gui/ShopGui.ts +146 -16
  60. package/src/Gui/TitleGui.ts +39 -0
  61. package/src/Gui/index.ts +15 -2
  62. package/src/Player/BattleManager.ts +39 -56
  63. package/src/Player/ClassManager.ts +82 -74
  64. package/src/Player/ComponentManager.ts +401 -37
  65. package/src/Player/Components.ts +380 -0
  66. package/src/Player/EffectManager.ts +50 -96
  67. package/src/Player/ElementManager.ts +74 -152
  68. package/src/Player/GuiManager.ts +284 -149
  69. package/src/Player/ItemManager.ts +747 -341
  70. package/src/Player/MoveManager.ts +1532 -750
  71. package/src/Player/ParameterManager.ts +636 -106
  72. package/src/Player/Player.ts +1273 -79
  73. package/src/Player/SkillManager.ts +558 -197
  74. package/src/Player/StateManager.ts +131 -258
  75. package/src/Player/VariableManager.ts +85 -157
  76. package/src/RpgServer.ts +293 -62
  77. package/src/decorators/event.ts +61 -0
  78. package/src/decorators/map.ts +343 -0
  79. package/src/index.ts +11 -1
  80. package/src/logs/log.ts +10 -3
  81. package/src/module.ts +126 -3
  82. package/src/node/connection.ts +254 -0
  83. package/src/node/index.ts +22 -0
  84. package/src/node/map.ts +328 -0
  85. package/src/node/room.ts +63 -0
  86. package/src/node/transport.ts +532 -0
  87. package/src/node/types.ts +61 -0
  88. package/src/presets/index.ts +1 -10
  89. package/src/rooms/BaseRoom.ts +232 -0
  90. package/src/rooms/lobby.ts +25 -7
  91. package/src/rooms/map.ts +2682 -206
  92. package/src/services/save.ts +147 -0
  93. package/src/storage/index.ts +1 -0
  94. package/src/storage/localStorage.ts +76 -0
  95. package/tests/battle.spec.ts +375 -0
  96. package/tests/change-map.spec.ts +72 -0
  97. package/tests/class.spec.ts +274 -0
  98. package/tests/custom-websocket.spec.ts +127 -0
  99. package/tests/effect.spec.ts +219 -0
  100. package/tests/element.spec.ts +221 -0
  101. package/tests/event.spec.ts +80 -0
  102. package/tests/gold.spec.ts +99 -0
  103. package/tests/item.spec.ts +609 -0
  104. package/tests/module.spec.ts +38 -0
  105. package/tests/move.spec.ts +601 -0
  106. package/tests/node-transport.spec.ts +223 -0
  107. package/tests/player-param.spec.ts +45 -0
  108. package/tests/prediction-reconciliation.spec.ts +182 -0
  109. package/tests/random-move.spec.ts +65 -0
  110. package/tests/skill.spec.ts +658 -0
  111. package/tests/state.spec.ts +467 -0
  112. package/tests/variable.spec.ts +185 -0
  113. package/tests/world-maps.spec.ts +896 -0
  114. package/vite.config.ts +36 -3
  115. package/dist/Player/Event.d.ts +0 -0
  116. package/src/Player/Event.ts +0 -0
@@ -1,27 +1,144 @@
1
1
  import {
2
- Constructor,
3
2
  isArray,
4
3
  isInstanceOf,
5
4
  isString,
6
5
  PlayerCtor,
7
- RpgCommonPlayer,
6
+ Skill,
8
7
  } from "@rpgjs/common";
9
8
  import { SkillLog } from "../logs";
10
9
  import { RpgPlayer } from "./Player";
11
10
  import { Effect } from "./EffectManager";
12
11
 
13
12
  /**
14
- * Interface defining dependencies from other mixins that SkillManager needs
13
+ * Type for skill class constructor
15
14
  */
16
- interface SkillManagerDependencies {
17
- sp: number;
18
- skills(): any[];
19
- hasEffect(effect: string): boolean;
20
- databaseById(id: string): any;
21
- applyStates(player: RpgPlayer, skill: any): void;
15
+ type SkillClass = { new (...args: any[]): any };
16
+
17
+ /**
18
+ * Interface defining the hooks that can be implemented on skill classes or objects
19
+ *
20
+ * These hooks are called at specific moments during the skill lifecycle:
21
+ * - `onLearn`: When the skill is learned by the player
22
+ * - `onUse`: When the skill is successfully used
23
+ * - `onUseFailed`: When the skill usage fails (e.g., chance roll failed)
24
+ * - `onForget`: When the skill is forgotten
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const skillHooks: SkillHooks = {
29
+ * onLearn(player) {
30
+ * console.log('Skill learned!');
31
+ * },
32
+ * onUse(player, target) {
33
+ * console.log('Skill used on target');
34
+ * }
35
+ * };
36
+ * ```
37
+ */
38
+ export interface SkillHooks {
39
+ /**
40
+ * Called when the skill is learned by the player
41
+ *
42
+ * @param player - The player learning the skill
43
+ */
44
+ onLearn?: (player: RpgPlayer) => void | Promise<void>;
45
+
46
+ /**
47
+ * Called when the skill is successfully used
48
+ *
49
+ * @param player - The player using the skill
50
+ * @param target - The target player(s) if any
51
+ */
52
+ onUse?: (player: RpgPlayer, target?: RpgPlayer | RpgPlayer[]) => void | Promise<void>;
53
+
54
+ /**
55
+ * Called when the skill usage fails (e.g., chance roll failed)
56
+ *
57
+ * @param player - The player attempting to use the skill
58
+ * @param target - The intended target player(s) if any
59
+ */
60
+ onUseFailed?: (player: RpgPlayer, target?: RpgPlayer | RpgPlayer[]) => void | Promise<void>;
61
+
62
+ /**
63
+ * Called when the skill is forgotten
64
+ *
65
+ * @param player - The player forgetting the skill
66
+ */
67
+ onForget?: (player: RpgPlayer) => void | Promise<void>;
22
68
  }
23
69
 
70
+ /**
71
+ * Interface for skill object definition
72
+ *
73
+ * Defines the properties that a skill can have when defined as an object.
74
+ * Skills can be defined as objects, classes, or string IDs referencing the database.
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * const fireSkill: SkillObject = {
79
+ * id: 'fire',
80
+ * name: 'Fire',
81
+ * description: 'A basic fire spell',
82
+ * spCost: 10,
83
+ * hitRate: 0.9,
84
+ * power: 50,
85
+ * onUse(player) {
86
+ * console.log('Fire spell cast!');
87
+ * }
88
+ * };
89
+ *
90
+ * player.learnSkill(fireSkill);
91
+ * ```
92
+ */
93
+ export interface SkillObject extends SkillHooks {
94
+ /**
95
+ * Unique identifier for the skill
96
+ * If not provided, one will be auto-generated
97
+ */
98
+ id?: string;
99
+
100
+ /**
101
+ * Display name of the skill
102
+ */
103
+ name?: string;
24
104
 
105
+ /**
106
+ * Description of the skill
107
+ */
108
+ description?: string;
109
+
110
+ /**
111
+ * SP (Skill Points) cost to use the skill
112
+ * @default 0
113
+ */
114
+ spCost?: number;
115
+
116
+ /**
117
+ * Hit rate (0-1) - probability of successful skill usage
118
+ * @default 1
119
+ */
120
+ hitRate?: number;
121
+
122
+ /**
123
+ * Base power of the skill for damage calculation
124
+ */
125
+ power?: number;
126
+
127
+ /**
128
+ * Coefficient multipliers for damage calculation
129
+ */
130
+ coefficient?: Record<string, number>;
131
+
132
+ /**
133
+ * Type marker for database
134
+ */
135
+ _type?: 'skill';
136
+
137
+ /**
138
+ * Allow additional properties
139
+ */
140
+ [key: string]: any;
141
+ }
25
142
 
26
143
  /**
27
144
  * Skill Manager Mixin
@@ -30,243 +147,487 @@ interface SkillManagerDependencies {
30
147
  * learning, forgetting, and using skills, including SP cost management,
31
148
  * hit rate calculations, and skill effects application.
32
149
  *
150
+ * Supports three input formats for skills:
151
+ * - **String ID**: References a skill in the database
152
+ * - **Class**: A skill class that will be instantiated
153
+ * - **Object**: A skill object with properties and hooks
154
+ *
33
155
  * @param Base - The base class to extend with skill management
34
156
  * @returns Extended class with skill management methods
35
157
  *
36
158
  * @example
37
159
  * ```ts
38
- * class MyPlayer extends WithSkillManager(BasePlayer) {
39
- * constructor() {
40
- * super();
41
- * // Skill system is automatically initialized
42
- * }
43
- * }
160
+ * // Using string ID (from database)
161
+ * player.learnSkill('fire');
44
162
  *
45
- * const player = new MyPlayer();
46
- * player.learnSkill(Fire);
47
- * player.useSkill(Fire, targetPlayer);
163
+ * // Using skill class
164
+ * player.learnSkill(FireSkill);
165
+ *
166
+ * // Using skill object
167
+ * player.learnSkill({
168
+ * id: 'ice',
169
+ * name: 'Ice',
170
+ * spCost: 15,
171
+ * onUse(player) {
172
+ * console.log('Ice spell cast!');
173
+ * }
174
+ * });
48
175
  * ```
49
176
  */
50
- export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase) {
51
- return class extends Base {
52
- _getSkillIndex(skillClass: any | string) {
53
- return (this as any).skills().findIndex((skill) => {
54
- if (isString(skill)) {
55
- return skill.id == skillClass;
177
+ export function WithSkillManager<TBase extends PlayerCtor>(Base: TBase): TBase {
178
+ return class extends (Base as any) {
179
+ private _getSkillSnapshot(skillData: any) {
180
+ if (!skillData) return null;
181
+
182
+ const snapshot = {
183
+ ...((skillData as any)._skillData || skillData),
184
+ };
185
+
186
+ const reactiveKeys = [
187
+ "id",
188
+ "name",
189
+ "description",
190
+ "spCost",
191
+ "icon",
192
+ "hitRate",
193
+ "power",
194
+ "coefficient",
195
+ ];
196
+
197
+ for (const key of reactiveKeys) {
198
+ const value = (skillData as any)[key];
199
+ if (typeof value === "function") {
200
+ if (key === "hitRate" && !(key in snapshot)) {
201
+ continue;
202
+ }
203
+ snapshot[key] = value();
204
+ } else if (value !== undefined) {
205
+ snapshot[key] = value;
56
206
  }
57
- if (isString(skillClass)) {
58
- return skillClass == (skill.id || skill);
207
+ }
208
+
209
+ if ((skillData as any)._skillInstance) {
210
+ snapshot._skillInstance = (skillData as any)._skillInstance;
211
+ }
212
+
213
+ return snapshot;
214
+ }
215
+
216
+ private _getLearnedSkillEntry(skillInput: SkillClass | SkillObject | string): Skill | null {
217
+ const index = this._getSkillIndex(skillInput);
218
+ return index >= 0 ? ((this as any).skills()[index] as Skill) : null;
219
+ }
220
+
221
+ private _getSkillMap(required: boolean = true) {
222
+ // Use this.map directly to support both RpgMap and LobbyRoom
223
+ const map = (this as any).getCurrentMap?.() || (this as any).map;
224
+ if (required && (!map || !map.database)) {
225
+ throw new Error('Player must be on a map to learn skills');
226
+ }
227
+ return map;
228
+ }
229
+
230
+ private _resolveSkillInput(
231
+ skillInput: SkillClass | SkillObject | string,
232
+ map: any,
233
+ databaseByIdOverride?: (id: string) => any
234
+ ) {
235
+ let skillId = '';
236
+ let skillData: any;
237
+ let skillInstance: any = null;
238
+
239
+ if (isString(skillInput)) {
240
+ skillId = skillInput as string;
241
+ skillData = databaseByIdOverride
242
+ ? databaseByIdOverride(skillId)
243
+ : (this as any).databaseById(skillId);
244
+ } else if (typeof skillInput === 'function') {
245
+ const SkillClassCtor = skillInput as SkillClass;
246
+ skillId = (SkillClassCtor as any).id || SkillClassCtor.name;
247
+
248
+ const existingData = map?.database()?.[skillId];
249
+ if (existingData) {
250
+ skillData = existingData;
251
+ } else if (map) {
252
+ map.addInDatabase(skillId, SkillClassCtor);
253
+ skillData = SkillClassCtor;
254
+ } else {
255
+ skillData = SkillClassCtor;
59
256
  }
60
- return isInstanceOf(skill, skillClass);
61
- });
257
+
258
+ skillInstance = new SkillClassCtor();
259
+ skillData = { ...skillData, ...skillInstance, id: skillId };
260
+ } else {
261
+ const skillObj = skillInput as SkillObject;
262
+ skillId = skillObj.id || `skill-${Date.now()}`;
263
+ skillObj.id = skillId;
264
+
265
+ const existingData = map?.database()?.[skillId];
266
+ if (existingData) {
267
+ skillData = { ...existingData, ...skillObj };
268
+ if (map) {
269
+ map.addInDatabase(skillId, skillData, { force: true });
270
+ }
271
+ } else if (map) {
272
+ map.addInDatabase(skillId, skillObj);
273
+ skillData = skillObj;
274
+ } else {
275
+ skillData = skillObj;
276
+ }
277
+
278
+ skillInstance = skillObj;
279
+ }
280
+
281
+ return { skillId, skillData, skillInstance };
282
+ }
283
+
284
+ private _createSkillInstance(
285
+ skillId: string,
286
+ skillData: any,
287
+ skillInstance: any
288
+ ) {
289
+ const instance = new Skill(skillData);
290
+ instance.id.set(skillId);
291
+ (instance as any)._skillData = {
292
+ ...skillData,
293
+ id: skillId,
294
+ };
295
+
296
+ if (skillInstance) {
297
+ (instance as any)._skillInstance = skillInstance;
298
+ }
299
+
300
+ return instance;
62
301
  }
63
302
 
64
303
  /**
65
- * Retrieves a learned skill. Returns null, if not found
66
- * ```ts
67
- * import Fire from 'your-database/skills/fire'
68
- *
69
- * player.getSkill(Fire)
70
- * ```
71
- *
72
- * @title Get Skill
73
- * @method player.getSkill(skillClass)
74
- * @param {SkillClass | string} skillClass or data id
75
- * @returns {instance of SkillClass | null}
76
- * @memberof SkillManager
304
+ * Create a skill instance without learning side effects.
77
305
  */
78
- getSkill(skillClass: any | string) {
79
- const index = this._getSkillIndex(skillClass);
80
- return this.skills()[index] ?? null;
306
+ createSkillInstance(skillInput: SkillClass | SkillObject | string) {
307
+ const map = this._getSkillMap();
308
+ const { skillId, skillData, skillInstance } = this._resolveSkillInput(skillInput, map);
309
+ const instance = this._createSkillInstance(skillId, skillData, skillInstance);
310
+ return { skillId, skillData, skillInstance, instance };
81
311
  }
82
312
 
83
313
  /**
84
- * Learn a skill. Attributes the coefficient 1 to the parameter INT (intelligence) if cd is not present on the class.
85
- *
86
- * `onLearn()` method is called on the SkillClass
87
- *
88
- * ```ts
89
- * import Fire from 'your-database/skills/fire'
90
- *
91
- * player.learnSkill(Fire)
92
- * ```
93
- *
94
- * @title Learn Skill
95
- * @method player.learnSkill(skillClass)
96
- * @param {SkillClass | string} skillId or data id
97
- * @throws {SkillLog} alreadyLearned
98
- * If the player already knows the skill
99
- * ```
100
- {
101
- id: SKILL_ALREADY_LEARNED,
102
- msg: '...'
103
- }
104
- ```
105
- * @returns {instance of SkillClass}
106
- * @memberof SkillManager
107
- */
108
- learnSkill(skillId: any | string) {
109
- if (this.getSkill(skillId)) {
110
- throw SkillLog.alreadyLearned(skillId);
314
+ * Resolve skill snapshot entries into Skill instances without side effects.
315
+ */
316
+ resolveSkillsSnapshot(snapshot: { skills?: any[] }, mapOverride?: any) {
317
+ if (!snapshot || !Array.isArray(snapshot.skills)) {
318
+ return snapshot;
111
319
  }
112
- const instance = (this as any).databaseById(skillId);
113
- this.skills().push(instance);
114
- this["execMethod"]("onLearn", [this], instance);
115
- return instance;
320
+
321
+ const map = mapOverride ?? this._getSkillMap(false);
322
+ if (!map || !map.database) {
323
+ return snapshot;
324
+ }
325
+
326
+ const databaseByIdOverride = (id: string) => {
327
+ const data = map.database()[id];
328
+ if (!data) {
329
+ throw new Error(
330
+ `The ID=${id} data is not found in the database. Add the data in the property "database"`
331
+ );
332
+ }
333
+ return data;
334
+ };
335
+
336
+ const skills = snapshot.skills.map((entry: any) => {
337
+ const skillId = isString(entry) ? entry : entry?.id;
338
+ if (!skillId) {
339
+ return entry;
340
+ }
341
+
342
+ const { skillData, skillInstance } = this._resolveSkillInput(
343
+ skillId,
344
+ map,
345
+ databaseByIdOverride
346
+ );
347
+ return this._createSkillInstance(skillId, skillData, skillInstance);
348
+ });
349
+
350
+ return { ...snapshot, skills };
116
351
  }
117
352
 
118
353
  /**
119
- * Forget a skill
120
- *
121
- * `onForget()` method is called on the SkillClass
122
- *
123
- * ```ts
124
- * import Fire from 'your-database/skills/fire'
125
- *
126
- * try {
127
- * player.forgetSkill(Fire)
128
- * }
129
- * catch (err) {
130
- * console.log(err)
131
- * }
132
- * ```
133
- *
134
- * @title Forget Skill
135
- * @method player.learnSkill(skillClass)
136
- * @param {SkillClass | string} skillId or data id
137
- * @throws {SkillLog} notLearned
138
- * If trying to forget a skill not learned
139
- * ```
140
- * {
141
- * id: SKILL_NOT_LEARNED,
142
- * msg: '...'
143
- * }
144
- * ```
145
- * @returns {instance of SkillClass}
146
- * @memberof SkillManager
354
+ * Find the index of a skill in the skills array
355
+ *
356
+ * Searches by ID for both string inputs and object/class inputs.
357
+ *
358
+ * @param skillInput - Skill ID, class, or object to find
359
+ * @returns Index of the skill or -1 if not found
147
360
  */
148
- forgetSkill(skillId: any | string) {
149
- if (isString(skillId)) skillId = (this as any).databaseById(skillId);
150
- const index = this._getSkillIndex(skillId);
151
- if (index == -1) {
152
- throw SkillLog.notLearned(skillId);
361
+ _getSkillIndex(skillInput: SkillClass | SkillObject | string): number {
362
+ // Get the ID to search for
363
+ let searchId = '';
364
+
365
+ if (isString(skillInput)) {
366
+ searchId = skillInput as string;
367
+ } else if (typeof skillInput === 'function') {
368
+ // It's a class - use the class name as ID
369
+ searchId = (skillInput as any).id || skillInput.name;
370
+ } else {
371
+ // It's an object - use its id property
372
+ searchId = (skillInput as SkillObject).id || '';
153
373
  }
154
- const instance = this.skills()[index];
155
- this.skills().splice(index, 1);
156
- this["execMethod"]("onForget", [this], instance);
157
- return instance;
374
+
375
+ return (this as any).skills().findIndex((skill: any) => {
376
+ const skillId = skill.id() || skill.name() || '';
377
+ return skillId === searchId;
378
+ });
158
379
  }
159
380
 
160
381
  /**
161
- * Using a skill
162
- *
163
- * `onUse()` method is called on the SkillClass
164
- *
165
- * If other players are indicated then damage will be done to these other players. The method `applyDamage()` will be executed
166
- *
382
+ * Retrieves a learned skill
383
+ *
384
+ * Searches the player's learned skills by ID, class, or object.
385
+ *
386
+ * @param skillInput - Skill ID, class, or object to find
387
+ * @returns The skill data if found, null otherwise
388
+ *
389
+ * @example
167
390
  * ```ts
168
- * import Fire from 'your-database/skills/fire'
169
- *
170
- * try {
171
- * player.useSkill(Fire)
172
- * }
173
- * catch (err) {
174
- * console.log(err)
175
- * }
176
- * ```
177
- *
178
- * or
179
- *
180
- *
181
- * * ```ts
182
- * import Fire from 'your-database/skills/fire'
183
- *
184
- * try {
185
- * player.useSkill(Fire, otherPlayer)
186
- * }
187
- * catch (err) {
188
- * console.log(err)
189
- * }
190
- * ```
191
- *
192
- * @title Use Skill
193
- * @method player.useSkill(skillClass,otherPlayer)
194
- * @param {SkillClass | string} skillId or data id
195
- * @param {Array<RpgPlayer> | RpgPlayer} [otherPlayer]
196
- * @throws {SkillLog} restriction
197
- * If the player has the `Effect.CAN_NOT_SKILL` effect
198
- * ```
199
- * {
200
- * id: RESTRICTION_SKILL,
201
- * msg: '...'
391
+ * const skill = player.getSkill('fire');
392
+ * if (skill) {
393
+ * console.log(`Fire skill costs ${skill.spCost} SP`);
202
394
  * }
203
395
  * ```
204
- * @throws {SkillLog} notLearned
205
- * If the player tries to use an unlearned skill
206
- * ```
207
- * {
208
- * id: SKILL_NOT_LEARNED,
209
- * msg: '...'
210
- * }
396
+ */
397
+ getSkill(skillInput: SkillClass | SkillObject | string): Skill | null {
398
+ const skill = this._getLearnedSkillEntry(skillInput);
399
+ return this._getSkillSnapshot(skill) as Skill | null;
400
+ }
401
+
402
+ /**
403
+ * Learn a new skill
404
+ *
405
+ * Adds a skill to the player's skill list. Supports three input formats:
406
+ * - **String ID**: Retrieves the skill from the database
407
+ * - **Class**: Creates an instance and adds to database if needed
408
+ * - **Object**: Uses directly and adds to database if needed
409
+ *
410
+ * @param skillInput - Skill ID, class, or object to learn
411
+ * @returns The learned skill data
412
+ * @throws SkillLog.alreadyLearned if the skill is already known
413
+ *
414
+ * @example
415
+ * ```ts
416
+ * // From database
417
+ * player.learnSkill('fire');
418
+ *
419
+ * // From class
420
+ * player.learnSkill(FireSkill);
421
+ *
422
+ * // From object
423
+ * player.learnSkill({
424
+ * id: 'custom-skill',
425
+ * name: 'Custom Skill',
426
+ * spCost: 20,
427
+ * onLearn(player) {
428
+ * console.log('Learned custom skill!');
429
+ * }
430
+ * });
211
431
  * ```
212
- * @throws {SkillLog} notEnoughSp
213
- * If the player does not have enough SP to use the skill
214
- * ```
215
- * {
216
- * id: NOT_ENOUGH_SP,
217
- * msg: '...'
218
- * }
432
+ */
433
+ learnSkill(skillInput: SkillClass | SkillObject | string): any {
434
+ const map = this._getSkillMap();
435
+ const { skillId, skillData, skillInstance } = this._resolveSkillInput(skillInput, map);
436
+
437
+ // Check if already learned
438
+ if (this._getLearnedSkillEntry(skillId)) {
439
+ throw SkillLog.alreadyLearned(skillData);
440
+ }
441
+
442
+ const instance = this._createSkillInstance(skillId, skillData, skillInstance);
443
+
444
+ (this as any).skills().push(instance);
445
+
446
+ // Call onLearn hook
447
+ const hookTarget = (instance as any)._skillInstance || instance;
448
+ this["execMethod"]("onLearn", [this], hookTarget);
449
+
450
+ return skillData;
451
+ }
452
+
453
+ /**
454
+ * Forget a learned skill
455
+ *
456
+ * Removes a skill from the player's skill list.
457
+ *
458
+ * @param skillInput - Skill ID, class, or object to forget
459
+ * @returns The forgotten skill data
460
+ * @throws SkillLog.notLearned if the skill is not known
461
+ *
462
+ * @example
463
+ * ```ts
464
+ * player.forgetSkill('fire');
465
+ * // or
466
+ * player.forgetSkill(FireSkill);
219
467
  * ```
220
- * @throws {SkillLog} chanceToUseFailed
221
- * If the chance to use the skill has failed (defined with the `hitRate` property)
222
- * ```
223
- * {
224
- * id: USE_CHANCE_SKILL_FAILED,
225
- * msg: '...'
226
- * }
468
+ */
469
+ forgetSkill(skillInput: SkillClass | SkillObject | string): any {
470
+ const index = this._getSkillIndex(skillInput);
471
+
472
+ if (index === -1) {
473
+ // Get skill data for error message
474
+ let skillData: any = skillInput;
475
+ if (isString(skillInput)) {
476
+ try {
477
+ skillData = (this as any).databaseById(skillInput);
478
+ } catch {
479
+ skillData = { name: skillInput, id: skillInput };
480
+ }
481
+ } else if (typeof skillInput === 'function') {
482
+ skillData = { name: (skillInput as SkillClass).name, id: (skillInput as any).id || (skillInput as SkillClass).name };
483
+ }
484
+ throw SkillLog.notLearned(skillData);
485
+ }
486
+
487
+ const skillEntry = (this as any).skills()[index];
488
+ const skillData = this._getSkillSnapshot(skillEntry);
489
+ (this as any).skills().splice(index, 1);
490
+
491
+ // Call onForget hook
492
+ const hookTarget =
493
+ (skillEntry as any)?._skillInstance ||
494
+ (skillEntry as any)?._skillData ||
495
+ skillData;
496
+ this["execMethod"]("onForget", [this], hookTarget);
497
+
498
+ return skillData;
499
+ }
500
+
501
+ /**
502
+ * Use a learned skill
503
+ *
504
+ * Executes a skill, consuming SP and applying effects to targets.
505
+ * The skill must be learned and the player must have enough SP.
506
+ *
507
+ * @param skillInput - Skill ID, class, or object to use
508
+ * @param otherPlayer - Optional target player(s) to apply skill effects to
509
+ * @returns The used skill data
510
+ * @throws SkillLog.restriction if player has CAN_NOT_SKILL effect
511
+ * @throws SkillLog.notLearned if skill is not known
512
+ * @throws SkillLog.notEnoughSp if not enough SP
513
+ * @throws SkillLog.chanceToUseFailed if hit rate check fails
514
+ *
515
+ * @example
516
+ * ```ts
517
+ * // Use skill without target
518
+ * player.useSkill('fire');
519
+ *
520
+ * // Use skill on a target
521
+ * player.useSkill('fire', enemy);
522
+ *
523
+ * // Use skill on multiple targets
524
+ * player.useSkill('fire', [enemy1, enemy2]);
227
525
  * ```
228
- *
229
- * `onUseFailed()` method is called on the SkillClass
230
- *
231
- * @returns {instance of SkillClass}
232
- * @memberof SkillManager
233
- * @todo
234
526
  */
235
- useSkill(skillId: any | string, otherPlayer?: RpgPlayer | RpgPlayer[]) {
236
- const skill = this.getSkill(skillId);
527
+ useSkill(skillInput: SkillClass | SkillObject | string, otherPlayer?: RpgPlayer | RpgPlayer[]): any {
528
+ const skillEntry = this._getLearnedSkillEntry(skillInput);
529
+ const skill = this._getSkillSnapshot(skillEntry);
530
+
531
+ // Check for skill restriction effect
237
532
  if ((this as any).hasEffect(Effect.CAN_NOT_SKILL)) {
238
- throw SkillLog.restriction(skillId);
533
+ throw SkillLog.restriction(skill || skillInput);
239
534
  }
535
+
536
+ // Check if skill is learned
240
537
  if (!skill) {
241
- throw SkillLog.notLearned(skillId);
538
+ throw SkillLog.notLearned(skillInput);
242
539
  }
243
- if (skill.spCost > (this as any).sp) {
244
- throw SkillLog.notEnoughSp(skillId, skill.spCost, (this as any).sp);
540
+
541
+ // Check SP cost
542
+ const spCost = typeof skill.spCost === "number" ? skill.spCost : 0;
543
+ if (spCost > (this as any).sp) {
544
+ throw SkillLog.notEnoughSp(skill, spCost, (this as any).sp);
245
545
  }
246
- (this as any).sp -= skill.spCost / ((this as any).hasEffect(Effect.HALF_SP_COST) ? 2 : 1);
247
- const hitRate = skill.hitRate ?? 1;
546
+
547
+ // Consume SP (halved if HALF_SP_COST effect is active)
548
+ const costMultiplier = (this as any).hasEffect(Effect.HALF_SP_COST) ? 2 : 1;
549
+ (this as any).sp -= spCost / costMultiplier;
550
+
551
+ // Check hit rate
552
+ const hitRate = typeof skill.hitRate === "number" ? skill.hitRate : 1;
248
553
  if (Math.random() > hitRate) {
249
- this["execMethod"]("onUseFailed", [this, otherPlayer], skill);
250
- throw SkillLog.chanceToUseFailed(skillId);
554
+ const hookTarget =
555
+ (skillEntry as any)?._skillInstance ||
556
+ (skillEntry as any)?._skillData ||
557
+ skill;
558
+ this["execMethod"]("onUseFailed", [this, otherPlayer], hookTarget);
559
+ throw SkillLog.chanceToUseFailed(skill);
251
560
  }
561
+
562
+ // Apply effects to targets
252
563
  if (otherPlayer) {
253
- let players: any = otherPlayer;
254
- if (!isArray(players)) {
255
- players = [otherPlayer];
256
- }
257
- for (let player of players) {
564
+ const players: RpgPlayer[] = isArray(otherPlayer) ? otherPlayer as RpgPlayer[] : [otherPlayer as RpgPlayer];
565
+ for (const player of players) {
258
566
  (this as any).applyStates(player, skill);
259
567
  (player as any).applyDamage(this, skill);
260
568
  }
261
569
  }
262
- this["execMethod"]("onUse", [this, otherPlayer], skill);
570
+
571
+ // Call onUse hook
572
+ const hookTarget =
573
+ (skillEntry as any)?._skillInstance ||
574
+ (skillEntry as any)?._skillData ||
575
+ skill;
576
+ this["execMethod"]("onUse", [this, otherPlayer], hookTarget);
577
+
263
578
  return skill;
264
579
  }
265
580
  } as unknown as TBase;
266
581
  }
267
582
 
268
583
  /**
269
- * Type helper to extract the interface from the WithSkillManager mixin
270
- * This provides the type without duplicating method signatures
584
+ * Interface for Skill Manager functionality
585
+ *
586
+ * Provides skill management capabilities including learning, forgetting, and using skills.
587
+ * This interface defines the public API of the SkillManager mixin.
271
588
  */
272
- export type ISkillManager = InstanceType<ReturnType<typeof WithSkillManager>>;
589
+ export interface ISkillManager {
590
+ /**
591
+ * Retrieves a learned skill. Returns null if not found
592
+ *
593
+ * @param skillInput - Skill class, object, or data id
594
+ * @returns The skill data or null
595
+ */
596
+ getSkill(skillInput: SkillClass | SkillObject | string): any | null;
597
+
598
+ /**
599
+ * Learn a skill
600
+ *
601
+ * Supports three input formats:
602
+ * - String ID: Retrieves from database
603
+ * - Class: Creates instance and adds to database
604
+ * - Object: Uses directly and adds to database
605
+ *
606
+ * @param skillInput - Skill class, object, or data id
607
+ * @returns The learned skill data
608
+ * @throws SkillLog.alreadyLearned if the player already knows the skill
609
+ */
610
+ learnSkill(skillInput: SkillClass | SkillObject | string): any;
611
+
612
+ /**
613
+ * Forget a skill
614
+ *
615
+ * @param skillInput - Skill class, object, or data id
616
+ * @returns The forgotten skill data
617
+ * @throws SkillLog.notLearned if trying to forget a skill not learned
618
+ */
619
+ forgetSkill(skillInput: SkillClass | SkillObject | string): any;
620
+
621
+ /**
622
+ * Use a skill
623
+ *
624
+ * @param skillInput - Skill class, object, or data id
625
+ * @param otherPlayer - Optional target player(s) to apply skill to
626
+ * @returns The used skill data
627
+ * @throws SkillLog.restriction if player has Effect.CAN_NOT_SKILL
628
+ * @throws SkillLog.notLearned if player tries to use an unlearned skill
629
+ * @throws SkillLog.notEnoughSp if player does not have enough SP
630
+ * @throws SkillLog.chanceToUseFailed if the chance to use the skill has failed
631
+ */
632
+ useSkill(skillInput: SkillClass | SkillObject | string, otherPlayer?: RpgPlayer | RpgPlayer[]): any;
633
+ }