@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,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,45 +1,22 @@
1
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
- getCurrentMap(): any;
15
+ getCurrentMap(): ReturnType<RpgPlayer['getCurrentMap']>;
13
16
  }
14
17
 
15
- /**
16
- * Battle Manager Mixin
17
- *
18
- * Provides battle management capabilities to any class. This mixin handles
19
- * damage calculation, critical hits, elemental vulnerabilities, and guard effects.
20
- * It implements a comprehensive battle system with customizable formulas and effects.
21
- *
22
- * @param Base - The base class to extend with battle management
23
- * @returns Extended class with battle management methods
24
- *
25
- * @example
26
- * ```ts
27
- * class MyPlayer extends WithBattleManager(BasePlayer) {
28
- * constructor() {
29
- * super();
30
- * // Battle system is automatically initialized
31
- * }
32
- * }
33
- *
34
- * const player = new MyPlayer();
35
- * const attacker = new MyPlayer();
36
- * const result = player.applyDamage(attacker);
37
- * console.log(`Damage dealt: ${result.damage}`);
38
- * ```
39
- */
40
- export function WithBattleManager<TBase extends PlayerCtor>(Base: TBase) {
41
- return class extends Base {
42
- /**
18
+ export interface IBattleManager {
19
+ /**
43
20
  * Apply damage. Player will lose HP. the `attackerPlayer` parameter is the other player, the one who attacks.
44
21
  *
45
22
  * If you don't set the skill parameter, it will be a physical attack.
@@ -71,6 +48,17 @@ export function WithBattleManager<TBase extends PlayerCtor>(Base: TBase) {
71
48
  * }
72
49
  * ```
73
50
  */
51
+ applyDamage(attackerPlayer: RpgPlayer, skill?: any): {
52
+ damage: number;
53
+ critical: boolean;
54
+ elementVulnerable: boolean;
55
+ guard: boolean;
56
+ superGuard: boolean;
57
+ };
58
+ }
59
+
60
+ export function WithBattleManager<TBase extends PlayerCtor>(Base: TBase): new (...args: ConstructorParameters<TBase>) => InstanceType<TBase> & IBattleManager {
61
+ return class extends Base {
74
62
  applyDamage(
75
63
  attackerPlayer: RpgPlayer,
76
64
  skill?: any
@@ -81,9 +69,10 @@ export function WithBattleManager<TBase extends PlayerCtor>(Base: TBase) {
81
69
  guard: boolean;
82
70
  superGuard: boolean;
83
71
  } {
72
+ const self = this as unknown as PlayerWithMixins;
84
73
  const getParam = (player: RpgPlayer) => {
85
74
  const params = {};
86
- (this as any).parameters.forEach((val, key) => {
75
+ Object.keys(self.parameters).forEach((key) => {
87
76
  params[key] = (player as any).param[key];
88
77
  });
89
78
  return {
@@ -100,26 +89,25 @@ export function WithBattleManager<TBase extends PlayerCtor>(Base: TBase) {
100
89
  let superGuard = false;
101
90
  let elementVulnerable = false;
102
91
  const paramA = getParam(attackerPlayer);
103
- const paramB = getParam(<any>this);
104
- console.log(paramA, paramB)
92
+ const paramB = getParam(self as any);
105
93
  if (skill) {
106
- fn = this.getFormulas("damageSkill");
94
+ fn = self.getFormulas("damageSkill");
107
95
  if (!fn) {
108
96
  throw new Error("Skill Formulas not exists");
109
97
  }
110
98
  damage = fn(paramA, paramB, skill);
111
99
  } else {
112
- fn = this.getFormulas("damagePhysic");
100
+ fn = self.getFormulas("damagePhysic");
113
101
  if (!fn) {
114
102
  throw new Error("Physic Formulas not exists");
115
103
  }
116
104
  damage = fn(paramA, paramB);
117
- const coef = (this as any).coefficientElements(attackerPlayer);
105
+ const coef = self.coefficientElements(attackerPlayer);
118
106
  if (coef >= 2) {
119
107
  elementVulnerable = true;
120
108
  }
121
109
  damage *= coef;
122
- fn = this.getFormulas("damageCritical");
110
+ fn = self.getFormulas("damageCritical");
123
111
  if (fn) {
124
112
  let newDamage = fn(damage, paramA, paramB);
125
113
  if (damage != newDamage) {
@@ -128,8 +116,8 @@ export function WithBattleManager<TBase extends PlayerCtor>(Base: TBase) {
128
116
  damage = newDamage;
129
117
  }
130
118
  }
131
- if ((this as any).hasEffect(Effect.GUARD)) {
132
- fn = this.getFormulas("damageGuard");
119
+ if (self.hasEffect(Effect.GUARD)) {
120
+ fn = self.getFormulas("damageGuard");
133
121
  if (fn) {
134
122
  let newDamage = fn(damage, paramA, paramB);
135
123
  if (damage != newDamage) {
@@ -138,11 +126,11 @@ export function WithBattleManager<TBase extends PlayerCtor>(Base: TBase) {
138
126
  damage = newDamage;
139
127
  }
140
128
  }
141
- if ((this as any).hasEffect(Effect.SUPER_GUARD)) {
129
+ if (self.hasEffect(Effect.SUPER_GUARD)) {
142
130
  damage /= 4;
143
131
  superGuard = true;
144
132
  }
145
- (this as any).hp -= damage;
133
+ self.hp -= damage;
146
134
  return {
147
135
  damage,
148
136
  critical,
@@ -179,14 +167,9 @@ export function WithBattleManager<TBase extends PlayerCtor>(Base: TBase) {
179
167
  * ```
180
168
  */
181
169
  getFormulas(name: string) {
182
- const map = (this as any).getCurrentMap();
183
- return map.damageFormulas[name];
170
+ const self = this as unknown as PlayerWithMixins;
171
+ const map = self.getCurrentMap();
172
+ return map?.damageFormulas[name];
184
173
  }
185
- } as unknown as TBase
186
- }
187
-
188
- /**
189
- * Type helper to extract the interface from the WithBattleManager mixin
190
- * This provides the type without duplicating method signatures
191
- */
192
- export type IBattleManager = InstanceType<ReturnType<typeof WithBattleManager>>;
174
+ } as unknown as any;
175
+ }
@@ -6,8 +6,8 @@ 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
13
  /**
@@ -36,81 +36,68 @@ interface PlayerWithMixins extends RpgCommonPlayer {
36
36
  */
37
37
  export function WithClassManager<TBase extends PlayerCtor>(Base: TBase) {
38
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
+ }
47
+
48
+ private _createClassInstance(classInput: ClassClass | string) {
49
+ const classClass = this._resolveClassInput(classInput);
50
+ const instance = new (classClass as ClassClass)();
51
+ return { classClass, instance };
52
+ }
39
53
 
40
54
  /**
41
- * Assign a class to the player
42
- *
43
- * Sets the player's class, which defines their combat abilities, stat growth,
44
- * and available skills. The class system provides the foundation for character
45
- * progression and specialization. When a class is set, it automatically triggers
46
- * the class's onSet method for any additional initialization.
47
- *
48
- * @param _class - The class constructor or class ID to assign to the player
49
- * @returns The instantiated class object
50
- *
51
- * @example
52
- * ```ts
53
- * import { Fighter } from 'my-database/classes/fighter'
54
- *
55
- * // Set class using constructor
56
- * const fighterClass = player.setClass(Fighter);
57
- * console.log('Class set:', fighterClass.name);
58
- *
59
- * // Set class using string ID
60
- * player.setClass('fighter');
61
- *
62
- * // Class affects available skills and stats
63
- * console.log('Available skills:', player.skills);
64
- * console.log('Class bonuses applied to stats');
65
- *
66
- * // Class determines level progression
67
- * player.level = 5;
68
- * // Skills may be automatically learned based on class definition
69
- * ```
55
+ * Create a class instance without side effects.
70
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
+
71
94
  setClass(_class: ClassClass | string) {
72
- if (isString(_class)) _class = (this as any).databaseById(_class);
73
- const classInstance = new (_class as ClassClass)();
95
+ const { instance } = this._createClassInstance(_class);
96
+ const classInstance = instance;
74
97
  (this as any)["execMethod"]("onSet", [this], classInstance);
75
98
  return classInstance;
76
99
  }
77
100
 
78
- /**
79
- * 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)
80
- *
81
- * Sets up the player as a specific actor archetype, which includes predefined
82
- * characteristics like starting equipment, parameters, level ranges, and associated class.
83
- * This is typically used for creating pre-configured character templates or NPCs
84
- * with specific roles and equipment loadouts.
85
- *
86
- * @param actorClass - The actor constructor or actor ID to assign to the player
87
- * @returns The instantiated actor object
88
- *
89
- * @example
90
- * ```ts
91
- * import { Hero } from 'my-database/classes/hero'
92
- *
93
- * // Set up player as Hero actor
94
- * const heroActor = player.setActor(Hero);
95
- * console.log('Actor configured:', heroActor.name);
96
- *
97
- * // Actor automatically sets up:
98
- * // - Starting equipment (sword, armor, etc.)
99
- * console.log('Starting equipment:', player.equipments());
100
- *
101
- * // - Parameter ranges and growth
102
- * console.log('Level range:', player.initialLevel, '-', player.finalLevel);
103
- *
104
- * // - Associated class
105
- * console.log('Assigned class:', player.class);
106
- *
107
- * // - Experience curve
108
- * console.log('EXP curve:', player.expCurve);
109
- *
110
- * // Actor setup is comprehensive
111
- * player.setActor('hero'); // Can also use string ID
112
- * ```
113
- */
114
101
  setActor(actorClass: ActorClass | string) {
115
102
  if (isString(actorClass)) actorClass = (this as any).databaseById(actorClass);
116
103
  const actor = new (actorClass as ActorClass)();
@@ -121,8 +108,11 @@ export function WithClassManager<TBase extends PlayerCtor>(Base: TBase) {
121
108
  (this as any).addParameter(param, actor.parameters[param]);
122
109
  }
123
110
  for (let item of actor.startingEquipment) {
124
- (this as any).addItem(item);
125
- (this as any).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
+ }
126
116
  }
127
117
  if (actor.class) this.setClass(actor.class);
128
118
  (this as any)["execMethod"]("onSet", [this], actor);
@@ -132,7 +122,25 @@ export function WithClassManager<TBase extends PlayerCtor>(Base: TBase) {
132
122
  }
133
123
 
134
124
  /**
135
- * Type helper to extract the interface from the WithClassManager mixin
136
- * This provides the type without duplicating method signatures
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.
137
129
  */
138
- export type IClassManager = InstanceType<ReturnType<typeof WithClassManager>>;
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;
146
+ }