gotchi-battler-game-logic 2.0.8 → 4.0.0
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/.cursor/rules/cursor-rules.mdc +67 -0
- package/.cursor/rules/directory-structure.mdc +63 -0
- package/.cursor/rules/self-improvement.mdc +64 -0
- package/.cursor/rules/tech-stack.mdc +99 -0
- package/README.md +7 -3
- package/eslint.config.js +31 -0
- package/game-logic/index.js +2 -5
- package/game-logic/v1.4/constants.js +0 -23
- package/game-logic/v1.4/index.js +64 -56
- package/game-logic/v1.5/constants.js +0 -23
- package/game-logic/v1.5/index.js +27 -21
- package/game-logic/v1.6/constants.js +0 -23
- package/game-logic/v1.6/index.js +27 -21
- package/game-logic/v1.7/constants.js +0 -23
- package/game-logic/v1.7/helpers.js +18 -3
- package/game-logic/v1.7/index.js +24 -18
- package/game-logic/v1.8/constants.js +112 -0
- package/game-logic/v1.8/helpers.js +628 -0
- package/game-logic/v1.8/index.js +832 -0
- package/game-logic/v2.0/constants.js +112 -0
- package/game-logic/v2.0/helpers.js +713 -0
- package/game-logic/v2.0/index.js +782 -0
- package/game-logic/v2.0/statuses.json +439 -0
- package/package.json +11 -4
- package/schemas/crystal.js +14 -0
- package/schemas/effect.js +25 -0
- package/schemas/gotchi.js +53 -0
- package/schemas/ingameteam.js +14 -0
- package/schemas/item.js +13 -0
- package/schemas/leaderskill.js +15 -0
- package/schemas/leaderskillstatus.js +12 -0
- package/schemas/special.js +22 -0
- package/schemas/team.js +24 -0
- package/schemas/team.json +254 -116
- package/scripts/balancing/createCSV.js +1 -1
- package/scripts/balancing/createTrainingGotchis.js +267 -0
- package/scripts/balancing/extractOnchainTraits.js +61 -0
- package/scripts/balancing/fixTrainingGotchis.js +41 -41
- package/scripts/balancing/processSims.js +6 -6
- package/scripts/balancing/sims.js +10 -17
- package/scripts/balancing/v1.7/mapGotchi.js +119 -0
- package/scripts/balancing/v1.7/setTeamPositions.js +2 -2
- package/scripts/balancing/v1.7/training_gotchis_traits.json +520 -0
- package/scripts/balancing/v1.7.1/mapGotchi.js +119 -0
- package/scripts/balancing/v1.7.1/setTeamPositions.js +2 -2
- package/scripts/balancing/v1.7.1/training_gotchis_traits.json +520 -0
- package/scripts/balancing/v1.7.2/mapGotchi.js +157 -0
- package/scripts/balancing/v1.7.2/setTeamPositions.js +2 -2
- package/scripts/balancing/v1.7.2/training_gotchis_traits.json +520 -0
- package/scripts/balancing/v1.7.3/class_combos.js +44 -0
- package/scripts/balancing/v1.7.3/mapGotchi.js +164 -0
- package/scripts/balancing/v1.7.3/setTeamPositions.js +122 -0
- package/scripts/balancing/v1.7.3/training_gotchis.json +22402 -0
- package/scripts/balancing/v1.7.3/training_gotchis_traits.json +37 -0
- package/scripts/balancing/v1.7.3/trait_combos.json +10 -0
- package/scripts/data/dungeon_mob_1.json +87 -0
- package/scripts/data/dungeon_mob_2.json +87 -0
- package/scripts/data/immaterialTeam1.json +374 -0
- package/scripts/data/immaterialTeam2.json +365 -0
- package/scripts/data/tournaments.json +5 -0
- package/scripts/generateAllSpecialsLogs.js +93 -0
- package/scripts/generateSpecialLogs.js +94 -0
- package/scripts/runCampaignBattles.js +41 -0
- package/scripts/runLocalBattle.js +6 -3
- package/scripts/runLocalDungeon.js +52 -0
- package/scripts/runPvPBattle.js +16 -0
- package/scripts/runRealBattle.js +8 -8
- package/scripts/simRealBattle.js +8 -8
- package/scripts/validateBattle.js +23 -16
- package/scripts/validateTournament.js +9 -9
- package/tests/getModifiedStats.test.js +78 -0
- package/utils/errors.js +13 -13
- package/utils/transforms.js +2 -8
- package/scripts/output/.gitkeep +0 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
|
|
3
|
+
const specials = [
|
|
4
|
+
{
|
|
5
|
+
'id': 1,
|
|
6
|
+
'class': 'Ninja',
|
|
7
|
+
'name': 'Spectral strike',
|
|
8
|
+
'cooldown': 0,
|
|
9
|
+
'leaderPassive': 'Sharpen blades'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
'id': 2,
|
|
13
|
+
'class': 'Enlightened',
|
|
14
|
+
'name': 'Meditate',
|
|
15
|
+
'cooldown': 0,
|
|
16
|
+
'leaderPassive': 'Cloud of Zen'
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
'id': 3,
|
|
20
|
+
'class': 'Cleaver',
|
|
21
|
+
'name': 'Cleave',
|
|
22
|
+
'cooldown': 2,
|
|
23
|
+
'leaderPassive': 'Frenzy'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
'id': 4,
|
|
27
|
+
'class': 'Tank',
|
|
28
|
+
'name': 'Taunt',
|
|
29
|
+
'cooldown': 0,
|
|
30
|
+
'leaderPassive': 'Fortify'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
'id': 5,
|
|
34
|
+
'class': 'Cursed',
|
|
35
|
+
'name': 'Curse',
|
|
36
|
+
'cooldown': 0,
|
|
37
|
+
'leaderPassive': 'Spread the fear'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
'id': 6,
|
|
41
|
+
'class': 'Healer',
|
|
42
|
+
'name': 'Blessing',
|
|
43
|
+
'cooldown': 0,
|
|
44
|
+
'leaderPassive': 'Cleansing Aura'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
'id': 7,
|
|
48
|
+
'class': 'Mage',
|
|
49
|
+
'name': 'Thunder',
|
|
50
|
+
'cooldown': 2,
|
|
51
|
+
'leaderPassive': 'Channel the coven'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
'id': 8,
|
|
55
|
+
'class': 'Troll',
|
|
56
|
+
'name': 'Devestating Smash',
|
|
57
|
+
'cooldown': 2,
|
|
58
|
+
'leaderPassive': 'Clan momentum'
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
const powerLevels = ['Godlike', 'Mythical', 'Legendary', 'Rare', 'Uncommon', 'Common', 'Garbage']
|
|
64
|
+
const traitCombos = [
|
|
65
|
+
'++++', '+++-', '++-+', '++--', '+-++', '+-+-', '+--+', '+---',
|
|
66
|
+
'-+++', '-++-', '-+-+', '-+--', '--++', '--+-', '---+', '----'
|
|
67
|
+
]
|
|
68
|
+
const classes = ['Ninja','Enlightened','Cleaver','Tank','Cursed','Healer', 'Mage', 'Troll']
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
// Create training gotchi objects like below for all combinations of powerLevels, traitCombos and classes
|
|
72
|
+
// {
|
|
73
|
+
// "id": 1,
|
|
74
|
+
// "snapshotBlock": 0,
|
|
75
|
+
// "onchainId": 1000001,
|
|
76
|
+
// "name": "Godlike ++++ Ninja",
|
|
77
|
+
// "brs": 783,
|
|
78
|
+
// "nrg": 110,
|
|
79
|
+
// "agg": 98,
|
|
80
|
+
// "spk": 101,
|
|
81
|
+
// "brn": 107,
|
|
82
|
+
// "eyc": 94,
|
|
83
|
+
// "eys": 94,
|
|
84
|
+
// "kinship": 0,
|
|
85
|
+
// "xp": 0,
|
|
86
|
+
// "speed": 161,
|
|
87
|
+
// "health": 587,
|
|
88
|
+
// "crit": 25,
|
|
89
|
+
// "armor": 0,
|
|
90
|
+
// "evade": 16,
|
|
91
|
+
// "resist": 0,
|
|
92
|
+
// "magic": 486,
|
|
93
|
+
// "physical": 196,
|
|
94
|
+
// "accuracy": 95,
|
|
95
|
+
// "attack": "magic",
|
|
96
|
+
// "actionDelay": 0.617,
|
|
97
|
+
// "specialId": 1,
|
|
98
|
+
// "svgFront": "https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/ninja_godlike_front.png",
|
|
99
|
+
// "svgBack": "https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/ninja_godlike_back.png",
|
|
100
|
+
// "svgLeft": "https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/ninja_godlike_left.png",
|
|
101
|
+
// "svgRight": "https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/ninja_godlike_right.png",
|
|
102
|
+
// "createdAt": "2023-09-09 12:10:01",
|
|
103
|
+
// "updatedAt": "2024-06-05 16:34:00",
|
|
104
|
+
// "special": {
|
|
105
|
+
// "id": 1,
|
|
106
|
+
// "class": "Ninja",
|
|
107
|
+
// "name": "Spectral strike",
|
|
108
|
+
// "cooldown": 0,
|
|
109
|
+
// "leaderPassive": "Sharpen blades"
|
|
110
|
+
// }
|
|
111
|
+
// }
|
|
112
|
+
|
|
113
|
+
const main = (version) => {
|
|
114
|
+
const trainingGotchisTraits = require(`./v${version}/training_gotchis_traits.json`)
|
|
115
|
+
const { mapGotchi } = require(`./v${version}/mapGotchi.js`)
|
|
116
|
+
|
|
117
|
+
const trainingGotchis = []
|
|
118
|
+
|
|
119
|
+
let id = 1
|
|
120
|
+
traitCombos.forEach((t) => {
|
|
121
|
+
powerLevels.forEach((p) => {
|
|
122
|
+
for (let index = 0; index < t.length; index ++) {
|
|
123
|
+
const c = t[index]
|
|
124
|
+
|
|
125
|
+
const gotchi = {
|
|
126
|
+
id,
|
|
127
|
+
snapshotBlock: 0,
|
|
128
|
+
onchainId: 1000000 + id,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Based on the position of the trait in the traitCombos array, we can determine the class of the gotchi
|
|
132
|
+
// e.g. if the first trait is positive then add ninja, if it's negative then add enlightened
|
|
133
|
+
const className = classes[index * 2 + (c === '+' ? 0 : 1)]
|
|
134
|
+
|
|
135
|
+
// e.g Godlike ++++ Mage
|
|
136
|
+
gotchi.name = `${p} ${t} ${className}`
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* This is for versions <= 1.7.2
|
|
140
|
+
*/
|
|
141
|
+
// const trainingGotchiTraits = trainingGotchisTraits[p.toLowerCase()][className.toLowerCase()]
|
|
142
|
+
|
|
143
|
+
// if (!trainingGotchiTraits) {
|
|
144
|
+
// throw new Error(`No traits found for ${p} ${className}`)
|
|
145
|
+
// }
|
|
146
|
+
|
|
147
|
+
// gotchi.brs = trainingGotchiTraits.brs
|
|
148
|
+
// gotchi.nrg = trainingGotchiTraits.nrg[t[0] === '-' ? 0 : 1]
|
|
149
|
+
// gotchi.agg = trainingGotchiTraits.agg[t[1] === '-' ? 0 : 1]
|
|
150
|
+
// gotchi.spk = trainingGotchiTraits.spk[t[2] === '-' ? 0 : 1]
|
|
151
|
+
// gotchi.brn = trainingGotchiTraits.brn[t[3] === '-' ? 0 : 1]
|
|
152
|
+
// gotchi.eys = trainingGotchiTraits.eys[1] // These are always in index 1
|
|
153
|
+
// gotchi.eyc = trainingGotchiTraits.eyc[1] // These are always in index 1
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* This is for versions >= 1.7.3
|
|
157
|
+
*/
|
|
158
|
+
const trainingGotchiTraits = trainingGotchisTraits[p.toLowerCase()]
|
|
159
|
+
|
|
160
|
+
if (!trainingGotchiTraits) {
|
|
161
|
+
throw new Error(`No traits found for ${p}`)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
gotchi.brs = trainingGotchiTraits.brs
|
|
165
|
+
gotchi.nrg = trainingGotchiTraits.traits[t[0] === '-' ? 0 : 1]
|
|
166
|
+
gotchi.agg = trainingGotchiTraits.traits[t[1] === '-' ? 0 : 1]
|
|
167
|
+
gotchi.spk = trainingGotchiTraits.traits[t[2] === '-' ? 0 : 1]
|
|
168
|
+
gotchi.brn = trainingGotchiTraits.traits[t[3] === '-' ? 0 : 1]
|
|
169
|
+
gotchi.eys = trainingGotchiTraits.eyes
|
|
170
|
+
gotchi.eyc = trainingGotchiTraits.eyes
|
|
171
|
+
|
|
172
|
+
gotchi.kinship = 0
|
|
173
|
+
gotchi.xp = 0
|
|
174
|
+
|
|
175
|
+
// Map the in game stats to the gotchi
|
|
176
|
+
mapGotchi(gotchi)
|
|
177
|
+
gotchi.attack = t[3] === '-' ? 'physical' : 'magic'
|
|
178
|
+
gotchi.actionDelay = Math.round(((100 / gotchi.speed) + Number.EPSILON) * 1000) / 1000
|
|
179
|
+
|
|
180
|
+
gotchi.specialId = classes.indexOf(className) + 1
|
|
181
|
+
gotchi.svgFront = `https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/${className.toLowerCase()}_${p.toLowerCase()}_front.png`
|
|
182
|
+
gotchi.svgBack = `https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/${className.toLowerCase()}_${p.toLowerCase()}_back.png`
|
|
183
|
+
gotchi.svgLeft = `https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/${className.toLowerCase()}_${p.toLowerCase()}_left.png`
|
|
184
|
+
gotchi.svgRight = `https://storage.googleapis.com/gotchi-battler-qa_gotchis/training/${className.toLowerCase()}_${p.toLowerCase()}_right.png`
|
|
185
|
+
gotchi.createdAt = '2023-09-09 12:10:01'
|
|
186
|
+
gotchi.updatedAt = '2024-06-05 16:34:00'
|
|
187
|
+
gotchi.special = specials.find(s => s.id === gotchi.specialId)
|
|
188
|
+
|
|
189
|
+
trainingGotchis.push(gotchi)
|
|
190
|
+
|
|
191
|
+
id++
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const avgGotchis = createAvgGotchs(trainingGotchis)
|
|
197
|
+
|
|
198
|
+
trainingGotchis.push(...avgGotchis)
|
|
199
|
+
|
|
200
|
+
fs.writeFileSync('./training_gotchis.json', JSON.stringify(trainingGotchis, null, 2))
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const createAvgGotchs = (trainingGotchis) => {
|
|
204
|
+
const statsToOverwrite = ['speed', 'health', 'crit', 'armor', 'evade', 'resist', 'accuracy']
|
|
205
|
+
|
|
206
|
+
const avgGotchis = [];
|
|
207
|
+
|
|
208
|
+
// Add magic gotchis
|
|
209
|
+
['magic', 'physical'].forEach((brainValue) => {
|
|
210
|
+
powerLevels.forEach((powerLevel) => {
|
|
211
|
+
classes.forEach((className) => {
|
|
212
|
+
// Find all gotchis with the same class and power level
|
|
213
|
+
// Names are in the format "Godlike ++++ Ninja"
|
|
214
|
+
const gotchis = trainingGotchis.filter(gotchi => gotchi.name.includes(powerLevel) && gotchi.name.includes(className))
|
|
215
|
+
|
|
216
|
+
// Copy over one of the gotchis
|
|
217
|
+
const avgGotchi = JSON.parse(JSON.stringify(gotchis[0]))
|
|
218
|
+
|
|
219
|
+
// Add an id
|
|
220
|
+
avgGotchi.id = trainingGotchis.length + avgGotchis.length + 1
|
|
221
|
+
|
|
222
|
+
// Overwrite the name
|
|
223
|
+
avgGotchi.name = `${powerLevel} avg-${brainValue} ${className}`
|
|
224
|
+
|
|
225
|
+
// Overwrite the stats
|
|
226
|
+
statsToOverwrite.forEach((stat) => {
|
|
227
|
+
const total = gotchis.reduce((acc, gotchi) => acc + gotchi[stat], 0)
|
|
228
|
+
|
|
229
|
+
if (['speed', 'health', 'armor', 'resist', 'magic', 'physical', 'accuracy'].includes(stat)) {
|
|
230
|
+
avgGotchi[stat] = Math.round(total / gotchis.length)
|
|
231
|
+
} else {
|
|
232
|
+
// Round to 2 decimal places
|
|
233
|
+
avgGotchi[stat] = Math.round((total / gotchis.length) * 100) / 100
|
|
234
|
+
}
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
// Get the highest/lowest magic value
|
|
238
|
+
const magicValues = gotchis.map(gotchi => gotchi.magic)
|
|
239
|
+
const highestMagic = Math.max(...magicValues)
|
|
240
|
+
const lowestMagic = Math.min(...magicValues)
|
|
241
|
+
|
|
242
|
+
// Get the highest/lowest physical value
|
|
243
|
+
const physicalValues = gotchis.map(gotchi => gotchi.physical)
|
|
244
|
+
const highestPhysical = Math.max(...physicalValues)
|
|
245
|
+
const lowestPhysical = Math.min(...physicalValues)
|
|
246
|
+
|
|
247
|
+
// If this is a magic gotchi then set the magic value to the highest
|
|
248
|
+
if (brainValue === 'magic') {
|
|
249
|
+
avgGotchi.magic = highestMagic
|
|
250
|
+
avgGotchi.physical = lowestPhysical
|
|
251
|
+
avgGotchi.attack = 'magic'
|
|
252
|
+
} else {
|
|
253
|
+
avgGotchi.magic = lowestMagic
|
|
254
|
+
avgGotchi.physical = highestPhysical
|
|
255
|
+
avgGotchi.attack = 'physical'
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
avgGotchis.push(avgGotchi)
|
|
259
|
+
})
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
return avgGotchis
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// node scripts/balancing/createTrainingGotchis.js
|
|
267
|
+
main('1.7.3')
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
const trainingGotchis = require('./v1.7/training_gotchis.json')
|
|
3
|
+
|
|
4
|
+
const onchainTraitNames = [
|
|
5
|
+
'nrg',
|
|
6
|
+
'agg',
|
|
7
|
+
'spk',
|
|
8
|
+
'brn',
|
|
9
|
+
'eys',
|
|
10
|
+
'eyc'
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
const main = () => {
|
|
14
|
+
const onchainTraits = {}
|
|
15
|
+
|
|
16
|
+
// Loop through all training gotchis ids 1-448 and extract the onchain traits
|
|
17
|
+
for (let i = 1; i < 449; i++) {
|
|
18
|
+
const gotchi = trainingGotchis[i]
|
|
19
|
+
|
|
20
|
+
const powerLevel = gotchi.name.split(' ')[0].toLowerCase()
|
|
21
|
+
const gotchiClass = gotchi.name.split(' ')[2].toLowerCase()
|
|
22
|
+
|
|
23
|
+
if (!onchainTraits[powerLevel]) {
|
|
24
|
+
onchainTraits[powerLevel] = {}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!onchainTraits[powerLevel][gotchiClass]) {
|
|
28
|
+
onchainTraits[powerLevel][gotchiClass] = {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!onchainTraits[powerLevel][gotchiClass].brs) {
|
|
32
|
+
onchainTraits[powerLevel][gotchiClass].brs = gotchi.brs
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onchainTraitNames.forEach((trait) => {
|
|
36
|
+
if (!onchainTraits[powerLevel][gotchiClass][trait]) {
|
|
37
|
+
onchainTraits[powerLevel][gotchiClass][trait] = []
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (gotchi[trait] < 50) {
|
|
41
|
+
if (!onchainTraits[powerLevel][gotchiClass][trait][0]) {
|
|
42
|
+
onchainTraits[powerLevel][gotchiClass][trait][0] = gotchi[trait]
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
if (!onchainTraits[powerLevel][gotchiClass][trait][1]) {
|
|
46
|
+
onchainTraits[powerLevel][gotchiClass][trait][1] = gotchi[trait]
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
fs.writeFileSync('./training_gotchis_traits.json', JSON.stringify(onchainTraits, (key, value) => {
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
return JSON.stringify(value) // Convert array to a string
|
|
55
|
+
}
|
|
56
|
+
return value
|
|
57
|
+
}, 2).replace(/"\[(.*?)\]"/g, '[$1]'))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// node scripts/balancing/extractOnchainTraits.js
|
|
61
|
+
main()
|
|
@@ -4,60 +4,60 @@ const { mapGotchi } = require('../../utils/mapGotchi')
|
|
|
4
4
|
|
|
5
5
|
const specials = [
|
|
6
6
|
{
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
'id': 1,
|
|
8
|
+
'class': 'Ninja',
|
|
9
|
+
'name': 'Spectral strike',
|
|
10
|
+
'cooldown': 0,
|
|
11
|
+
'leaderPassive': 'Sharpen blades'
|
|
12
12
|
},
|
|
13
13
|
{
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
'id': 2,
|
|
15
|
+
'class': 'Enlightened',
|
|
16
|
+
'name': 'Meditate',
|
|
17
|
+
'cooldown': 0,
|
|
18
|
+
'leaderPassive': 'Cloud of Zen'
|
|
19
19
|
},
|
|
20
20
|
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
21
|
+
'id': 3,
|
|
22
|
+
'class': 'Cleaver',
|
|
23
|
+
'name': 'Cleave',
|
|
24
|
+
'cooldown': 2,
|
|
25
|
+
'leaderPassive': 'Frenzy'
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
'id': 4,
|
|
29
|
+
'class': 'Tank',
|
|
30
|
+
'name': 'Taunt',
|
|
31
|
+
'cooldown': 0,
|
|
32
|
+
'leaderPassive': 'Fortify'
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
'id': 5,
|
|
36
|
+
'class': 'Cursed',
|
|
37
|
+
'name': 'Curse',
|
|
38
|
+
'cooldown': 0,
|
|
39
|
+
'leaderPassive': 'Spread the fear'
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
'id': 6,
|
|
43
|
+
'class': 'Healer',
|
|
44
|
+
'name': 'Blessing',
|
|
45
|
+
'cooldown': 0,
|
|
46
|
+
'leaderPassive': 'Cleansing Aura'
|
|
47
47
|
},
|
|
48
48
|
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
'id': 7,
|
|
50
|
+
'class': 'Mage',
|
|
51
|
+
'name': 'Thunder',
|
|
52
|
+
'cooldown': 2,
|
|
53
|
+
'leaderPassive': 'Channel the coven'
|
|
54
54
|
},
|
|
55
55
|
{
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
'id': 8,
|
|
57
|
+
'class': 'Troll',
|
|
58
|
+
'name': 'Devestating Smash',
|
|
59
|
+
'cooldown': 2,
|
|
60
|
+
'leaderPassive': 'Clan momentum'
|
|
61
61
|
}
|
|
62
62
|
]
|
|
63
63
|
|
|
@@ -65,7 +65,7 @@ const classes = ['Ninja','Enlightened','Cleaver','Tank','Cursed','Healer', 'Mage
|
|
|
65
65
|
const powerLevels = ['Godlike', 'Mythical', 'Legendary', 'Rare', 'Uncommon', 'Common', 'Garbage']
|
|
66
66
|
|
|
67
67
|
const addAvgGotchs = () => {
|
|
68
|
-
const statsToOverwrite = [
|
|
68
|
+
const statsToOverwrite = ['speed', 'health', 'crit', 'armor', 'evade', 'resist', 'accuracy']
|
|
69
69
|
|
|
70
70
|
const avgGotchis = [];
|
|
71
71
|
|
|
@@ -41,16 +41,16 @@ const classes = ['Ninja','Enlightened','Cleaver','Tank','Cursed','Healer', 'Mage
|
|
|
41
41
|
|
|
42
42
|
const calculateKpis = (data) => {
|
|
43
43
|
// Extract wins values
|
|
44
|
-
const wins = data.map(item => item.wins)
|
|
44
|
+
const wins = data.map(item => item.wins)
|
|
45
45
|
|
|
46
46
|
// Calculate the mean of wins
|
|
47
|
-
const winsMean = wins.reduce((sum, value) => sum + value, 0) / wins.length
|
|
47
|
+
const winsMean = wins.reduce((sum, value) => sum + value, 0) / wins.length
|
|
48
48
|
|
|
49
49
|
// Calculate the wins per slot
|
|
50
50
|
const winsPerSlot = winsMean / 5
|
|
51
51
|
|
|
52
52
|
// Calculate the standard deviation of wins
|
|
53
|
-
const winsVariance = wins.reduce((sum, value) => sum + Math.pow(value - winsMean, 2), 0) / (wins.length - 1)
|
|
53
|
+
const winsVariance = wins.reduce((sum, value) => sum + Math.pow(value - winsMean, 2), 0) / (wins.length - 1)
|
|
54
54
|
const winsStdev = Math.sqrt(winsVariance)
|
|
55
55
|
|
|
56
56
|
// Calculate Interquartile Range (IQR)
|
|
@@ -91,8 +91,8 @@ const calculateKpis = (data) => {
|
|
|
91
91
|
// G is power level, avg is trait combo, 1 is class number, B is formation position
|
|
92
92
|
const leaderWins = data.filter(item => item.slot1.includes(`${i + 1}`)).map(item => item.wins)
|
|
93
93
|
|
|
94
|
-
const leaderWinsMean = leaderWins.reduce((sum, value) => sum + value, 0) / leaderWins.length
|
|
95
|
-
const leaderWinsVariance = leaderWins.reduce((sum, value) => sum + Math.pow(value - leaderWinsMean, 2), 0) / (leaderWins.length - 1)
|
|
94
|
+
const leaderWinsMean = leaderWins.reduce((sum, value) => sum + value, 0) / leaderWins.length
|
|
95
|
+
const leaderWinsVariance = leaderWins.reduce((sum, value) => sum + Math.pow(value - leaderWinsMean, 2), 0) / (leaderWins.length - 1)
|
|
96
96
|
const leaderWinsStdev = Math.sqrt(leaderWinsVariance)
|
|
97
97
|
|
|
98
98
|
// Calculate Interquartile Range (IQR)
|
|
@@ -196,7 +196,7 @@ const main = async (executionId, numOfTasks, gameLogicVersion) => {
|
|
|
196
196
|
})
|
|
197
197
|
|
|
198
198
|
if(process.env.CLOUD_RUN_JOB) {
|
|
199
|
-
const bucket = storage.bucket('gotchi-battler-sims-v1-7-
|
|
199
|
+
const bucket = storage.bucket('gotchi-battler-sims-v1-7-3')
|
|
200
200
|
|
|
201
201
|
// Get number of files in the bucket
|
|
202
202
|
const [files] = await bucket.getFiles()
|
|
@@ -125,7 +125,7 @@ const getGotchisSimNameFromTeam = (team) => {
|
|
|
125
125
|
|
|
126
126
|
[0,1,2,3,4].forEach(i => {
|
|
127
127
|
const gotchi = team.formation.back[i] || team.formation.front[i]
|
|
128
|
-
const position =
|
|
128
|
+
const position = team.formation.back[i] ? 'B' : 'F'
|
|
129
129
|
|
|
130
130
|
const nameParts = gotchi.name.split(' ')
|
|
131
131
|
// Return e.g. "R|++++|1_B"
|
|
@@ -139,7 +139,7 @@ const runSims = async (simsVersion, gameLogicVersion, simsPerMatchup) => {
|
|
|
139
139
|
const classCombos = require(`./${simsVersion}/class_combos.js`)
|
|
140
140
|
const classTraitCombos = require(`./${simsVersion}/trait_combos.json`)
|
|
141
141
|
const setTeamPositions = require(`./${simsVersion}/setTeamPositions`)
|
|
142
|
-
const gameLogic = require(
|
|
142
|
+
const gameLogic = require('../../game-logic')[gameLogicVersion].gameLoop
|
|
143
143
|
|
|
144
144
|
const attackingPowerLevels = ['Godlike']
|
|
145
145
|
const defendingPowerLevels = ['Godlike', 'Mythical', 'Legendary']
|
|
@@ -181,7 +181,7 @@ const runSims = async (simsVersion, gameLogicVersion, simsPerMatchup) => {
|
|
|
181
181
|
|
|
182
182
|
console.time(`Sims for ${attackingTeam.name}`)
|
|
183
183
|
|
|
184
|
-
defendingTeamIndexes.forEach((defendingTeamIndex
|
|
184
|
+
defendingTeamIndexes.forEach((defendingTeamIndex) => {
|
|
185
185
|
const defendingTeam = createTeamFromTeamIndex(defendingTeamIndex, trainingGotchis, setTeamPositions)
|
|
186
186
|
|
|
187
187
|
let matchupWins = 0
|
|
@@ -231,23 +231,16 @@ const runSims = async (simsVersion, gameLogicVersion, simsPerMatchup) => {
|
|
|
231
231
|
|
|
232
232
|
if (process.env.CLOUD_RUN_JOB && process.env.SIMS_BUCKET) {
|
|
233
233
|
// Save as JSON to GCS
|
|
234
|
-
|
|
235
|
-
await storage.bucket(process.env.SIMS_BUCKET).file(`${process.env.CLOUD_RUN_EXECUTION}_${attackingTeamIndex}.json`).save(JSON.stringify(results))
|
|
236
|
-
} catch (err) {
|
|
237
|
-
throw err
|
|
238
|
-
}
|
|
234
|
+
await storage.bucket(process.env.SIMS_BUCKET).file(`${process.env.CLOUD_RUN_EXECUTION}_${attackingTeamIndex}.json`).save(JSON.stringify(results))
|
|
239
235
|
|
|
240
236
|
} else {
|
|
241
|
-
|
|
237
|
+
console.log('Results', results)
|
|
242
238
|
|
|
243
239
|
// Test saving to GCS
|
|
244
|
-
|
|
240
|
+
const test = false
|
|
241
|
+
if (test) {
|
|
245
242
|
process.env.GOOGLE_APPLICATION_CREDENTIALS = path.join(__dirname, '../../keyfile.json')
|
|
246
|
-
|
|
247
|
-
await storage.bucket(process.env.SIMS_BUCKET).file(`avg-sims-znw68_${attackingTeamIndex}.json`).save(JSON.stringify(results))
|
|
248
|
-
} catch (err) {
|
|
249
|
-
throw err
|
|
250
|
-
}
|
|
243
|
+
await storage.bucket(process.env.SIMS_BUCKET).file(`avg-sims-znw68_${attackingTeamIndex}.json`).save(JSON.stringify(results))
|
|
251
244
|
}
|
|
252
245
|
}
|
|
253
246
|
}
|
|
@@ -259,9 +252,9 @@ module.exports = runSims
|
|
|
259
252
|
// 2nd argument is the sims version
|
|
260
253
|
// 3rd argument is the game logic version
|
|
261
254
|
// 4th argument is the number of sims per matchup
|
|
262
|
-
// node scripts/balancing/sims.js 0 v1.7.
|
|
255
|
+
// node scripts/balancing/sims.js 0 v1.7.3 v1.7 3 true
|
|
263
256
|
if (require.main === module) {
|
|
264
|
-
const simsVersion = process.env.SIMS_VERSION || process.argv[3] || 'v1.7.
|
|
257
|
+
const simsVersion = process.env.SIMS_VERSION || process.argv[3] || 'v1.7.3'
|
|
265
258
|
const gameLogicVersion = process.env.GAME_LOGIC_VERSION || process.argv[4] || 'v1.7'
|
|
266
259
|
const simsPerMatchup = parseInt(process.env.SIMS_PER_MATCHUP) || parseInt(process.argv[5]) || 3
|
|
267
260
|
const useAvg = process.env.USE_AVG || process.argv[6] === 'true' || false
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const mapTeam = (team) => {
|
|
2
|
+
for(const gotchi of team.formation.front) {
|
|
3
|
+
if (gotchi) mapGotchi(gotchi)
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
for(const gotchi of team.formation.back) {
|
|
7
|
+
if (gotchi) mapGotchi(gotchi)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// This is copied/hacked from the mapGotchi function in the backend
|
|
12
|
+
const mapGotchi = (gotchi) => {
|
|
13
|
+
const traitMaps = {
|
|
14
|
+
speed: {
|
|
15
|
+
baseFormula: 100,
|
|
16
|
+
multiplier: 1,
|
|
17
|
+
traitKey: 0,
|
|
18
|
+
isNegative: false
|
|
19
|
+
},
|
|
20
|
+
health: {
|
|
21
|
+
baseFormula: 'brs*0.75',
|
|
22
|
+
multiplier: 12,
|
|
23
|
+
traitKey: 0,
|
|
24
|
+
isNegative: true
|
|
25
|
+
},
|
|
26
|
+
crit: {
|
|
27
|
+
baseFormula: 0,
|
|
28
|
+
multiplier: 0.5,
|
|
29
|
+
traitKey: 1,
|
|
30
|
+
isNegative: false
|
|
31
|
+
},
|
|
32
|
+
armor: {
|
|
33
|
+
baseFormula: 0,
|
|
34
|
+
multiplier: 2,
|
|
35
|
+
traitKey: 1,
|
|
36
|
+
isNegative: true
|
|
37
|
+
},
|
|
38
|
+
evade: {
|
|
39
|
+
baseFormula: 0,
|
|
40
|
+
multiplier: 0.3,
|
|
41
|
+
traitKey: 2,
|
|
42
|
+
isNegative: false
|
|
43
|
+
},
|
|
44
|
+
resist: {
|
|
45
|
+
baseFormula: 0,
|
|
46
|
+
multiplier: 1,
|
|
47
|
+
traitKey: 2,
|
|
48
|
+
isNegative: true
|
|
49
|
+
},
|
|
50
|
+
magic: {
|
|
51
|
+
baseFormula: 'brs*0.25',
|
|
52
|
+
multiplier: 5,
|
|
53
|
+
traitKey: 3,
|
|
54
|
+
isNegative: false
|
|
55
|
+
},
|
|
56
|
+
physical: {
|
|
57
|
+
baseFormula: 'brs*0.25',
|
|
58
|
+
multiplier: 5,
|
|
59
|
+
traitKey: 3,
|
|
60
|
+
isNegative: true
|
|
61
|
+
},
|
|
62
|
+
accuracy: {
|
|
63
|
+
baseFormula: 50,
|
|
64
|
+
multiplier: 0.5,
|
|
65
|
+
traitKey: 45,
|
|
66
|
+
isNegative: false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const traitValue = (trait) => {
|
|
71
|
+
return trait < 50 ? 50 - trait : trait - 50 + 1
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const onchainVals = [
|
|
75
|
+
gotchi.nrg,
|
|
76
|
+
gotchi.agg,
|
|
77
|
+
gotchi.spk,
|
|
78
|
+
gotchi.brn,
|
|
79
|
+
gotchi.eyc,
|
|
80
|
+
gotchi.eys
|
|
81
|
+
]
|
|
82
|
+
// Convert trait value to in-game value
|
|
83
|
+
const traitValues = onchainVals.map(x => { return traitValue(x) })
|
|
84
|
+
|
|
85
|
+
// Map traits
|
|
86
|
+
for(const trait in traitMaps) {
|
|
87
|
+
const traitMap = traitMaps[trait]
|
|
88
|
+
const onchainVal = onchainVals[traitMap.traitKey]
|
|
89
|
+
|
|
90
|
+
let base = traitMap.baseFormula
|
|
91
|
+
|
|
92
|
+
// If baseFormula is a string and contains a * then it is a formula
|
|
93
|
+
if (typeof traitMap.baseFormula === 'string' && traitMap.baseFormula.includes('*')) {
|
|
94
|
+
const formula = traitMap.baseFormula.split('*')
|
|
95
|
+
|
|
96
|
+
if (!gotchi[formula[0]]) throw new Error('Trait not found: ', formula[0])
|
|
97
|
+
|
|
98
|
+
base = Math.round(Number(gotchi[formula[0]]) * Number(formula[1]))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
let newTrait
|
|
102
|
+
if (trait !== 'accuracy') {
|
|
103
|
+
if (traitMap.isNegative) {
|
|
104
|
+
newTrait = onchainVal < 50 ? Math.round(base + (traitValues[traitMap.traitKey] * traitMap.multiplier)) : base
|
|
105
|
+
} else {
|
|
106
|
+
newTrait = onchainVal < 50 ? base : Math.round(base + (traitValues[traitMap.traitKey] * traitMap.multiplier))
|
|
107
|
+
}
|
|
108
|
+
} else {
|
|
109
|
+
newTrait = base + ((traitValues[4] + traitValues[5]) * traitMap.multiplier)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (newTrait !== gotchi[trait]) gotchi[trait] = newTrait
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = {
|
|
117
|
+
mapGotchi,
|
|
118
|
+
mapTeam
|
|
119
|
+
}
|