@rpgjs/server 5.0.0-alpha.4 → 5.0.0-alpha.41

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 (101) 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 +34 -12
  11. package/dist/Player/ClassManager.d.ts +46 -13
  12. package/dist/Player/ComponentManager.d.ts +123 -0
  13. package/dist/Player/Components.d.ts +345 -0
  14. package/dist/Player/EffectManager.d.ts +86 -0
  15. package/dist/Player/ElementManager.d.ts +104 -0
  16. package/dist/Player/GoldManager.d.ts +22 -0
  17. package/dist/Player/GuiManager.d.ts +259 -0
  18. package/dist/Player/ItemFixture.d.ts +6 -0
  19. package/dist/Player/ItemManager.d.ts +450 -9
  20. package/dist/Player/MoveManager.d.ts +324 -69
  21. package/dist/Player/ParameterManager.d.ts +344 -14
  22. package/dist/Player/Player.d.ts +460 -8
  23. package/dist/Player/SkillManager.d.ts +197 -15
  24. package/dist/Player/StateManager.d.ts +89 -25
  25. package/dist/Player/VariableManager.d.ts +74 -0
  26. package/dist/RpgServer.d.ts +502 -64
  27. package/dist/RpgServerEngine.d.ts +2 -1
  28. package/dist/decorators/event.d.ts +46 -0
  29. package/dist/decorators/map.d.ts +287 -0
  30. package/dist/index.d.ts +10 -0
  31. package/dist/index.js +21653 -20900
  32. package/dist/index.js.map +1 -1
  33. package/dist/logs/log.d.ts +2 -3
  34. package/dist/module.d.ts +43 -1
  35. package/dist/presets/index.d.ts +0 -9
  36. package/dist/rooms/BaseRoom.d.ts +132 -0
  37. package/dist/rooms/lobby.d.ts +10 -2
  38. package/dist/rooms/map.d.ts +1236 -17
  39. package/dist/services/save.d.ts +43 -0
  40. package/dist/storage/index.d.ts +1 -0
  41. package/dist/storage/localStorage.d.ts +23 -0
  42. package/package.json +14 -10
  43. package/src/Gui/DialogGui.ts +19 -4
  44. package/src/Gui/GameoverGui.ts +39 -0
  45. package/src/Gui/Gui.ts +23 -1
  46. package/src/Gui/MenuGui.ts +155 -6
  47. package/src/Gui/NotificationGui.ts +1 -2
  48. package/src/Gui/SaveLoadGui.ts +60 -0
  49. package/src/Gui/ShopGui.ts +146 -16
  50. package/src/Gui/TitleGui.ts +39 -0
  51. package/src/Gui/index.ts +15 -2
  52. package/src/Player/BattleManager.ts +91 -49
  53. package/src/Player/ClassManager.ts +118 -50
  54. package/src/Player/ComponentManager.ts +425 -19
  55. package/src/Player/Components.ts +380 -0
  56. package/src/Player/EffectManager.ts +81 -44
  57. package/src/Player/ElementManager.ts +109 -86
  58. package/src/Player/GoldManager.ts +32 -35
  59. package/src/Player/GuiManager.ts +308 -150
  60. package/src/Player/ItemFixture.ts +4 -5
  61. package/src/Player/ItemManager.ts +774 -355
  62. package/src/Player/MoveManager.ts +1544 -774
  63. package/src/Player/ParameterManager.ts +546 -104
  64. package/src/Player/Player.ts +1163 -88
  65. package/src/Player/SkillManager.ts +520 -195
  66. package/src/Player/StateManager.ts +170 -182
  67. package/src/Player/VariableManager.ts +101 -63
  68. package/src/RpgServer.ts +525 -63
  69. package/src/core/context.ts +1 -0
  70. package/src/decorators/event.ts +61 -0
  71. package/src/decorators/map.ts +327 -0
  72. package/src/index.ts +11 -1
  73. package/src/logs/log.ts +10 -3
  74. package/src/module.ts +126 -3
  75. package/src/presets/index.ts +1 -10
  76. package/src/rooms/BaseRoom.ts +232 -0
  77. package/src/rooms/lobby.ts +25 -7
  78. package/src/rooms/map.ts +2502 -194
  79. package/src/services/save.ts +147 -0
  80. package/src/storage/index.ts +1 -0
  81. package/src/storage/localStorage.ts +76 -0
  82. package/tests/battle.spec.ts +375 -0
  83. package/tests/change-map.spec.ts +72 -0
  84. package/tests/class.spec.ts +274 -0
  85. package/tests/effect.spec.ts +219 -0
  86. package/tests/element.spec.ts +221 -0
  87. package/tests/event.spec.ts +80 -0
  88. package/tests/gold.spec.ts +99 -0
  89. package/tests/item.spec.ts +609 -0
  90. package/tests/module.spec.ts +38 -0
  91. package/tests/move.spec.ts +601 -0
  92. package/tests/player-param.spec.ts +28 -0
  93. package/tests/prediction-reconciliation.spec.ts +182 -0
  94. package/tests/random-move.spec.ts +65 -0
  95. package/tests/skill.spec.ts +658 -0
  96. package/tests/state.spec.ts +467 -0
  97. package/tests/variable.spec.ts +185 -0
  98. package/tests/world-maps.spec.ts +896 -0
  99. package/vite.config.ts +16 -0
  100. package/dist/Player/Event.d.ts +0 -0
  101. package/src/Player/Event.ts +0 -0
@@ -1,43 +1,173 @@
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 ShopGui extends Gui implements IGui {
5
+ export type ShopSellList = Record<string, number> | Array<{ id: string; multiplier: number }>
6
+ export type ShopItemInput = string | { id?: string; [key: string]: any }
7
+
8
+ export interface ShopGuiOptions {
9
+ items: ShopItemInput[]
10
+ sell?: ShopSellList
11
+ sellMultiplier?: number
12
+ message?: string
13
+ face?: {
14
+ id: string
15
+ expression?: string
16
+ }
17
+ }
18
+
19
+ export class ShopGui extends Gui {
20
+ private itemsInput: ShopItemInput[] = []
21
+ private sellMultipliers: Record<string, number> = {}
22
+ private baseSellMultiplier = 0.5
23
+ private messageInput?: string
24
+ private faceInput?: { id: string; expression?: string }
25
+
7
26
  constructor(player: RpgPlayer) {
8
27
  super(PrebuiltGui.Shop, player)
9
28
  }
10
29
 
11
- open(items: any[]) {
12
- items = items.map(item => {
13
- const it = new item()
30
+ private normalizeSellMultipliers(sell?: ShopSellList) {
31
+ if (!sell) return {}
32
+ if (Array.isArray(sell)) {
33
+ return sell.reduce<Record<string, number>>((acc, entry) => {
34
+ if (entry && entry.id) acc[entry.id] = entry.multiplier ?? 0
35
+ return acc
36
+ }, {})
37
+ }
38
+ return { ...sell }
39
+ }
40
+
41
+ private buildShopData() {
42
+ const player = this.player as any
43
+ const databaseById = player.databaseById?.bind(player)
44
+ const equippedIds = new Set(
45
+ (player.equipments?.() || []).map((it) => it?.id?.() ?? it?.id ?? it?.name)
46
+ )
47
+ const playerParams = {
48
+ ...(player.param || {}),
49
+ atk: player.atk ?? 0,
50
+ def: player.pdef ?? 0,
51
+ pdef: player.pdef ?? 0,
52
+ sdef: player.sdef ?? 0
53
+ }
54
+
55
+ const getStatValue = (data, key, fallbackKeys: string[] = []) => {
56
+ if (data && typeof data[key] === 'number') return data[key]
57
+ for (const fallbackKey of fallbackKeys) {
58
+ if (data && typeof data[fallbackKey] === 'number') return data[fallbackKey]
59
+ }
60
+ const modifier = data?.paramsModifier?.[key]
61
+ if (modifier && typeof modifier.value === 'number') return modifier.value
62
+ for (const fallbackKey of fallbackKeys) {
63
+ const fallbackModifier = data?.paramsModifier?.[fallbackKey]
64
+ if (fallbackModifier && typeof fallbackModifier.value === 'number') return fallbackModifier.value
65
+ }
66
+ return undefined
67
+ }
68
+
69
+ const buildItemData = (item: ShopItemInput, overrides: { price?: number; quantity?: number } = {}) => {
70
+ const rawId = typeof item === 'string'
71
+ ? item
72
+ : (typeof item?.id === 'function' ? item.id() : (item?.id ?? item?.name))
73
+ const data = databaseById(rawId)
74
+ const itemName = typeof item?.name === 'function' ? item.name() : item?.name
75
+ const itemDescription = typeof item?.description === 'function' ? item.description() : item?.description
76
+ const itemPrice = typeof item?.price === 'function' ? item.price() : item?.price
77
+ const itemIcon = typeof item?.icon === 'function' ? item.icon() : item?.icon
78
+ const atk = getStatValue(data, 'atk')
79
+ const def = getStatValue(data, 'def', ['pdef', 'sdef'])
80
+ const intValue = getStatValue(data, 'int')
81
+ const agi = getStatValue(data, 'agi')
82
+ const stats = {
83
+ ...(atk !== undefined ? { atk } : {}),
84
+ ...(def !== undefined ? { def } : {}),
85
+ ...(intValue !== undefined ? { int: intValue } : {}),
86
+ ...(agi !== undefined ? { agi } : {})
87
+ }
14
88
  return {
15
- price: it.price,
16
- name: it.name,
17
- description: it.description,
18
- id: it.id,
19
- type: item.type
89
+ price: overrides.price ?? data?.price ?? itemPrice ?? 0,
90
+ name: data?.name ?? itemName ?? rawId,
91
+ description: data?.description ?? itemDescription ?? '',
92
+ icon: data?.icon ?? itemIcon,
93
+ id: rawId,
94
+ type: data?._type ?? item.type ?? item?._type,
95
+ stats: Object.keys(stats).length ? stats : undefined,
96
+ equipped: rawId ? equippedIds.has(rawId) : false,
97
+ ...(overrides.quantity !== undefined ? { quantity: overrides.quantity } : {})
20
98
  }
21
- })
22
- this.on('buyItem', ({ id, nb }) => {
99
+ }
100
+
101
+ const items = this.itemsInput.map(item => buildItemData(item))
102
+
103
+ const sellItems = (player.items?.() || [])
104
+ .map((item) => {
105
+ const id = item?.id?.()
106
+ if (!id) return null
107
+ const multiplier = Object.prototype.hasOwnProperty.call(this.sellMultipliers, id)
108
+ ? this.sellMultipliers[id]
109
+ : this.baseSellMultiplier
110
+ const basePrice = databaseById(id)?.price ?? (typeof item?.price === 'function' ? item.price() : item?.price) ?? 0
111
+ const price = basePrice * multiplier
112
+ const quantity = item?.quantity?.()
113
+ return buildItemData(item, { price, quantity })
114
+ })
115
+ .filter(Boolean)
116
+
117
+ return { items, sellItems, playerParams, message: this.messageInput, face: this.faceInput }
118
+ }
119
+
120
+ private refreshShop(clientActionId?: string) {
121
+ this.update(this.buildShopData(), { clientActionId })
122
+ }
123
+
124
+ open(itemsOrOptions: any[] | ShopGuiOptions) {
125
+ const options: ShopGuiOptions = Array.isArray(itemsOrOptions)
126
+ ? { items: itemsOrOptions }
127
+ : (itemsOrOptions || { items: [] })
128
+ this.itemsInput = options.items || []
129
+ this.baseSellMultiplier = typeof options.sellMultiplier === 'number' ? options.sellMultiplier : 0.5
130
+ this.sellMultipliers = this.normalizeSellMultipliers(options.sell)
131
+ this.messageInput = options.message
132
+ this.faceInput = options.face
133
+ this.on('buyItem', ({ id, nb, clientActionId }) => {
23
134
  try {
24
135
  this.player.buyItem(id, nb)
136
+ this.player.syncChanges()
25
137
  }
26
138
  catch (err) {
27
139
  console.log(err)
28
140
  }
141
+ finally {
142
+ this.refreshShop(clientActionId)
143
+ }
29
144
  })
30
- this.on('sellItem', ({ id, nb }) => {
145
+ this.on('sellItem', ({ id, nb, clientActionId }) => {
31
146
  try {
32
- this.player.sellItem(id, nb)
147
+ const multiplier = Object.prototype.hasOwnProperty.call(this.sellMultipliers, id)
148
+ ? this.sellMultipliers[id]
149
+ : this.baseSellMultiplier
150
+ const basePrice = (this.player as any).databaseById?.(id)?.price ?? (typeof (inventory as any)?.price === 'function' ? (inventory as any).price() : (inventory as any)?.price) ?? 0
151
+ const price = basePrice * multiplier
152
+ if (!basePrice || price <= 0) return
153
+ const inventory = (this.player as any).getItem?.(id)
154
+ if (!inventory) return
155
+ const quantity = inventory.quantity()
156
+ if (quantity - nb < 0) return
157
+ ;(this.player as any)._gold.update((gold) => gold + price * nb)
158
+ ;(this.player as any).removeItem(id, nb)
159
+ this.player.syncChanges()
33
160
  }
34
161
  catch (err) {
35
162
  console.log(err)
36
163
  }
164
+ finally {
165
+ this.refreshShop(clientActionId)
166
+ }
37
167
  })
38
- return super.open({ items }, {
168
+ return super.open(this.buildShopData(), {
39
169
  waitingAction: true,
40
170
  blockPlayerInput: true
41
171
  })
42
172
  }
43
- }
173
+ }
@@ -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 TitleEntry {
6
+ id: string
7
+ label: string
8
+ disabled?: boolean
9
+ }
10
+
11
+ export interface TitleGuiOptions {
12
+ entries?: TitleEntry[]
13
+ title?: string
14
+ subtitle?: string
15
+ version?: string
16
+ showPressStart?: boolean
17
+ }
18
+
19
+ export interface TitleGuiSelection {
20
+ id?: string
21
+ index?: number
22
+ entry?: TitleEntry
23
+ }
24
+
25
+ export class TitleGui extends Gui {
26
+ constructor(player: RpgPlayer) {
27
+ super(PrebuiltGui.TitleScreen, player)
28
+ }
29
+
30
+ open(options: TitleGuiOptions = {}): Promise<TitleGuiSelection | null> {
31
+ this.on('select', (selection: TitleGuiSelection) => {
32
+ this.close(selection)
33
+ })
34
+ return super.open(options, {
35
+ waitingAction: true,
36
+ blockPlayerInput: true
37
+ })
38
+ }
39
+ }
package/src/Gui/index.ts CHANGED
@@ -3,11 +3,24 @@ import { DialogGui } from './DialogGui'
3
3
  import { MenuGui } from './MenuGui'
4
4
  import { ShopGui } from './ShopGui'
5
5
  import { NotificationGui } from './NotificationGui'
6
+ import { SaveLoadGui } from './SaveLoadGui'
7
+ import { TitleGui } from './TitleGui'
8
+ import { GameoverGui } from './GameoverGui'
6
9
 
7
10
  export {
8
11
  Gui,
9
12
  DialogGui,
10
13
  MenuGui,
11
14
  ShopGui,
12
- NotificationGui
13
- }
15
+ NotificationGui,
16
+ SaveLoadGui,
17
+ TitleGui,
18
+ GameoverGui
19
+ }
20
+
21
+ export { DialogPosition } from './DialogGui'
22
+ export type { SaveLoadMode, SaveLoadOptions, SaveSlot } from './SaveLoadGui'
23
+ export type { MenuEntryId, MenuEntry, MenuGuiOptions } from './MenuGui'
24
+ export type { ShopGuiOptions, ShopSellList } from './ShopGui'
25
+ export type { TitleEntry, TitleGuiOptions, TitleGuiSelection } from './TitleGui'
26
+ export type { GameoverEntry, GameoverGuiOptions, GameoverGuiSelection } from './GameoverGui'
@@ -1,19 +1,53 @@
1
- import { Constructor, RpgCommonPlayer } from "@rpgjs/common";
1
+ import { Constructor, PlayerCtor, RpgCommonPlayer } from "@rpgjs/common";
2
2
  import { RpgPlayer } from "./Player";
3
- import { ATK, PDEF, SDEF } from "../presets";
3
+ import { ATK, PDEF, SDEF } from "@rpgjs/common";
4
4
  import { Effect } from "./EffectManager";
5
+ import type { IElementManager } from "./ElementManager";
6
+ import type { IEffectManager } from "./EffectManager";
7
+ import type { IParameterManager } from "./ParameterManager";
5
8
 
6
- interface PlayerWithMixins extends RpgCommonPlayer {
7
- parameters: any[];
9
+ /**
10
+ * Interface combining methods from other managers needed by BattleManager
11
+ * Reuses existing interfaces instead of duplicating method signatures
12
+ */
13
+ interface PlayerWithMixins extends IElementManager, IEffectManager, Pick<IParameterManager, 'parameters' | 'hp'> {
8
14
  getFormulas(name: string): any;
9
- hasEffect(effect: string): boolean;
10
- coefficientElements(attackerPlayer: RpgPlayer): number;
11
- hp: number;
12
- getFormulas(name: string): any;
13
- hasEffect(effect: string): boolean;
15
+ getCurrentMap(): ReturnType<RpgPlayer['getCurrentMap']>;
14
16
  }
15
17
 
16
18
  export interface IBattleManager {
19
+ /**
20
+ * Apply damage. Player will lose HP. the `attackerPlayer` parameter is the other player, the one who attacks.
21
+ *
22
+ * If you don't set the skill parameter, it will be a physical attack.
23
+ * The attack formula is already defined but you can customize it in the server options.
24
+ * This method handles all aspects of damage calculation including critical hits,
25
+ * elemental vulnerabilities, guard effects, and applies the final damage to HP.
26
+ *
27
+ * @param attackerPlayer - The attacking player who deals the damage
28
+ * @param skill - Optional skill object for magical attacks, if not provided uses physical attack
29
+ * @returns Object containing damage details and special effects that occurred
30
+ *
31
+ * @example
32
+ * ```ts
33
+ * // Physical attack
34
+ * const result = player.applyDamage(attackerPlayer);
35
+ * console.log(`Physical damage: ${result.damage}, Critical: ${result.critical}`);
36
+ *
37
+ * // Magical attack with skill
38
+ * const fireSkill = { id: 'fire', power: 50, element: 'fire' };
39
+ * const magicResult = player.applyDamage(attackerPlayer, fireSkill);
40
+ * console.log(`Magic damage: ${magicResult.damage}, Vulnerable: ${magicResult.elementVulnerable}`);
41
+ *
42
+ * // Check for guard effects
43
+ * if (result.guard) {
44
+ * console.log('Attack was partially blocked!');
45
+ * }
46
+ * if (result.superGuard) {
47
+ * console.log('Attack was heavily reduced by super guard!');
48
+ * }
49
+ * ```
50
+ */
17
51
  applyDamage(attackerPlayer: RpgPlayer, skill?: any): {
18
52
  damage: number;
19
53
  critical: boolean;
@@ -23,27 +57,8 @@ export interface IBattleManager {
23
57
  };
24
58
  }
25
59
 
26
- export function WithBattleManager<TBase extends Constructor<PlayerWithMixins>>(
27
- Base: TBase
28
- ): Constructor<IBattleManager> & TBase {
29
- return class extends Base implements IBattleManager {
30
- /**
31
- * Apply damage. Player will lose HP. the `attackerPlayer` parameter is the other player, the one who attacks.
32
- *
33
- * If you don't set the skill parameter, it will be a physical attack.
34
- * The attack formula is already defined but you can customize it in the server options
35
- *
36
- * ```ts
37
- * player.applyDamage(attackerPlayer) // returns { damage: number }
38
- * ```
39
- *
40
- * @title Apply Damage
41
- * @method player.applyDamage(attackerPlayer,skill)
42
- * @param {RpgPlayer} attackerPlayer The attacking player
43
- * @param {any} [skill]
44
- * @returns {object}
45
- * @memberof BattleManager
46
- * */
60
+ export function WithBattleManager<TBase extends PlayerCtor>(Base: TBase): new (...args: ConstructorParameters<TBase>) => InstanceType<TBase> & IBattleManager {
61
+ return class extends Base {
47
62
  applyDamage(
48
63
  attackerPlayer: RpgPlayer,
49
64
  skill?: any
@@ -54,15 +69,16 @@ export function WithBattleManager<TBase extends Constructor<PlayerWithMixins>>(
54
69
  guard: boolean;
55
70
  superGuard: boolean;
56
71
  } {
72
+ const self = this as unknown as PlayerWithMixins;
57
73
  const getParam = (player: RpgPlayer) => {
58
74
  const params = {};
59
- this.parameters.forEach((val, key) => {
60
- params[key] = player.param[key];
75
+ Object.keys(self.parameters).forEach((key) => {
76
+ params[key] = (player as any).param[key];
61
77
  });
62
78
  return {
63
- [ATK]: player.atk,
64
- [PDEF]: player.pdef,
65
- [SDEF]: player.sdef,
79
+ [ATK]: (player as any).atk,
80
+ [PDEF]: (player as any).pdef,
81
+ [SDEF]: (player as any).sdef,
66
82
  ...params,
67
83
  };
68
84
  };
@@ -73,26 +89,25 @@ export function WithBattleManager<TBase extends Constructor<PlayerWithMixins>>(
73
89
  let superGuard = false;
74
90
  let elementVulnerable = false;
75
91
  const paramA = getParam(attackerPlayer);
76
- const paramB = getParam(<any>this);
77
- console.log(paramA, paramB)
92
+ const paramB = getParam(self as any);
78
93
  if (skill) {
79
- fn = this.getFormulas("damageSkill");
94
+ fn = self.getFormulas("damageSkill");
80
95
  if (!fn) {
81
96
  throw new Error("Skill Formulas not exists");
82
97
  }
83
98
  damage = fn(paramA, paramB, skill);
84
99
  } else {
85
- fn = this.getFormulas("damagePhysic");
100
+ fn = self.getFormulas("damagePhysic");
86
101
  if (!fn) {
87
102
  throw new Error("Physic Formulas not exists");
88
103
  }
89
104
  damage = fn(paramA, paramB);
90
- const coef = this.coefficientElements(attackerPlayer);
105
+ const coef = self.coefficientElements(attackerPlayer);
91
106
  if (coef >= 2) {
92
107
  elementVulnerable = true;
93
108
  }
94
109
  damage *= coef;
95
- fn = this.getFormulas("damageCritical");
110
+ fn = self.getFormulas("damageCritical");
96
111
  if (fn) {
97
112
  let newDamage = fn(damage, paramA, paramB);
98
113
  if (damage != newDamage) {
@@ -101,8 +116,8 @@ export function WithBattleManager<TBase extends Constructor<PlayerWithMixins>>(
101
116
  damage = newDamage;
102
117
  }
103
118
  }
104
- if (this.hasEffect(Effect.GUARD)) {
105
- fn = this.getFormulas("damageGuard");
119
+ if (self.hasEffect(Effect.GUARD)) {
120
+ fn = self.getFormulas("damageGuard");
106
121
  if (fn) {
107
122
  let newDamage = fn(damage, paramA, paramB);
108
123
  if (damage != newDamage) {
@@ -111,11 +126,11 @@ export function WithBattleManager<TBase extends Constructor<PlayerWithMixins>>(
111
126
  damage = newDamage;
112
127
  }
113
128
  }
114
- if (this.hasEffect(Effect.SUPER_GUARD)) {
129
+ if (self.hasEffect(Effect.SUPER_GUARD)) {
115
130
  damage /= 4;
116
131
  superGuard = true;
117
132
  }
118
- this.hp -= damage;
133
+ self.hp -= damage;
119
134
  return {
120
135
  damage,
121
136
  critical,
@@ -125,9 +140,36 @@ export function WithBattleManager<TBase extends Constructor<PlayerWithMixins>>(
125
140
  };
126
141
  }
127
142
 
143
+ /**
144
+ * Get damage formulas from the current map
145
+ *
146
+ * Retrieves the damage calculation formulas defined in the current map's configuration.
147
+ * These formulas are used to calculate different types of damage including physical,
148
+ * magical, critical hits, and guard effects. The formulas provide flexibility in
149
+ * customizing the battle system's damage calculations.
150
+ *
151
+ * @param name - The name of the formula to retrieve (e.g., 'damagePhysic', 'damageSkill')
152
+ * @returns The formula function or undefined if not found
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * // Get physical damage formula
157
+ * const physicFormula = player.getFormulas('damagePhysic');
158
+ * if (physicFormula) {
159
+ * const damage = physicFormula(attackerParams, defenderParams);
160
+ * }
161
+ *
162
+ * // Get critical damage formula
163
+ * const criticalFormula = player.getFormulas('damageCritical');
164
+ * if (criticalFormula) {
165
+ * const criticalDamage = criticalFormula(baseDamage, attackerParams, defenderParams);
166
+ * }
167
+ * ```
168
+ */
128
169
  getFormulas(name: string) {
129
- const map = this.getCurrentMap();
130
- return map.damageFormulas[name];
170
+ const self = this as unknown as PlayerWithMixins;
171
+ const map = self.getCurrentMap();
172
+ return map?.damageFormulas[name];
131
173
  }
132
- };
133
- }
174
+ } as unknown as any;
175
+ }
@@ -1,4 +1,4 @@
1
- import { Constructor, isString, RpgCommonPlayer } from "@rpgjs/common";
1
+ import { Constructor, isString, PlayerCtor, RpgCommonPlayer } from "@rpgjs/common";
2
2
 
3
3
  type ClassClass = any;
4
4
  type ActorClass = any;
@@ -6,73 +6,141 @@ type ActorClass = any;
6
6
  interface PlayerWithMixins extends RpgCommonPlayer {
7
7
  databaseById(id: string): any;
8
8
  addParameter(name: string, { start, end }: { start: number, end: number }): void;
9
- addItem(item: any): void;
10
- equip(item: any, equip: boolean): void;
9
+ addItem(item: any): any;
10
+ equip(itemId: string, equip?: boolean | 'auto'): void;
11
11
  }
12
12
 
13
- export interface IClassManager {
14
- setClass(_class: ClassClass | string): ClassClass;
15
- setActor(actorClass: ActorClass | string): ActorClass;
16
- }
13
+ /**
14
+ * Class Manager Mixin
15
+ *
16
+ * Provides class and actor management capabilities to any class. This mixin handles
17
+ * character class assignment and actor setup, including automatic parameter configuration,
18
+ * starting equipment, and skill progression based on class definitions.
19
+ *
20
+ * @param Base - The base class to extend with class management
21
+ * @returns Extended class with class management methods
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * class MyPlayer extends WithClassManager(BasePlayer) {
26
+ * constructor() {
27
+ * super();
28
+ * // Class system is automatically initialized
29
+ * }
30
+ * }
31
+ *
32
+ * const player = new MyPlayer();
33
+ * player.setClass(Fighter);
34
+ * player.setActor(Hero);
35
+ * ```
36
+ */
37
+ export function WithClassManager<TBase extends PlayerCtor>(Base: TBase) {
38
+ return class extends Base {
39
+ private _resolveClassInput(classInput: ClassClass | string, databaseByIdOverride?: (id: string) => any) {
40
+ if (isString(classInput)) {
41
+ return databaseByIdOverride
42
+ ? databaseByIdOverride(classInput)
43
+ : (this as any).databaseById(classInput);
44
+ }
45
+ return classInput;
46
+ }
17
47
 
18
- export function WithClassManager<TBase extends Constructor<PlayerWithMixins>>(
19
- Base: TBase
20
- ): Constructor<IClassManager> & TBase {
21
- return class extends Base implements IClassManager {
48
+ private _createClassInstance(classInput: ClassClass | string) {
49
+ const classClass = this._resolveClassInput(classInput);
50
+ const instance = new (classClass as ClassClass)();
51
+ return { classClass, instance };
52
+ }
22
53
 
23
54
  /**
24
- * Assign a class to the player
25
- *
26
- * ```ts
27
- * import { Fighter } from 'my-database/classes/fighter'
28
- *
29
- * player.setClass(Fighter)
30
- * ```
31
- *
32
- * @title Set Class
33
- * @method player.setClass(ClassClass)
34
- * @param {ClassClass | string} class class or id
35
- * @returns {instance of ClassClass}
36
- * @memberof ClassManager
37
- * */
55
+ * Create a class instance without side effects.
56
+ */
57
+ createClassInstance(classInput: ClassClass | string) {
58
+ return this._createClassInstance(classInput);
59
+ }
60
+
61
+ /**
62
+ * Resolve class snapshot entry into a class instance without side effects.
63
+ */
64
+ resolveClassSnapshot(snapshot: { _class?: any }, mapOverride?: any) {
65
+ if (!snapshot || snapshot._class == null) {
66
+ return snapshot;
67
+ }
68
+
69
+ const map = mapOverride ?? ((this as any).getCurrentMap?.() || (this as any).map);
70
+ if (!map || !map.database) {
71
+ return snapshot;
72
+ }
73
+
74
+ const databaseByIdOverride = (id: string) => {
75
+ const data = map.database()[id];
76
+ if (!data) {
77
+ throw new Error(
78
+ `The ID=${id} data is not found in the database. Add the data in the property "database"`
79
+ );
80
+ }
81
+ return data;
82
+ };
83
+
84
+ const classId = isString(snapshot._class) ? snapshot._class : snapshot._class?.id;
85
+ if (!classId) {
86
+ return snapshot;
87
+ }
88
+
89
+ const classClass = this._resolveClassInput(classId, databaseByIdOverride);
90
+ const { instance } = this._createClassInstance(classClass as ClassClass);
91
+ return { ...snapshot, _class: instance };
92
+ }
93
+
38
94
  setClass(_class: ClassClass | string) {
39
- if (isString(_class)) _class = this.databaseById(_class);
40
- const classInstance = new (_class as ClassClass)();
41
- this["execMethod"]("onSet", [this], classInstance);
95
+ const { instance } = this._createClassInstance(_class);
96
+ const classInstance = instance;
97
+ (this as any)["execMethod"]("onSet", [this], classInstance);
42
98
  return classInstance;
43
99
  }
44
100
 
45
- /**
46
- * Allows to give a set of already defined properties to the player (default equipment, or a list of skills to learn according to the level)
47
- *
48
- * ```ts
49
- * import { Hero } from 'my-database/classes/hero'
50
- *
51
- * player.setActor(Hero)
52
- * ```
53
- *
54
- * @title Set Actor
55
- * @method player.setActor(ActorClass)
56
- * @param {ActorClass | string} actorClass actor class or id
57
- * @returns {instance of ActorClass}
58
- * @memberof ClassManager
59
- * */
60
101
  setActor(actorClass: ActorClass | string) {
61
- if (isString(actorClass)) actorClass = this.databaseById(actorClass);
102
+ if (isString(actorClass)) actorClass = (this as any).databaseById(actorClass);
62
103
  const actor = new (actorClass as ActorClass)();
63
104
  ["name", "initialLevel", "finalLevel", "expCurve"].forEach((key) => {
64
- if (actor[key]) this[key] = actor[key];
105
+ if (actor[key]) (this as any)[key] = actor[key];
65
106
  });
66
107
  for (let param in actor.parameters) {
67
- this.addParameter(param, actor.parameters[param]);
108
+ (this as any).addParameter(param, actor.parameters[param]);
68
109
  }
69
110
  for (let item of actor.startingEquipment) {
70
- this.addItem(item);
71
- this.equip(item, true);
111
+ const inventory = (this as any).addItem(item);
112
+ const itemId = inventory?.id?.();
113
+ if (itemId) {
114
+ (this as any).equip(itemId, true);
115
+ }
72
116
  }
73
117
  if (actor.class) this.setClass(actor.class);
74
- this["execMethod"]("onSet", [this], actor);
118
+ (this as any)["execMethod"]("onSet", [this], actor);
75
119
  return actor;
76
120
  }
77
- };
121
+ } as unknown as TBase;
122
+ }
123
+
124
+ /**
125
+ * Interface for Class Manager functionality
126
+ *
127
+ * Provides class and actor management capabilities including character class assignment
128
+ * and actor setup. This interface defines the public API of the ClassManager mixin.
129
+ */
130
+ export interface IClassManager {
131
+ /**
132
+ * Assign a class to the player
133
+ *
134
+ * @param _class - The class constructor or class ID to assign to the player
135
+ * @returns The instantiated class object
136
+ */
137
+ setClass(_class: ClassClass | string): any;
138
+
139
+ /**
140
+ * Set up the player as a specific actor archetype
141
+ *
142
+ * @param actorClass - The actor constructor or actor ID to assign to the player
143
+ * @returns The instantiated actor object
144
+ */
145
+ setActor(actorClass: ActorClass | string): any;
78
146
  }