gotchi-battler-game-logic 2.0.0 → 2.0.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.
- package/README.md +2 -2
- package/game-logic/v1.7/helpers.js +605 -0
- package/game-logic/v1.7/index.js +14 -607
- package/index.js +1 -1
- package/package.json +1 -1
- package/schemas/team.json +54 -0
- package/scripts/data/team1.json +13 -0
- package/scripts/runBattle.js +4 -2
package/README.md
CHANGED
|
@@ -15,11 +15,11 @@ npm install gotchi-battler-game-logic
|
|
|
15
15
|
To use the module in your project, import it as follows:
|
|
16
16
|
|
|
17
17
|
```javascript
|
|
18
|
-
const {
|
|
18
|
+
const { battle } = require('gotchi-battler-game-logic')
|
|
19
19
|
|
|
20
20
|
// team1 and team2 an in-game team objects (see below)
|
|
21
21
|
// seed is a random seed for the game
|
|
22
|
-
const result =
|
|
22
|
+
const result = battle(team1, team2, seed)
|
|
23
23
|
```
|
|
24
24
|
The schema for the in-game team object can be found in `/schemas/team.json`
|
|
25
25
|
|
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
const {
|
|
2
|
+
PASSIVES,
|
|
3
|
+
BUFF_MULT_EFFECTS,
|
|
4
|
+
BUFF_FLAT_EFFECTS,
|
|
5
|
+
DEBUFF_MULT_EFFECTS,
|
|
6
|
+
DEBUFF_FLAT_EFFECTS,
|
|
7
|
+
MULTS
|
|
8
|
+
} = require('./constants')
|
|
9
|
+
|
|
10
|
+
// Get only alive gotchis in a team
|
|
11
|
+
const getAlive = (team, row) => {
|
|
12
|
+
if (row) {
|
|
13
|
+
return team.formation[row].filter(x => x).filter(x => x.health > 0)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return [...team.formation.front, ...team.formation.back].filter(x => x).filter(x => x.health > 0)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get the formation position of a gotchi
|
|
21
|
+
* @param {Object} team1 An in-game team object
|
|
22
|
+
* @param {Object} team2 An in-game team object
|
|
23
|
+
* @param {Number} gotchiId The id of the gotchi
|
|
24
|
+
* @returns {Object} position The formation position of the gotchi
|
|
25
|
+
* @returns {Number} position.team The team the gotchi is on
|
|
26
|
+
* @returns {String} position.row The row the gotchi is on
|
|
27
|
+
* @returns {Number} position.position The position of the gotchi in the row
|
|
28
|
+
* @returns {null} position null if the gotchi is not found
|
|
29
|
+
**/
|
|
30
|
+
const getFormationPosition = (team1, team2, gotchiId) => {
|
|
31
|
+
const team1FrontIndex = team1.formation.front.findIndex(x => x && x.id === gotchiId)
|
|
32
|
+
|
|
33
|
+
if (team1FrontIndex !== -1) return {
|
|
34
|
+
team: 1,
|
|
35
|
+
row: 'front',
|
|
36
|
+
position: team1FrontIndex,
|
|
37
|
+
name: team1.formation.front[team1FrontIndex].name
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const team1BackIndex = team1.formation.back.findIndex(x => x && x.id === gotchiId)
|
|
41
|
+
|
|
42
|
+
if (team1BackIndex !== -1) return {
|
|
43
|
+
team: 1,
|
|
44
|
+
row: 'back',
|
|
45
|
+
position: team1BackIndex,
|
|
46
|
+
name: team1.formation.back[team1BackIndex].name
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const team2FrontIndex = team2.formation.front.findIndex(x => x && x.id === gotchiId)
|
|
50
|
+
|
|
51
|
+
if (team2FrontIndex !== -1) return {
|
|
52
|
+
team: 2,
|
|
53
|
+
row: 'front',
|
|
54
|
+
position: team2FrontIndex,
|
|
55
|
+
name: team2.formation.front[team2FrontIndex].name
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const team2BackIndex = team2.formation.back.findIndex(x => x && x.id === gotchiId)
|
|
59
|
+
|
|
60
|
+
if (team2BackIndex !== -1) return {
|
|
61
|
+
team: 2,
|
|
62
|
+
row: 'back',
|
|
63
|
+
position: team2BackIndex,
|
|
64
|
+
name: team2.formation.back[team2BackIndex].name
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get the leader gotchi of a team
|
|
72
|
+
* @param {Object} team An in-game team object
|
|
73
|
+
* @returns {Object} gotchi The leader gotchi
|
|
74
|
+
* @returns {Number} leader.id The id of the gotchi
|
|
75
|
+
* @returns {String} leader.special The special object of the gotchi
|
|
76
|
+
* @returns {String} leader.special.class The class of the special
|
|
77
|
+
**/
|
|
78
|
+
const getLeaderGotchi = (team) => {
|
|
79
|
+
const leader = [...team.formation.front, ...team.formation.back].find(x => x && x.id === team.leader)
|
|
80
|
+
|
|
81
|
+
if (!leader) throw new Error('Leader not found')
|
|
82
|
+
|
|
83
|
+
return leader
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the next gotchi to act
|
|
88
|
+
* @param {Object} team1 An in-game team object
|
|
89
|
+
* @param {Object} team2 An in-game team object
|
|
90
|
+
* @param {Function} rng The random number generator
|
|
91
|
+
* @returns {Object} position The formation position of the gotchi
|
|
92
|
+
**/
|
|
93
|
+
const getNextToAct = (team1, team2, rng) => {
|
|
94
|
+
const aliveGotchis = [...getAlive(team1), ...getAlive(team2)]
|
|
95
|
+
|
|
96
|
+
aliveGotchis.sort((a, b) => a.actionDelay - b.actionDelay)
|
|
97
|
+
|
|
98
|
+
let toAct = aliveGotchis.filter(gotchi => gotchi.actionDelay === aliveGotchis[0].actionDelay)
|
|
99
|
+
|
|
100
|
+
// If only one gotchi can act then return it
|
|
101
|
+
if (toAct.length === 1) return getFormationPosition(team1, team2, toAct[0].id)
|
|
102
|
+
|
|
103
|
+
// Lowest speeds win tiebreaker
|
|
104
|
+
toAct.sort((a, b) => a.speed - b.speed)
|
|
105
|
+
toAct = toAct.filter(gotchi => gotchi.speed === toAct[0].speed)
|
|
106
|
+
|
|
107
|
+
// If only one gotchi can act then return it
|
|
108
|
+
|
|
109
|
+
if (toAct.length === 1) return getFormationPosition(team1, team2, toAct[0].id)
|
|
110
|
+
|
|
111
|
+
// If still tied then randomly choose
|
|
112
|
+
const randomIndex = Math.floor(rng() * toAct.length)
|
|
113
|
+
|
|
114
|
+
if (!toAct[randomIndex]) throw new Error(`No gotchi found at index ${randomIndex}`)
|
|
115
|
+
|
|
116
|
+
toAct = toAct[randomIndex]
|
|
117
|
+
return getFormationPosition(team1, team2, toAct.id)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const getTarget = (defendingTeam, rng) => {
|
|
121
|
+
// Check for taunt gotchis
|
|
122
|
+
const taunt = [...getAlive(defendingTeam, 'front'), ...getAlive(defendingTeam, 'back')].filter(gotchi => gotchi.statuses && gotchi.statuses.includes("taunt"))
|
|
123
|
+
|
|
124
|
+
if (taunt.length) {
|
|
125
|
+
if (taunt.length === 1) return taunt[0]
|
|
126
|
+
|
|
127
|
+
// If multiple taunt gotchis then randomly choose one
|
|
128
|
+
return taunt[Math.floor(rng() * taunt.length)]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Target gotchis in the front row first
|
|
132
|
+
const frontRow = getAlive(defendingTeam, 'front')
|
|
133
|
+
|
|
134
|
+
if (frontRow.length) {
|
|
135
|
+
return frontRow[Math.floor(rng() * frontRow.length)]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// If no gotchis in front row then target back row
|
|
139
|
+
const backRow = getAlive(defendingTeam, 'back')
|
|
140
|
+
|
|
141
|
+
if (backRow.length) {
|
|
142
|
+
return backRow[Math.floor(rng() * backRow.length)]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
throw new Error('No gotchis to target')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const applySpeedPenalty = (gotchi, penalty) => {
|
|
149
|
+
const speedPenalty = (gotchi.speed - 100) * penalty
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
...gotchi,
|
|
153
|
+
magic: gotchi.magic - speedPenalty,
|
|
154
|
+
physical: gotchi.physical - speedPenalty
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get the damage of an attack
|
|
160
|
+
* @param {Object} attackingTeam The attacking team
|
|
161
|
+
* @param {Object} defendingTeam The defending team
|
|
162
|
+
* @param {Object} attackingGotchi The gotchi attacking
|
|
163
|
+
* @param {Object} defendingGotchi The gotchi defending
|
|
164
|
+
* @param {Number} multiplier The damage multiplier
|
|
165
|
+
* @param {Boolean} ignoreArmor Whether to ignore armor
|
|
166
|
+
* @param {Number} speedPenalty The speed penalty to apply
|
|
167
|
+
* @returns {Number} damage The damage of the attack
|
|
168
|
+
**/
|
|
169
|
+
const getDamage = (attackingTeam, defendingTeam, attackingGotchi, defendingGotchi, multiplier, ignoreArmor, speedPenalty) => {
|
|
170
|
+
|
|
171
|
+
const attackerWithSpeedPenalty = speedPenalty ? applySpeedPenalty(attackingGotchi, speedPenalty) : attackingGotchi
|
|
172
|
+
|
|
173
|
+
// Apply any status effects
|
|
174
|
+
const modifiedAttackingGotchi = getModifiedStats(attackerWithSpeedPenalty)
|
|
175
|
+
const modifiedDefendingGotchi = getModifiedStats(defendingGotchi)
|
|
176
|
+
|
|
177
|
+
let attackValue = modifiedAttackingGotchi.attack === 'magic' ? modifiedAttackingGotchi.magic : modifiedAttackingGotchi.physical
|
|
178
|
+
|
|
179
|
+
// If attacking gotchi is in the front row then apply front row attack bonus
|
|
180
|
+
if (getFormationPosition(attackingTeam, defendingTeam, attackingGotchi.id).row === 'front') {
|
|
181
|
+
attackValue = Math.round(attackValue * MULTS.FRONT_ROW_ATK_BONUS)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let defenseValue = modifiedAttackingGotchi.attack === 'magic' ? modifiedDefendingGotchi.magic : modifiedDefendingGotchi.physical
|
|
185
|
+
|
|
186
|
+
// If defending gotchi is in the front row then apply front row defence penalty
|
|
187
|
+
if (getFormationPosition(attackingTeam, defendingTeam, defendingGotchi.id).row === 'front') {
|
|
188
|
+
defenseValue = Math.round(defenseValue * MULTS.FRONT_ROW_DEF_NERF)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Add armor to defense value
|
|
192
|
+
if (!ignoreArmor) defenseValue += modifiedDefendingGotchi.armor
|
|
193
|
+
|
|
194
|
+
// Calculate damage
|
|
195
|
+
let damage = Math.round((attackValue / defenseValue) * 100)
|
|
196
|
+
|
|
197
|
+
// Apply multiplier
|
|
198
|
+
if (multiplier) damage = Math.round(damage * multiplier)
|
|
199
|
+
|
|
200
|
+
// check for environment effects
|
|
201
|
+
if (defendingGotchi.environmentEffects && defendingGotchi.environmentEffects.length > 0) {
|
|
202
|
+
damage = Math.round(damage * (1 + (defendingGotchi.environmentEffects.length * 0.5)))
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return damage
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Apply status effects to a gotchi
|
|
210
|
+
* @param {Object} gotchi An in-game gotchi object
|
|
211
|
+
* @returns {Object} gotchi An in-game gotchi object with modified stats
|
|
212
|
+
*/
|
|
213
|
+
const getModifiedStats = (gotchi) => {
|
|
214
|
+
const statMods = {}
|
|
215
|
+
|
|
216
|
+
gotchi.statuses.forEach(status => {
|
|
217
|
+
const statusStatMods = {}
|
|
218
|
+
|
|
219
|
+
// apply any modifier from BUFF_MULT_EFFECTS
|
|
220
|
+
if (BUFF_MULT_EFFECTS[status]) {
|
|
221
|
+
Object.keys(BUFF_MULT_EFFECTS[status]).forEach(stat => {
|
|
222
|
+
const modifier = Math.round(gotchi[stat] * BUFF_MULT_EFFECTS[status][stat])
|
|
223
|
+
|
|
224
|
+
statusStatMods[stat] = modifier
|
|
225
|
+
})
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// apply any modifier from BUFF_FLAT_EFFECTS
|
|
229
|
+
if (BUFF_FLAT_EFFECTS[status]) {
|
|
230
|
+
Object.keys(BUFF_FLAT_EFFECTS[status]).forEach(stat => {
|
|
231
|
+
if (statusStatMods[stat]) {
|
|
232
|
+
// If a mod for this status already exists, only add if the new mod is greater
|
|
233
|
+
if (BUFF_FLAT_EFFECTS[status][stat] > statusStatMods[stat]) statusStatMods[stat] = BUFF_FLAT_EFFECTS[status][stat]
|
|
234
|
+
} else {
|
|
235
|
+
statusStatMods[stat] = BUFF_FLAT_EFFECTS[status][stat]
|
|
236
|
+
}
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// apply any modifier from DEBUFF_MULT_EFFECTS
|
|
241
|
+
if (DEBUFF_MULT_EFFECTS[status]) {
|
|
242
|
+
Object.keys(DEBUFF_MULT_EFFECTS[status]).forEach(stat => {
|
|
243
|
+
const modifier = Math.round(gotchi[stat] * DEBUFF_MULT_EFFECTS[status][stat])
|
|
244
|
+
|
|
245
|
+
statusStatMods[stat] = -modifier
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// apply any modifier from DEBUFF_FLAT_EFFECTS
|
|
250
|
+
if (DEBUFF_FLAT_EFFECTS[status]) {
|
|
251
|
+
Object.keys(DEBUFF_FLAT_EFFECTS[status]).forEach(stat => {
|
|
252
|
+
if (statusStatMods[stat]) {
|
|
253
|
+
// If a mod for this status already exists, only add if the new mod is greater
|
|
254
|
+
if (DEBUFF_FLAT_EFFECTS[status][stat] < statusStatMods[stat]) statusStatMods[stat] = DEBUFF_FLAT_EFFECTS[status][stat]
|
|
255
|
+
} else {
|
|
256
|
+
statusStatMods[stat] = -DEBUFF_FLAT_EFFECTS[status][stat]
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// apply status mods
|
|
262
|
+
Object.keys(statusStatMods).forEach(stat => {
|
|
263
|
+
statMods[stat] = statMods[stat] ? statMods[stat] + statusStatMods[stat] : statusStatMods[stat]
|
|
264
|
+
})
|
|
265
|
+
})
|
|
266
|
+
|
|
267
|
+
const modifiedGotchi = {
|
|
268
|
+
...gotchi
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// apply stat mods
|
|
272
|
+
Object.keys(statMods).forEach(stat => {
|
|
273
|
+
if (statMods[stat] < 0) {
|
|
274
|
+
modifiedGotchi[stat] = modifiedGotchi[stat] + statMods[stat] < 0 ? 0 : modifiedGotchi[stat] + statMods[stat]
|
|
275
|
+
} else {
|
|
276
|
+
modifiedGotchi[stat] += statMods[stat]
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
// Recalculate attack type
|
|
282
|
+
modifiedGotchi.attack = modifiedGotchi.magic > modifiedGotchi.physical ? 'magic' : 'physical'
|
|
283
|
+
|
|
284
|
+
return modifiedGotchi
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const calculateActionDelay = (gotchi) => {
|
|
288
|
+
// Calculate action delay and round to 3 decimal places
|
|
289
|
+
return Math.round(((100 / getModifiedStats(gotchi).speed) + Number.EPSILON) * 1000) / 1000
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const getNewActionDelay = (gotchi) => {
|
|
293
|
+
// Calculate new action delay and round to 3 decimal places
|
|
294
|
+
return Math.round((gotchi.actionDelay + calculateActionDelay(gotchi) + Number.EPSILON) * 1000) / 1000
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Simplify a team object for storage
|
|
299
|
+
* @param {Object} team An in-game team object
|
|
300
|
+
* @returns {Object} simplifiedTeam A simplified team object
|
|
301
|
+
*/
|
|
302
|
+
const simplifyTeam = (team) => {
|
|
303
|
+
return {
|
|
304
|
+
name: team.name,
|
|
305
|
+
owner: team.owner,
|
|
306
|
+
leaderId: team.leader,
|
|
307
|
+
rows: [
|
|
308
|
+
{
|
|
309
|
+
slots: team.formation.front.map((x) => {
|
|
310
|
+
return {
|
|
311
|
+
isActive: x ? true : false,
|
|
312
|
+
id: x ? x.id : null
|
|
313
|
+
}
|
|
314
|
+
})
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
slots: team.formation.back.map((x) => {
|
|
318
|
+
return {
|
|
319
|
+
isActive: x ? true : false,
|
|
320
|
+
id: x ? x.id : null
|
|
321
|
+
}
|
|
322
|
+
})
|
|
323
|
+
}
|
|
324
|
+
],
|
|
325
|
+
uiOrder: getUiOrder(team)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Get the UI order of a team (used for the front end)
|
|
331
|
+
* @param {Object} team An in-game team object
|
|
332
|
+
* @returns {Array} uiOrder An array of gotchi ids in the order they should be displayed
|
|
333
|
+
**/
|
|
334
|
+
const getUiOrder = (team) => {
|
|
335
|
+
const uiOrder = []
|
|
336
|
+
|
|
337
|
+
if (team.formation.front[0]) uiOrder.push(team.formation.front[0].id)
|
|
338
|
+
if (team.formation.back[0]) uiOrder.push(team.formation.back[0].id)
|
|
339
|
+
if (team.formation.front[1]) uiOrder.push(team.formation.front[1].id)
|
|
340
|
+
if (team.formation.back[1]) uiOrder.push(team.formation.back[1].id)
|
|
341
|
+
if (team.formation.front[2]) uiOrder.push(team.formation.front[2].id)
|
|
342
|
+
if (team.formation.back[2]) uiOrder.push(team.formation.back[2].id)
|
|
343
|
+
if (team.formation.front[3]) uiOrder.push(team.formation.front[3].id)
|
|
344
|
+
if (team.formation.back[3]) uiOrder.push(team.formation.back[3].id)
|
|
345
|
+
if (team.formation.front[4]) uiOrder.push(team.formation.front[4].id)
|
|
346
|
+
if (team.formation.back[4]) uiOrder.push(team.formation.back[4].id)
|
|
347
|
+
|
|
348
|
+
return uiOrder
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Add the leader statuses to a team
|
|
353
|
+
* @param {Object} team An in-game team object
|
|
354
|
+
**/
|
|
355
|
+
const addLeaderToTeam = (team) => {
|
|
356
|
+
// Add passive leader abilities
|
|
357
|
+
const teamLeader = getLeaderGotchi(team)
|
|
358
|
+
|
|
359
|
+
team.leaderPassive = teamLeader.special.id
|
|
360
|
+
|
|
361
|
+
// Apply leader passive statuses
|
|
362
|
+
switch (team.leaderPassive) {
|
|
363
|
+
case 1:
|
|
364
|
+
// Sharpen blades - all allies gain 'sharp_blades' status
|
|
365
|
+
getAlive(team).forEach(x => {
|
|
366
|
+
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
367
|
+
})
|
|
368
|
+
break
|
|
369
|
+
case 2:
|
|
370
|
+
// Cloud of Zen - Leader get 'cloud_of_zen' status
|
|
371
|
+
teamLeader.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
372
|
+
break
|
|
373
|
+
case 3:
|
|
374
|
+
// Frenzy - all allies get 'frenzy' status
|
|
375
|
+
getAlive(team).forEach(x => {
|
|
376
|
+
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
377
|
+
})
|
|
378
|
+
break
|
|
379
|
+
case 4:
|
|
380
|
+
// All allies get 'fortify' status
|
|
381
|
+
getAlive(team).forEach(x => {
|
|
382
|
+
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
break
|
|
386
|
+
case 5:
|
|
387
|
+
// Spread the fear - all allies get 'spread_the_fear' status
|
|
388
|
+
getAlive(team).forEach(x => {
|
|
389
|
+
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
390
|
+
})
|
|
391
|
+
break
|
|
392
|
+
case 6:
|
|
393
|
+
// Cleansing aura - every healer ally and every tank ally gets 'cleansing_aura' status
|
|
394
|
+
getAlive(team).forEach(x => {
|
|
395
|
+
if (x.special.id === 6 || x.special.id === 4) x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
396
|
+
})
|
|
397
|
+
break
|
|
398
|
+
case 7:
|
|
399
|
+
// All allies get 'channel_the_coven' status
|
|
400
|
+
getAlive(team).forEach(x => {
|
|
401
|
+
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
402
|
+
})
|
|
403
|
+
break
|
|
404
|
+
case 8:
|
|
405
|
+
// All allies get 'clan_momentum' status
|
|
406
|
+
getAlive(team).forEach(x => {
|
|
407
|
+
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
408
|
+
})
|
|
409
|
+
break
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const removeLeaderPassivesFromTeam = (team) => {
|
|
414
|
+
let statusesRemoved = []
|
|
415
|
+
if (!team.leaderPassive) return statusesRemoved
|
|
416
|
+
|
|
417
|
+
// Remove leader passive statuses from team
|
|
418
|
+
getAlive(team).forEach(x => {
|
|
419
|
+
// add effects for each status removed
|
|
420
|
+
x.statuses.forEach(status => {
|
|
421
|
+
if (status === PASSIVES[team.leaderPassive - 1]) {
|
|
422
|
+
statusesRemoved.push({
|
|
423
|
+
target: x.id,
|
|
424
|
+
status: status
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
x.statuses = x.statuses.filter(x => x !== PASSIVES[team.leaderPassive - 1])
|
|
430
|
+
})
|
|
431
|
+
|
|
432
|
+
team.leaderPassive = null
|
|
433
|
+
|
|
434
|
+
return statusesRemoved
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const getExpiredStatuses = (team1, team2) => {
|
|
438
|
+
// If leader is dead, remove leader passive
|
|
439
|
+
let statusesExpired = []
|
|
440
|
+
if (team1.leaderPassive && !getAlive(team1).find(x => x.id === team1.leader)) {
|
|
441
|
+
// Remove leader passive statuses
|
|
442
|
+
statusesExpired = removeLeaderPassivesFromTeam(team1)
|
|
443
|
+
}
|
|
444
|
+
if (team2.leaderPassive && !getAlive(team2).find(x => x.id === team2.leader)) {
|
|
445
|
+
// Remove leader passive statuses
|
|
446
|
+
statusesExpired = removeLeaderPassivesFromTeam(team2)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
return statusesExpired
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Add a status to a gotchi
|
|
454
|
+
* @param {Object} gotchi An in-game gotchi object
|
|
455
|
+
* @param {String} status The status to add
|
|
456
|
+
* @returns {Boolean} success A boolean to determine if the status was added
|
|
457
|
+
**/
|
|
458
|
+
const addStatusToGotchi = (gotchi, status) => {
|
|
459
|
+
// Check that gotchi doesn't already have max number of statuses
|
|
460
|
+
if (gotchi.statuses.filter(item => item === status).length >= MULTS.MAX_STATUSES) return false
|
|
461
|
+
|
|
462
|
+
gotchi.statuses.push(status)
|
|
463
|
+
|
|
464
|
+
return true
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const scrambleGotchiIds = (allAliveGotchis, team1, team2) => {
|
|
468
|
+
// check there's no duplicate gotchis
|
|
469
|
+
const gotchiIds = allAliveGotchis.map(x => x.id)
|
|
470
|
+
|
|
471
|
+
if (gotchiIds.length !== new Set(gotchiIds).size) {
|
|
472
|
+
// scramble gotchi ids
|
|
473
|
+
allAliveGotchis.forEach(x => {
|
|
474
|
+
const newId = Math.floor(Math.random() * 10000000)
|
|
475
|
+
|
|
476
|
+
// find gotchi in team1 or team2
|
|
477
|
+
const position = getFormationPosition(team1, team2, x.id)
|
|
478
|
+
|
|
479
|
+
// change gotchi id
|
|
480
|
+
if (position) {
|
|
481
|
+
if (position.team === 1) {
|
|
482
|
+
if (x.id === team1.leader) team1.leader = newId
|
|
483
|
+
team1.formation[position.row][position.position].id = newId
|
|
484
|
+
} else {
|
|
485
|
+
if (x.id === team2.leader) team2.leader = newId
|
|
486
|
+
team2.formation[position.row][position.position].id = newId
|
|
487
|
+
}
|
|
488
|
+
} else {
|
|
489
|
+
throw new Error('Gotchi not found in team1 or team2')
|
|
490
|
+
}
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
// check again
|
|
494
|
+
const newGotchiIds = allAliveGotchis.map(x => x.id)
|
|
495
|
+
if (newGotchiIds.length !== new Set(newGotchiIds).size) {
|
|
496
|
+
// Scramble again
|
|
497
|
+
scrambleGotchiIds(allAliveGotchis, team1, team2)
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Prepare teams for battle
|
|
504
|
+
* @param {Array} allAliveGotchis An array of all alive gotchis
|
|
505
|
+
* @param {Object} team1 An in-game team object
|
|
506
|
+
* @param {Object} team2 An in-game team object
|
|
507
|
+
**/
|
|
508
|
+
const prepareTeams = (allAliveGotchis, team1, team2) => {
|
|
509
|
+
// check there's no duplicate gotchis
|
|
510
|
+
scrambleGotchiIds(allAliveGotchis, team1, team2);
|
|
511
|
+
|
|
512
|
+
allAliveGotchis.forEach(x => {
|
|
513
|
+
// Add statuses property to all gotchis
|
|
514
|
+
x.statuses = []
|
|
515
|
+
|
|
516
|
+
// Calculate initial action delay for all gotchis
|
|
517
|
+
x.actionDelay = calculateActionDelay(x)
|
|
518
|
+
|
|
519
|
+
// Calculate attack type
|
|
520
|
+
x.attack = x.magic > x.physical ? 'magic' : 'physical'
|
|
521
|
+
|
|
522
|
+
// Add original stats to all gotchis
|
|
523
|
+
// Do a deep copy of the gotchi object to avoid modifying the original object
|
|
524
|
+
x.originalStats = JSON.parse(JSON.stringify(x))
|
|
525
|
+
|
|
526
|
+
// Add environmentEffects to all gotchis
|
|
527
|
+
x.environmentEffects = []
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
// Add leader passive to team
|
|
531
|
+
addLeaderToTeam(team1)
|
|
532
|
+
addLeaderToTeam(team2)
|
|
533
|
+
|
|
534
|
+
// Apply stat items
|
|
535
|
+
applyStatItems(allAliveGotchis)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Get log gotchi object for battle logs
|
|
540
|
+
* @param {Array} allAliveGotchis An array of all alive gotchis
|
|
541
|
+
* @returns {Array} logGotchis An array of gotchi objects for logs
|
|
542
|
+
*/
|
|
543
|
+
const getLogGotchis = (allAliveGotchis) => {
|
|
544
|
+
const logGotchis = JSON.parse(JSON.stringify(allAliveGotchis))
|
|
545
|
+
|
|
546
|
+
logGotchis.forEach(x => {
|
|
547
|
+
// Change gotchi.special.class to gotchi.special.gotchiClass to avoid conflicts with class keyword
|
|
548
|
+
x.special.gotchiClass = x.special.class
|
|
549
|
+
|
|
550
|
+
// Remove unnecessary properties to reduce log size
|
|
551
|
+
delete x.special.class
|
|
552
|
+
delete x.snapshotBlock
|
|
553
|
+
delete x.onchainId
|
|
554
|
+
delete x.brs
|
|
555
|
+
delete x.nrg
|
|
556
|
+
delete x.agg
|
|
557
|
+
delete x.spk
|
|
558
|
+
delete x.brn
|
|
559
|
+
delete x.eyc
|
|
560
|
+
delete x.eys
|
|
561
|
+
delete x.kinship
|
|
562
|
+
delete x.xp
|
|
563
|
+
delete x.actionDelay
|
|
564
|
+
delete x.attack
|
|
565
|
+
delete x.originalStats
|
|
566
|
+
delete x.environmentEffects
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
return logGotchis
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Apply stat items to gotchis
|
|
574
|
+
* @param {Array} gotchis An array of gotchis
|
|
575
|
+
*/
|
|
576
|
+
const applyStatItems = (gotchis) => {
|
|
577
|
+
gotchis.forEach(gotchi => {
|
|
578
|
+
// Apply stat items
|
|
579
|
+
if (gotchi.item && gotchi.item.stat && gotchi.item.statValue) {
|
|
580
|
+
gotchi[gotchi.item.stat] += gotchi.item.statValue
|
|
581
|
+
}
|
|
582
|
+
})
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
module.exports = {
|
|
586
|
+
getAlive,
|
|
587
|
+
getFormationPosition,
|
|
588
|
+
getLeaderGotchi,
|
|
589
|
+
getNextToAct,
|
|
590
|
+
getTarget,
|
|
591
|
+
getDamage,
|
|
592
|
+
getModifiedStats,
|
|
593
|
+
calculateActionDelay,
|
|
594
|
+
getNewActionDelay,
|
|
595
|
+
simplifyTeam,
|
|
596
|
+
getUiOrder,
|
|
597
|
+
addLeaderToTeam,
|
|
598
|
+
removeLeaderPassivesFromTeam,
|
|
599
|
+
getExpiredStatuses,
|
|
600
|
+
addStatusToGotchi,
|
|
601
|
+
scrambleGotchiIds,
|
|
602
|
+
prepareTeams,
|
|
603
|
+
getLogGotchis,
|
|
604
|
+
applyStatItems
|
|
605
|
+
}
|
package/game-logic/v1.7/index.js
CHANGED
|
@@ -5,575 +5,26 @@ const teamSchema = require('../../schemas/team.json')
|
|
|
5
5
|
|
|
6
6
|
const { GameError } = require('../../utils/errors')
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const {
|
|
9
9
|
PASSIVES,
|
|
10
|
-
BUFF_MULT_EFFECTS,
|
|
11
|
-
BUFF_FLAT_EFFECTS,
|
|
12
|
-
DEBUFF_MULT_EFFECTS,
|
|
13
|
-
DEBUFF_FLAT_EFFECTS,
|
|
14
10
|
DEBUFFS,
|
|
15
11
|
BUFFS,
|
|
16
12
|
MULTS
|
|
17
13
|
} = require('./constants')
|
|
18
14
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
* @param {Number} gotchiId The id of the gotchi
|
|
33
|
-
* @returns {Object} position The formation position of the gotchi
|
|
34
|
-
* @returns {Number} position.team The team the gotchi is on
|
|
35
|
-
* @returns {String} position.row The row the gotchi is on
|
|
36
|
-
* @returns {Number} position.position The position of the gotchi in the row
|
|
37
|
-
* @returns {null} position null if the gotchi is not found
|
|
38
|
-
**/
|
|
39
|
-
const getFormationPosition = (team1, team2, gotchiId) => {
|
|
40
|
-
const team1FrontIndex = team1.formation.front.findIndex(x => x && x.id === gotchiId)
|
|
41
|
-
|
|
42
|
-
if (team1FrontIndex !== -1) return {
|
|
43
|
-
team: 1,
|
|
44
|
-
row: 'front',
|
|
45
|
-
position: team1FrontIndex,
|
|
46
|
-
name: team1.formation.front[team1FrontIndex].name
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const team1BackIndex = team1.formation.back.findIndex(x => x && x.id === gotchiId)
|
|
50
|
-
|
|
51
|
-
if (team1BackIndex !== -1) return {
|
|
52
|
-
team: 1,
|
|
53
|
-
row: 'back',
|
|
54
|
-
position: team1BackIndex,
|
|
55
|
-
name: team1.formation.back[team1BackIndex].name
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const team2FrontIndex = team2.formation.front.findIndex(x => x && x.id === gotchiId)
|
|
59
|
-
|
|
60
|
-
if (team2FrontIndex !== -1) return {
|
|
61
|
-
team: 2,
|
|
62
|
-
row: 'front',
|
|
63
|
-
position: team2FrontIndex,
|
|
64
|
-
name: team2.formation.front[team2FrontIndex].name
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const team2BackIndex = team2.formation.back.findIndex(x => x && x.id === gotchiId)
|
|
68
|
-
|
|
69
|
-
if (team2BackIndex !== -1) return {
|
|
70
|
-
team: 2,
|
|
71
|
-
row: 'back',
|
|
72
|
-
position: team2BackIndex,
|
|
73
|
-
name: team2.formation.back[team2BackIndex].name
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return null
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Get the leader gotchi of a team
|
|
81
|
-
* @param {Object} team An in-game team object
|
|
82
|
-
* @returns {Object} gotchi The leader gotchi
|
|
83
|
-
* @returns {Number} leader.id The id of the gotchi
|
|
84
|
-
* @returns {String} leader.special The special object of the gotchi
|
|
85
|
-
* @returns {String} leader.special.class The class of the special
|
|
86
|
-
**/
|
|
87
|
-
const getLeaderGotchi = (team) => {
|
|
88
|
-
const leader = [...team.formation.front, ...team.formation.back].find(x => x && x.id === team.leader)
|
|
89
|
-
|
|
90
|
-
if (!leader) throw new Error('Leader not found')
|
|
91
|
-
|
|
92
|
-
return leader
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Get the next gotchi to act
|
|
97
|
-
* @param {Object} team1 An in-game team object
|
|
98
|
-
* @param {Object} team2 An in-game team object
|
|
99
|
-
* @param {Function} rng The random number generator
|
|
100
|
-
* @returns {Object} position The formation position of the gotchi
|
|
101
|
-
**/
|
|
102
|
-
const getNextToAct = (team1, team2, rng) => {
|
|
103
|
-
const aliveGotchis = [...getAlive(team1), ...getAlive(team2)]
|
|
104
|
-
|
|
105
|
-
aliveGotchis.sort((a, b) => a.actionDelay - b.actionDelay)
|
|
106
|
-
|
|
107
|
-
let toAct = aliveGotchis.filter(gotchi => gotchi.actionDelay === aliveGotchis[0].actionDelay)
|
|
108
|
-
|
|
109
|
-
// If only one gotchi can act then return it
|
|
110
|
-
if (toAct.length === 1) return getFormationPosition(team1, team2, toAct[0].id)
|
|
111
|
-
|
|
112
|
-
// Lowest speeds win tiebreaker
|
|
113
|
-
toAct.sort((a, b) => a.speed - b.speed)
|
|
114
|
-
toAct = toAct.filter(gotchi => gotchi.speed === toAct[0].speed)
|
|
115
|
-
|
|
116
|
-
// If only one gotchi can act then return it
|
|
117
|
-
|
|
118
|
-
if (toAct.length === 1) return getFormationPosition(team1, team2, toAct[0].id)
|
|
119
|
-
|
|
120
|
-
// If still tied then randomly choose
|
|
121
|
-
const randomIndex = Math.floor(rng() * toAct.length)
|
|
122
|
-
|
|
123
|
-
if (!toAct[randomIndex]) throw new Error(`No gotchi found at index ${randomIndex}`)
|
|
124
|
-
|
|
125
|
-
toAct = toAct[randomIndex]
|
|
126
|
-
return getFormationPosition(team1, team2, toAct.id)
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const getTarget = (defendingTeam, rng) => {
|
|
130
|
-
// Check for taunt gotchis
|
|
131
|
-
const taunt = [...getAlive(defendingTeam, 'front'), ...getAlive(defendingTeam, 'back')].filter(gotchi => gotchi.statuses && gotchi.statuses.includes("taunt"))
|
|
132
|
-
|
|
133
|
-
if (taunt.length) {
|
|
134
|
-
if (taunt.length === 1) return taunt[0]
|
|
135
|
-
|
|
136
|
-
// If multiple taunt gotchis then randomly choose one
|
|
137
|
-
return taunt[Math.floor(rng() * taunt.length)]
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Target gotchis in the front row first
|
|
141
|
-
const frontRow = getAlive(defendingTeam, 'front')
|
|
142
|
-
|
|
143
|
-
if (frontRow.length) {
|
|
144
|
-
return frontRow[Math.floor(rng() * frontRow.length)]
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// If no gotchis in front row then target back row
|
|
148
|
-
const backRow = getAlive(defendingTeam, 'back')
|
|
149
|
-
|
|
150
|
-
if (backRow.length) {
|
|
151
|
-
return backRow[Math.floor(rng() * backRow.length)]
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
throw new Error('No gotchis to target')
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const applySpeedPenalty = (gotchi, penalty) => {
|
|
158
|
-
const speedPenalty = (gotchi.speed - 100) * penalty
|
|
159
|
-
|
|
160
|
-
return {
|
|
161
|
-
...gotchi,
|
|
162
|
-
magic: gotchi.magic - speedPenalty,
|
|
163
|
-
physical: gotchi.physical - speedPenalty
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Get the damage of an attack
|
|
169
|
-
* @param {Object} attackingTeam The attacking team
|
|
170
|
-
* @param {Object} defendingTeam The defending team
|
|
171
|
-
* @param {Object} attackingGotchi The gotchi attacking
|
|
172
|
-
* @param {Object} defendingGotchi The gotchi defending
|
|
173
|
-
* @param {Number} multiplier The damage multiplier
|
|
174
|
-
* @param {Boolean} ignoreArmor Whether to ignore armor
|
|
175
|
-
* @param {Number} speedPenalty The speed penalty to apply
|
|
176
|
-
* @returns {Number} damage The damage of the attack
|
|
177
|
-
**/
|
|
178
|
-
const getDamage = (attackingTeam, defendingTeam, attackingGotchi, defendingGotchi, multiplier, ignoreArmor, speedPenalty) => {
|
|
179
|
-
|
|
180
|
-
const attackerWithSpeedPenalty = speedPenalty ? applySpeedPenalty(attackingGotchi, speedPenalty) : attackingGotchi
|
|
181
|
-
|
|
182
|
-
// Apply any status effects
|
|
183
|
-
const modifiedAttackingGotchi = getModifiedStats(attackerWithSpeedPenalty)
|
|
184
|
-
const modifiedDefendingGotchi = getModifiedStats(defendingGotchi)
|
|
185
|
-
|
|
186
|
-
let attackValue = modifiedAttackingGotchi.attack === 'magic' ? modifiedAttackingGotchi.magic : modifiedAttackingGotchi.physical
|
|
187
|
-
|
|
188
|
-
// If attacking gotchi is in the front row then apply front row attack bonus
|
|
189
|
-
if (getFormationPosition(attackingTeam, defendingTeam, attackingGotchi.id).row === 'front') {
|
|
190
|
-
attackValue = Math.round(attackValue * MULTS.FRONT_ROW_ATK_BONUS)
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
let defenseValue = modifiedAttackingGotchi.attack === 'magic' ? modifiedDefendingGotchi.magic : modifiedDefendingGotchi.physical
|
|
194
|
-
|
|
195
|
-
// If defending gotchi is in the front row then apply front row defence penalty
|
|
196
|
-
if (getFormationPosition(attackingTeam, defendingTeam, defendingGotchi.id).row === 'front') {
|
|
197
|
-
defenseValue = Math.round(defenseValue * MULTS.FRONT_ROW_DEF_NERF)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Add armor to defense value
|
|
201
|
-
if (!ignoreArmor) defenseValue += modifiedDefendingGotchi.armor
|
|
202
|
-
|
|
203
|
-
// Calculate damage
|
|
204
|
-
let damage = Math.round((attackValue / defenseValue) * 100)
|
|
205
|
-
|
|
206
|
-
// Apply multiplier
|
|
207
|
-
if (multiplier) damage = Math.round(damage * multiplier)
|
|
208
|
-
|
|
209
|
-
// check for environment effects
|
|
210
|
-
if (defendingGotchi.environmentEffects && defendingGotchi.environmentEffects.length > 0) {
|
|
211
|
-
damage = Math.round(damage * (1 + (defendingGotchi.environmentEffects.length * 0.5)))
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return damage
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/**
|
|
218
|
-
* Apply status effects to a gotchi
|
|
219
|
-
* @param {Object} gotchi An in-game gotchi object
|
|
220
|
-
* @returns {Object} gotchi An in-game gotchi object with modified stats
|
|
221
|
-
*/
|
|
222
|
-
const getModifiedStats = (gotchi) => {
|
|
223
|
-
const statMods = {}
|
|
224
|
-
|
|
225
|
-
gotchi.statuses.forEach(status => {
|
|
226
|
-
const statusStatMods = {}
|
|
227
|
-
|
|
228
|
-
// apply any modifier from BUFF_MULT_EFFECTS
|
|
229
|
-
if (BUFF_MULT_EFFECTS[status]) {
|
|
230
|
-
Object.keys(BUFF_MULT_EFFECTS[status]).forEach(stat => {
|
|
231
|
-
const modifier = Math.round(gotchi[stat] * BUFF_MULT_EFFECTS[status][stat])
|
|
232
|
-
|
|
233
|
-
statusStatMods[stat] = modifier
|
|
234
|
-
})
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// apply any modifier from BUFF_FLAT_EFFECTS
|
|
238
|
-
if (BUFF_FLAT_EFFECTS[status]) {
|
|
239
|
-
Object.keys(BUFF_FLAT_EFFECTS[status]).forEach(stat => {
|
|
240
|
-
if (statusStatMods[stat]) {
|
|
241
|
-
// If a mod for this status already exists, only add if the new mod is greater
|
|
242
|
-
if (BUFF_FLAT_EFFECTS[status][stat] > statusStatMods[stat]) statusStatMods[stat] = BUFF_FLAT_EFFECTS[status][stat]
|
|
243
|
-
} else {
|
|
244
|
-
statusStatMods[stat] = BUFF_FLAT_EFFECTS[status][stat]
|
|
245
|
-
}
|
|
246
|
-
})
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// apply any modifier from DEBUFF_MULT_EFFECTS
|
|
250
|
-
if (DEBUFF_MULT_EFFECTS[status]) {
|
|
251
|
-
Object.keys(DEBUFF_MULT_EFFECTS[status]).forEach(stat => {
|
|
252
|
-
const modifier = Math.round(gotchi[stat] * DEBUFF_MULT_EFFECTS[status][stat])
|
|
253
|
-
|
|
254
|
-
statusStatMods[stat] = -modifier
|
|
255
|
-
})
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// apply any modifier from DEBUFF_FLAT_EFFECTS
|
|
259
|
-
if (DEBUFF_FLAT_EFFECTS[status]) {
|
|
260
|
-
Object.keys(DEBUFF_FLAT_EFFECTS[status]).forEach(stat => {
|
|
261
|
-
if (statusStatMods[stat]) {
|
|
262
|
-
// If a mod for this status already exists, only add if the new mod is greater
|
|
263
|
-
if (DEBUFF_FLAT_EFFECTS[status][stat] < statusStatMods[stat]) statusStatMods[stat] = DEBUFF_FLAT_EFFECTS[status][stat]
|
|
264
|
-
} else {
|
|
265
|
-
statusStatMods[stat] = -DEBUFF_FLAT_EFFECTS[status][stat]
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// apply status mods
|
|
271
|
-
Object.keys(statusStatMods).forEach(stat => {
|
|
272
|
-
statMods[stat] = statMods[stat] ? statMods[stat] + statusStatMods[stat] : statusStatMods[stat]
|
|
273
|
-
})
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
const modifiedGotchi = {
|
|
277
|
-
...gotchi
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// apply stat mods
|
|
281
|
-
Object.keys(statMods).forEach(stat => {
|
|
282
|
-
if (statMods[stat] < 0) {
|
|
283
|
-
modifiedGotchi[stat] = modifiedGotchi[stat] + statMods[stat] < 0 ? 0 : modifiedGotchi[stat] + statMods[stat]
|
|
284
|
-
} else {
|
|
285
|
-
modifiedGotchi[stat] += statMods[stat]
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
// Recalculate attack type
|
|
291
|
-
modifiedGotchi.attack = modifiedGotchi.magic > modifiedGotchi.physical ? 'magic' : 'physical'
|
|
292
|
-
|
|
293
|
-
return modifiedGotchi
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
const calculateActionDelay = (gotchi) => {
|
|
297
|
-
// Calculate action delay and round to 3 decimal places
|
|
298
|
-
return Math.round(((100 / getModifiedStats(gotchi).speed) + Number.EPSILON) * 1000) / 1000
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const getNewActionDelay = (gotchi) => {
|
|
302
|
-
// Calculate new action delay and round to 3 decimal places
|
|
303
|
-
return Math.round((gotchi.actionDelay + calculateActionDelay(gotchi) + Number.EPSILON) * 1000) / 1000
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Simplify a team object for storage
|
|
308
|
-
* @param {Object} team An in-game team object
|
|
309
|
-
* @returns {Object} simplifiedTeam A simplified team object
|
|
310
|
-
*/
|
|
311
|
-
const simplifyTeam = (team) => {
|
|
312
|
-
return {
|
|
313
|
-
name: team.name,
|
|
314
|
-
owner: team.owner,
|
|
315
|
-
leaderId: team.leader,
|
|
316
|
-
rows: [
|
|
317
|
-
{
|
|
318
|
-
slots: team.formation.front.map((x) => {
|
|
319
|
-
return {
|
|
320
|
-
isActive: x ? true : false,
|
|
321
|
-
id: x ? x.id : null
|
|
322
|
-
}
|
|
323
|
-
})
|
|
324
|
-
},
|
|
325
|
-
{
|
|
326
|
-
slots: team.formation.back.map((x) => {
|
|
327
|
-
return {
|
|
328
|
-
isActive: x ? true : false,
|
|
329
|
-
id: x ? x.id : null
|
|
330
|
-
}
|
|
331
|
-
})
|
|
332
|
-
}
|
|
333
|
-
],
|
|
334
|
-
uiOrder: getUiOrder(team)
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Get the UI order of a team (used for the front end)
|
|
340
|
-
* @param {Object} team An in-game team object
|
|
341
|
-
* @returns {Array} uiOrder An array of gotchi ids in the order they should be displayed
|
|
342
|
-
**/
|
|
343
|
-
const getUiOrder = (team) => {
|
|
344
|
-
const uiOrder = []
|
|
345
|
-
|
|
346
|
-
if (team.formation.front[0]) uiOrder.push(team.formation.front[0].id)
|
|
347
|
-
if (team.formation.back[0]) uiOrder.push(team.formation.back[0].id)
|
|
348
|
-
if (team.formation.front[1]) uiOrder.push(team.formation.front[1].id)
|
|
349
|
-
if (team.formation.back[1]) uiOrder.push(team.formation.back[1].id)
|
|
350
|
-
if (team.formation.front[2]) uiOrder.push(team.formation.front[2].id)
|
|
351
|
-
if (team.formation.back[2]) uiOrder.push(team.formation.back[2].id)
|
|
352
|
-
if (team.formation.front[3]) uiOrder.push(team.formation.front[3].id)
|
|
353
|
-
if (team.formation.back[3]) uiOrder.push(team.formation.back[3].id)
|
|
354
|
-
if (team.formation.front[4]) uiOrder.push(team.formation.front[4].id)
|
|
355
|
-
if (team.formation.back[4]) uiOrder.push(team.formation.back[4].id)
|
|
356
|
-
|
|
357
|
-
return uiOrder
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Add the leader statuses to a team
|
|
362
|
-
* @param {Object} team An in-game team object
|
|
363
|
-
**/
|
|
364
|
-
const addLeaderToTeam = (team) => {
|
|
365
|
-
// Add passive leader abilities
|
|
366
|
-
const teamLeader = getLeaderGotchi(team)
|
|
367
|
-
|
|
368
|
-
team.leaderPassive = teamLeader.special.id
|
|
369
|
-
|
|
370
|
-
// Apply leader passive statuses
|
|
371
|
-
switch (team.leaderPassive) {
|
|
372
|
-
case 1:
|
|
373
|
-
// Sharpen blades - all allies gain 'sharp_blades' status
|
|
374
|
-
getAlive(team).forEach(x => {
|
|
375
|
-
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
376
|
-
})
|
|
377
|
-
break
|
|
378
|
-
case 2:
|
|
379
|
-
// Cloud of Zen - Leader get 'cloud_of_zen' status
|
|
380
|
-
teamLeader.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
381
|
-
break
|
|
382
|
-
case 3:
|
|
383
|
-
// Frenzy - all allies get 'frenzy' status
|
|
384
|
-
getAlive(team).forEach(x => {
|
|
385
|
-
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
386
|
-
})
|
|
387
|
-
break
|
|
388
|
-
case 4:
|
|
389
|
-
// All allies get 'fortify' status
|
|
390
|
-
getAlive(team).forEach(x => {
|
|
391
|
-
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
392
|
-
})
|
|
393
|
-
|
|
394
|
-
break
|
|
395
|
-
case 5:
|
|
396
|
-
// Spread the fear - all allies get 'spread_the_fear' status
|
|
397
|
-
getAlive(team).forEach(x => {
|
|
398
|
-
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
399
|
-
})
|
|
400
|
-
break
|
|
401
|
-
case 6:
|
|
402
|
-
// Cleansing aura - every healer ally and every tank ally gets 'cleansing_aura' status
|
|
403
|
-
getAlive(team).forEach(x => {
|
|
404
|
-
if (x.special.id === 6 || x.special.id === 4) x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
405
|
-
})
|
|
406
|
-
break
|
|
407
|
-
case 7:
|
|
408
|
-
// All allies get 'channel_the_coven' status
|
|
409
|
-
getAlive(team).forEach(x => {
|
|
410
|
-
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
411
|
-
})
|
|
412
|
-
break
|
|
413
|
-
case 8:
|
|
414
|
-
// All allies get 'clan_momentum' status
|
|
415
|
-
getAlive(team).forEach(x => {
|
|
416
|
-
x.statuses.push(PASSIVES[team.leaderPassive - 1])
|
|
417
|
-
})
|
|
418
|
-
break
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const removeLeaderPassivesFromTeam = (team) => {
|
|
423
|
-
let statusesRemoved = []
|
|
424
|
-
if (!team.leaderPassive) return statusesRemoved
|
|
425
|
-
|
|
426
|
-
// Remove leader passive statuses from team
|
|
427
|
-
getAlive(team).forEach(x => {
|
|
428
|
-
// add effects for each status removed
|
|
429
|
-
x.statuses.forEach(status => {
|
|
430
|
-
if (status === PASSIVES[team.leaderPassive - 1]) {
|
|
431
|
-
statusesRemoved.push({
|
|
432
|
-
target: x.id,
|
|
433
|
-
status: status
|
|
434
|
-
})
|
|
435
|
-
}
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
x.statuses = x.statuses.filter(x => x !== PASSIVES[team.leaderPassive - 1])
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
team.leaderPassive = null
|
|
442
|
-
|
|
443
|
-
return statusesRemoved
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
const getExpiredStatuses = (team1, team2) => {
|
|
447
|
-
// If leader is dead, remove leader passive
|
|
448
|
-
let statusesExpired = []
|
|
449
|
-
if (team1.leaderPassive && !getAlive(team1).find(x => x.id === team1.leader)) {
|
|
450
|
-
// Remove leader passive statuses
|
|
451
|
-
statusesExpired = removeLeaderPassivesFromTeam(team1)
|
|
452
|
-
}
|
|
453
|
-
if (team2.leaderPassive && !getAlive(team2).find(x => x.id === team2.leader)) {
|
|
454
|
-
// Remove leader passive statuses
|
|
455
|
-
statusesExpired = removeLeaderPassivesFromTeam(team2)
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
return statusesExpired
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Add a status to a gotchi
|
|
463
|
-
* @param {Object} gotchi An in-game gotchi object
|
|
464
|
-
* @param {String} status The status to add
|
|
465
|
-
* @returns {Boolean} success A boolean to determine if the status was added
|
|
466
|
-
**/
|
|
467
|
-
const addStatusToGotchi = (gotchi, status) => {
|
|
468
|
-
// Check that gotchi doesn't already have max number of statuses
|
|
469
|
-
if (gotchi.statuses.filter(item => item === status).length >= MULTS.MAX_STATUSES) return false
|
|
470
|
-
|
|
471
|
-
gotchi.statuses.push(status)
|
|
472
|
-
|
|
473
|
-
return true
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const scrambleGotchiIds = (allAliveGotchis, team1, team2) => {
|
|
477
|
-
// check there's no duplicate gotchis
|
|
478
|
-
const gotchiIds = allAliveGotchis.map(x => x.id)
|
|
479
|
-
|
|
480
|
-
if (gotchiIds.length !== new Set(gotchiIds).size) {
|
|
481
|
-
// scramble gotchi ids
|
|
482
|
-
allAliveGotchis.forEach(x => {
|
|
483
|
-
const newId = Math.floor(Math.random() * 10000000)
|
|
484
|
-
|
|
485
|
-
// find gotchi in team1 or team2
|
|
486
|
-
const position = getFormationPosition(team1, team2, x.id)
|
|
487
|
-
|
|
488
|
-
// change gotchi id
|
|
489
|
-
if (position) {
|
|
490
|
-
if (position.team === 1) {
|
|
491
|
-
if (x.id === team1.leader) team1.leader = newId
|
|
492
|
-
team1.formation[position.row][position.position].id = newId
|
|
493
|
-
} else {
|
|
494
|
-
if (x.id === team2.leader) team2.leader = newId
|
|
495
|
-
team2.formation[position.row][position.position].id = newId
|
|
496
|
-
}
|
|
497
|
-
} else {
|
|
498
|
-
throw new Error('Gotchi not found in team1 or team2')
|
|
499
|
-
}
|
|
500
|
-
})
|
|
501
|
-
|
|
502
|
-
// check again
|
|
503
|
-
const newGotchiIds = allAliveGotchis.map(x => x.id)
|
|
504
|
-
if (newGotchiIds.length !== new Set(newGotchiIds).size) {
|
|
505
|
-
// Scramble again
|
|
506
|
-
scrambleGotchiIds(allAliveGotchis, team1, team2)
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/**
|
|
512
|
-
* Prepare teams for battle
|
|
513
|
-
* @param {Array} allAliveGotchis An array of all alive gotchis
|
|
514
|
-
* @param {Object} team1 An in-game team object
|
|
515
|
-
* @param {Object} team2 An in-game team object
|
|
516
|
-
**/
|
|
517
|
-
const prepareTeams = (allAliveGotchis, team1, team2) => {
|
|
518
|
-
// check there's no duplicate gotchis
|
|
519
|
-
scrambleGotchiIds(allAliveGotchis, team1, team2);
|
|
520
|
-
|
|
521
|
-
allAliveGotchis.forEach(x => {
|
|
522
|
-
// Add statuses property to all gotchis
|
|
523
|
-
x.statuses = []
|
|
524
|
-
|
|
525
|
-
// Calculate initial action delay for all gotchis
|
|
526
|
-
x.actionDelay = calculateActionDelay(x)
|
|
527
|
-
|
|
528
|
-
// Calculate attack type
|
|
529
|
-
x.attack = x.magic > x.physical ? 'magic' : 'physical'
|
|
530
|
-
|
|
531
|
-
// Add original stats to all gotchis
|
|
532
|
-
// Do a deep copy of the gotchi object to avoid modifying the original object
|
|
533
|
-
x.originalStats = JSON.parse(JSON.stringify(x))
|
|
534
|
-
|
|
535
|
-
// Add environmentEffects to all gotchis
|
|
536
|
-
x.environmentEffects = []
|
|
537
|
-
})
|
|
538
|
-
|
|
539
|
-
// Add leader passive to team
|
|
540
|
-
addLeaderToTeam(team1)
|
|
541
|
-
addLeaderToTeam(team2);
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
/**
|
|
545
|
-
* Get log gotchi object for battle logs
|
|
546
|
-
* @param {Array} allAliveGotchis An array of all alive gotchis
|
|
547
|
-
* @returns {Array} logGotchis An array of gotchi objects for logs
|
|
548
|
-
*/
|
|
549
|
-
const getLogGotchis = (allAliveGotchis) => {
|
|
550
|
-
const logGotchis = JSON.parse(JSON.stringify(allAliveGotchis))
|
|
551
|
-
|
|
552
|
-
logGotchis.forEach(x => {
|
|
553
|
-
// Change gotchi.special.class to gotchi.special.gotchiClass to avoid conflicts with class keyword
|
|
554
|
-
x.special.gotchiClass = x.special.class
|
|
555
|
-
|
|
556
|
-
// Remove unnecessary properties to reduce log size
|
|
557
|
-
delete x.special.class
|
|
558
|
-
delete x.snapshotBlock
|
|
559
|
-
delete x.onchainId
|
|
560
|
-
delete x.brs
|
|
561
|
-
delete x.nrg
|
|
562
|
-
delete x.agg
|
|
563
|
-
delete x.spk
|
|
564
|
-
delete x.brn
|
|
565
|
-
delete x.eyc
|
|
566
|
-
delete x.eys
|
|
567
|
-
delete x.kinship
|
|
568
|
-
delete x.xp
|
|
569
|
-
delete x.actionDelay
|
|
570
|
-
delete x.attack
|
|
571
|
-
delete x.originalStats
|
|
572
|
-
delete x.environmentEffects
|
|
573
|
-
})
|
|
574
|
-
|
|
575
|
-
return logGotchis
|
|
576
|
-
}
|
|
15
|
+
const {
|
|
16
|
+
getAlive,
|
|
17
|
+
getNextToAct,
|
|
18
|
+
getTarget,
|
|
19
|
+
getDamage,
|
|
20
|
+
getModifiedStats,
|
|
21
|
+
getNewActionDelay,
|
|
22
|
+
simplifyTeam,
|
|
23
|
+
getExpiredStatuses,
|
|
24
|
+
addStatusToGotchi,
|
|
25
|
+
prepareTeams,
|
|
26
|
+
getLogGotchis
|
|
27
|
+
} = require('./helpers')
|
|
577
28
|
|
|
578
29
|
/**
|
|
579
30
|
* Run a battle between two teams
|
|
@@ -855,28 +306,6 @@ const handleStatusEffects = (attackingGotchi, attackingTeam, defendingTeam, rng)
|
|
|
855
306
|
|
|
856
307
|
const modifiedAttackingGotchi = getModifiedStats(attackingGotchi)
|
|
857
308
|
|
|
858
|
-
// Check for cleansing_aura
|
|
859
|
-
// if (attackingGotchi.statuses.includes('cleansing_aura')) {
|
|
860
|
-
// // Remove all debuffs from all allies
|
|
861
|
-
// const aliveAllies = getAlive(attackingTeam)
|
|
862
|
-
// aliveAllies.forEach((ally) => {
|
|
863
|
-
// ally.statuses.forEach((status) => {
|
|
864
|
-
// if (DEBUFFS.includes(status)) {
|
|
865
|
-
// passiveEffects.push({
|
|
866
|
-
// source: attackingGotchi.id,
|
|
867
|
-
// target: ally.id,
|
|
868
|
-
// status,
|
|
869
|
-
// damage: 0,
|
|
870
|
-
// remove: true
|
|
871
|
-
// })
|
|
872
|
-
// }
|
|
873
|
-
// })
|
|
874
|
-
|
|
875
|
-
// // Remove status effects
|
|
876
|
-
// ally.statuses = ally.statuses.filter((status) => !DEBUFFS.includes(status))
|
|
877
|
-
// })
|
|
878
|
-
// }
|
|
879
|
-
|
|
880
309
|
// Check for global status effects
|
|
881
310
|
const allAliveGotchis = [...getAlive(attackingTeam), ...getAlive(defendingTeam)]
|
|
882
311
|
|
|
@@ -977,24 +406,6 @@ const handleStatusEffects = (attackingGotchi, attackingTeam, defendingTeam, rng)
|
|
|
977
406
|
|
|
978
407
|
break
|
|
979
408
|
}
|
|
980
|
-
|
|
981
|
-
// Stun
|
|
982
|
-
// if (status === 'stun') {
|
|
983
|
-
// // Skip turn
|
|
984
|
-
// statusEffects.push({
|
|
985
|
-
// target: attackingGotchi.id,
|
|
986
|
-
// status,
|
|
987
|
-
// damage: 0,
|
|
988
|
-
// remove: true
|
|
989
|
-
// })
|
|
990
|
-
|
|
991
|
-
// skipTurn = 'STUN'
|
|
992
|
-
|
|
993
|
-
// // Remove first instance of stun
|
|
994
|
-
// attackingGotchi.statuses.splice(i, 1)
|
|
995
|
-
|
|
996
|
-
// break
|
|
997
|
-
// }
|
|
998
409
|
}
|
|
999
410
|
|
|
1000
411
|
return {
|
|
@@ -1036,8 +447,6 @@ const executeTurn = (team1, team2, rng) => {
|
|
|
1036
447
|
let specialDone = false
|
|
1037
448
|
// Check if special attack is ready
|
|
1038
449
|
if (attackingGotchi.special.cooldown === 0) {
|
|
1039
|
-
// TODO: Check if special attack should be used
|
|
1040
|
-
|
|
1041
450
|
// Execute special attack
|
|
1042
451
|
const specialResults = specialAttack(attackingGotchi, attackingTeam, defendingTeam, rng)
|
|
1043
452
|
|
|
@@ -1383,7 +792,5 @@ const specialAttack = (attackingGotchi, attackingTeam, defendingTeam, rng) => {
|
|
|
1383
792
|
}
|
|
1384
793
|
|
|
1385
794
|
module.exports = {
|
|
1386
|
-
getFormationPosition,
|
|
1387
|
-
getModifiedStats,
|
|
1388
795
|
gameLoop
|
|
1389
796
|
}
|
package/index.js
CHANGED
|
@@ -6,7 +6,7 @@ const teamSchema = require('./schemas/team')
|
|
|
6
6
|
const { webappTeamToInGameTeam, inGameTeamToWebappTeam } = require('./utils/transforms')
|
|
7
7
|
|
|
8
8
|
module.exports = {
|
|
9
|
-
|
|
9
|
+
battle: currentVersion.gameLoop,
|
|
10
10
|
teamSchema,
|
|
11
11
|
webappTeamToInGameTeam,
|
|
12
12
|
inGameTeamToWebappTeam
|
package/package.json
CHANGED
package/schemas/team.json
CHANGED
|
@@ -171,6 +171,60 @@
|
|
|
171
171
|
"required": ["id", "name", "cooldown", "leaderPassive"],
|
|
172
172
|
"additionalProperties": false
|
|
173
173
|
},
|
|
174
|
+
"itemId": {
|
|
175
|
+
"type": ["integer", "null"]
|
|
176
|
+
},
|
|
177
|
+
"item": {
|
|
178
|
+
"type": ["object", "null"],
|
|
179
|
+
"properties": {
|
|
180
|
+
"id": {
|
|
181
|
+
"type": "integer"
|
|
182
|
+
},
|
|
183
|
+
"name": {
|
|
184
|
+
"type": "string"
|
|
185
|
+
},
|
|
186
|
+
"description": {
|
|
187
|
+
"type": "string"
|
|
188
|
+
},
|
|
189
|
+
"image": {
|
|
190
|
+
"type": "string",
|
|
191
|
+
"format": "uri"
|
|
192
|
+
},
|
|
193
|
+
"rarity": {
|
|
194
|
+
"type": "string",
|
|
195
|
+
"enum": ["common", "uncommon", "rare", "legendary", "mythical", "godlike"]
|
|
196
|
+
},
|
|
197
|
+
"cost": {
|
|
198
|
+
"type": "number"
|
|
199
|
+
},
|
|
200
|
+
"stat": {
|
|
201
|
+
"type": "string",
|
|
202
|
+
"enum": ["speed", "health", "crit", "armor", "evade", "resist", "magic", "physical", "accuracy"]
|
|
203
|
+
},
|
|
204
|
+
"statValue": {
|
|
205
|
+
"type": "integer"
|
|
206
|
+
},
|
|
207
|
+
"createdAt": {
|
|
208
|
+
"type": "string"
|
|
209
|
+
},
|
|
210
|
+
"updatedAt": {
|
|
211
|
+
"type": "string"
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
"required": ["id", "name", "description", "image", "rarity", "cost", "stat", "statValue"],
|
|
215
|
+
"example": {
|
|
216
|
+
"id": 1,
|
|
217
|
+
"name": "Speed +",
|
|
218
|
+
"description": "Increase speed by 1",
|
|
219
|
+
"image": "https://gotchibattler.com/apple-touch-icon.png",
|
|
220
|
+
"rarity": "common",
|
|
221
|
+
"cost": 0.2,
|
|
222
|
+
"stat": "speed",
|
|
223
|
+
"statValue": 1,
|
|
224
|
+
"createdAt": "2022-02-22T22:22:22Z",
|
|
225
|
+
"updatedAt": "2022-02-22T22:22:22Z"
|
|
226
|
+
}
|
|
227
|
+
},
|
|
174
228
|
"statuses": {
|
|
175
229
|
"type": "array",
|
|
176
230
|
"items": {
|
package/scripts/data/team1.json
CHANGED
|
@@ -36,6 +36,19 @@
|
|
|
36
36
|
"name": "Meditate",
|
|
37
37
|
"cooldown": 0,
|
|
38
38
|
"leaderPassive": "Cloud of Zen"
|
|
39
|
+
},
|
|
40
|
+
"itemId": 1,
|
|
41
|
+
"item": {
|
|
42
|
+
"id": 1,
|
|
43
|
+
"name": "Speed +",
|
|
44
|
+
"description": "Increase speed by 1",
|
|
45
|
+
"image": "https://gotchibattler.com/apple-touch-icon.png",
|
|
46
|
+
"rarity": "common",
|
|
47
|
+
"cost": 0.2,
|
|
48
|
+
"stat": "speed",
|
|
49
|
+
"statValue": 1,
|
|
50
|
+
"createdAt": "2022-02-22T22:22:22Z",
|
|
51
|
+
"updatedAt": "2022-02-22T22:22:22Z"
|
|
39
52
|
}
|
|
40
53
|
},
|
|
41
54
|
null,
|
package/scripts/runBattle.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
|
-
const battle = require('..')
|
|
3
|
+
const { battle } = require('..')
|
|
4
4
|
|
|
5
5
|
// Edit these json files to test different battles
|
|
6
6
|
// NOTE: Only the in-game stats (speed, health, crit etc..) are used in the game logic
|
|
@@ -13,4 +13,6 @@ const timestamp = new Date().getTime()
|
|
|
13
13
|
const resultsFilename = `results-${timestamp}.json`
|
|
14
14
|
fs.writeFileSync(path.join(__dirname, 'output', resultsFilename), JSON.stringify(results, null, '\t'))
|
|
15
15
|
|
|
16
|
-
console.log(`Results written to ${path.join(__dirname, 'output', resultsFilename)}`)
|
|
16
|
+
console.log(`Results written to ${path.join(__dirname, 'output', resultsFilename)}`)
|
|
17
|
+
|
|
18
|
+
// node scripts/runBattle.js
|