gotchi-battler-game-logic 2.0.8 → 3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gotchi-battler-game-logic",
3
- "version": "2.0.8",
3
+ "version": "3.0.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
package/schemas/team.json CHANGED
@@ -108,13 +108,13 @@
108
108
  "type": "integer"
109
109
  },
110
110
  "crit": {
111
- "type": "integer"
111
+ "type": "number"
112
112
  },
113
113
  "armor": {
114
114
  "type": "integer"
115
115
  },
116
116
  "evade": {
117
- "type": "integer"
117
+ "type": "number"
118
118
  },
119
119
  "resist": {
120
120
  "type": "integer"
@@ -111,7 +111,7 @@ const main = async () => {
111
111
  module.exports = main
112
112
 
113
113
  if (require.main === module) {
114
- // node scripts/balancing/createCSV.js avg-sims-dfhn2
114
+ // node scripts/balancing/createCSV.js avg-sims-8jzd7
115
115
 
116
116
  main()
117
117
  .then(() => {
@@ -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()
@@ -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-2')
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()
@@ -259,9 +259,9 @@ module.exports = runSims
259
259
  // 2nd argument is the sims version
260
260
  // 3rd argument is the game logic version
261
261
  // 4th argument is the number of sims per matchup
262
- // node scripts/balancing/sims.js 0 v1.7.2 v1.7 3 true
262
+ // node scripts/balancing/sims.js 0 v1.7.3 v1.7 3 true
263
263
  if (require.main === module) {
264
- const simsVersion = process.env.SIMS_VERSION || process.argv[3] || 'v1.7.2'
264
+ const simsVersion = process.env.SIMS_VERSION || process.argv[3] || 'v1.7.3'
265
265
  const gameLogicVersion = process.env.GAME_LOGIC_VERSION || process.argv[4] || 'v1.7'
266
266
  const simsPerMatchup = parseInt(process.env.SIMS_PER_MATCHUP) || parseInt(process.argv[5]) || 3
267
267
  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
+ }