@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.
- package/dist/Gui/DialogGui.d.ts +5 -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 +28 -3
- package/dist/Gui/TitleGui.d.ts +23 -0
- package/dist/Gui/index.d.ts +10 -1
- package/dist/Player/BattleManager.d.ts +34 -12
- package/dist/Player/ClassManager.d.ts +46 -13
- package/dist/Player/ComponentManager.d.ts +123 -0
- package/dist/Player/Components.d.ts +345 -0
- package/dist/Player/EffectManager.d.ts +86 -0
- package/dist/Player/ElementManager.d.ts +104 -0
- package/dist/Player/GoldManager.d.ts +22 -0
- package/dist/Player/GuiManager.d.ts +259 -0
- package/dist/Player/ItemFixture.d.ts +6 -0
- package/dist/Player/ItemManager.d.ts +450 -9
- package/dist/Player/MoveManager.d.ts +324 -69
- package/dist/Player/ParameterManager.d.ts +344 -14
- package/dist/Player/Player.d.ts +460 -8
- package/dist/Player/SkillManager.d.ts +197 -15
- package/dist/Player/StateManager.d.ts +89 -25
- package/dist/Player/VariableManager.d.ts +74 -0
- package/dist/RpgServer.d.ts +502 -64
- package/dist/RpgServerEngine.d.ts +2 -1
- package/dist/decorators/event.d.ts +46 -0
- package/dist/decorators/map.d.ts +287 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +21653 -20900
- package/dist/index.js.map +1 -1
- package/dist/logs/log.d.ts +2 -3
- package/dist/module.d.ts +43 -1
- package/dist/presets/index.d.ts +0 -9
- package/dist/rooms/BaseRoom.d.ts +132 -0
- package/dist/rooms/lobby.d.ts +10 -2
- package/dist/rooms/map.d.ts +1236 -17
- 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 +14 -10
- package/src/Gui/DialogGui.ts +19 -4
- 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 +146 -16
- package/src/Gui/TitleGui.ts +39 -0
- package/src/Gui/index.ts +15 -2
- package/src/Player/BattleManager.ts +91 -49
- package/src/Player/ClassManager.ts +118 -50
- package/src/Player/ComponentManager.ts +425 -19
- package/src/Player/Components.ts +380 -0
- package/src/Player/EffectManager.ts +81 -44
- package/src/Player/ElementManager.ts +109 -86
- package/src/Player/GoldManager.ts +32 -35
- package/src/Player/GuiManager.ts +308 -150
- package/src/Player/ItemFixture.ts +4 -5
- package/src/Player/ItemManager.ts +774 -355
- package/src/Player/MoveManager.ts +1544 -774
- package/src/Player/ParameterManager.ts +546 -104
- package/src/Player/Player.ts +1163 -88
- package/src/Player/SkillManager.ts +520 -195
- package/src/Player/StateManager.ts +170 -182
- package/src/Player/VariableManager.ts +101 -63
- package/src/RpgServer.ts +525 -63
- package/src/core/context.ts +1 -0
- package/src/decorators/event.ts +61 -0
- package/src/decorators/map.ts +327 -0
- package/src/index.ts +11 -1
- package/src/logs/log.ts +10 -3
- package/src/module.ts +126 -3
- package/src/presets/index.ts +1 -10
- package/src/rooms/BaseRoom.ts +232 -0
- package/src/rooms/lobby.ts +25 -7
- package/src/rooms/map.ts +2502 -194
- package/src/services/save.ts +147 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/localStorage.ts +76 -0
- package/tests/battle.spec.ts +375 -0
- package/tests/change-map.spec.ts +72 -0
- package/tests/class.spec.ts +274 -0
- package/tests/effect.spec.ts +219 -0
- package/tests/element.spec.ts +221 -0
- package/tests/event.spec.ts +80 -0
- package/tests/gold.spec.ts +99 -0
- package/tests/item.spec.ts +609 -0
- package/tests/module.spec.ts +38 -0
- package/tests/move.spec.ts +601 -0
- package/tests/player-param.spec.ts +28 -0
- package/tests/prediction-reconciliation.spec.ts +182 -0
- package/tests/random-move.spec.ts +65 -0
- package/tests/skill.spec.ts +658 -0
- package/tests/state.spec.ts +467 -0
- package/tests/variable.spec.ts +185 -0
- package/tests/world-maps.spec.ts +896 -0
- package/vite.config.ts +16 -0
- package/dist/Player/Event.d.ts +0 -0
- package/src/Player/Event.ts +0 -0
package/src/Gui/ShopGui.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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:
|
|
16
|
-
name:
|
|
17
|
-
description:
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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 "
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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
|
|
27
|
-
Base
|
|
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
|
-
|
|
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(
|
|
77
|
-
console.log(paramA, paramB)
|
|
92
|
+
const paramB = getParam(self as any);
|
|
78
93
|
if (skill) {
|
|
79
|
-
fn =
|
|
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 =
|
|
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 =
|
|
105
|
+
const coef = self.coefficientElements(attackerPlayer);
|
|
91
106
|
if (coef >= 2) {
|
|
92
107
|
elementVulnerable = true;
|
|
93
108
|
}
|
|
94
109
|
damage *= coef;
|
|
95
|
-
fn =
|
|
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 (
|
|
105
|
-
fn =
|
|
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 (
|
|
129
|
+
if (self.hasEffect(Effect.SUPER_GUARD)) {
|
|
115
130
|
damage /= 4;
|
|
116
131
|
superGuard = true;
|
|
117
132
|
}
|
|
118
|
-
|
|
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
|
|
130
|
-
|
|
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):
|
|
10
|
-
equip(
|
|
9
|
+
addItem(item: any): any;
|
|
10
|
+
equip(itemId: string, equip?: boolean | 'auto'): void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
*
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
const classInstance =
|
|
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
|
-
|
|
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
|
}
|