gotchi-battler-game-logic 2.0.7 → 2.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.vscode/settings.json +4 -4
  2. package/Dockerfile +9 -9
  3. package/README.md +49 -49
  4. package/cloudbuild.yaml +27 -27
  5. package/constants/tournamentManagerAbi.json +208 -208
  6. package/game-logic/index.js +6 -6
  7. package/game-logic/v1.4/constants.js +114 -114
  8. package/game-logic/v1.4/index.js +1366 -1366
  9. package/game-logic/v1.6/constants.js +123 -123
  10. package/game-logic/v1.6/index.js +1406 -1406
  11. package/game-logic/v1.7/constants.js +142 -140
  12. package/game-logic/v1.7/helpers.js +595 -593
  13. package/game-logic/v1.7/index.js +802 -795
  14. package/index.js +12 -12
  15. package/package.json +26 -26
  16. package/schemas/team.json +349 -343
  17. package/scripts/balancing/createCSV.js +126 -126
  18. package/scripts/balancing/fixTrainingGotchis.js +155 -259
  19. package/scripts/balancing/processSims.js +229 -229
  20. package/scripts/balancing/sims.js +278 -278
  21. package/scripts/balancing/v1.7/class_combos.js +43 -43
  22. package/scripts/balancing/v1.7/setTeamPositions.js +105 -105
  23. package/scripts/balancing/v1.7/training_gotchis.json +20161 -20161
  24. package/scripts/balancing/v1.7/trait_combos.json +9 -9
  25. package/scripts/balancing/v1.7.1/class_combos.js +43 -43
  26. package/scripts/balancing/v1.7.1/setTeamPositions.js +122 -122
  27. package/scripts/balancing/v1.7.1/training_gotchis.json +22401 -22401
  28. package/scripts/balancing/v1.7.1/trait_combos.json +9 -9
  29. package/scripts/balancing/v1.7.2/class_combos.js +44 -0
  30. package/scripts/balancing/v1.7.2/setTeamPositions.js +122 -0
  31. package/scripts/balancing/v1.7.2/training_gotchis.json +22402 -0
  32. package/scripts/balancing/v1.7.2/trait_combos.json +10 -0
  33. package/scripts/data/team1.json +213 -213
  34. package/scripts/data/team2.json +200 -200
  35. package/scripts/data/tournaments.json +66 -66
  36. package/scripts/{runBattle.js → runLocalBattle.js} +18 -18
  37. package/scripts/runRealBattle.js +52 -0
  38. package/scripts/simRealBattle.js +121 -0
  39. package/scripts/validateBattle.js +74 -70
  40. package/scripts/validateTournament.js +101 -101
  41. package/utils/contracts.js +12 -12
  42. package/utils/errors.js +29 -29
  43. package/utils/mapGotchi.js +119 -0
  44. package/utils/transforms.js +89 -88
  45. package/utils/validations.js +39 -39
  46. package/debug.log +0 -2
  47. package/game-logic/v1.6/debug.log +0 -1
  48. package/game-logic/v1.7/debug.log +0 -3
  49. package/scripts/data/debug.log +0 -2
  50. package/scripts/debug.log +0 -1
@@ -0,0 +1,121 @@
1
+ const axios = require('axios')
2
+ const axiosRetry = require('axios-retry').default
3
+ axiosRetry(axios, {
4
+ retries: 3,
5
+ retryDelay: axiosRetry.exponentialDelay
6
+ })
7
+
8
+ const { webappTeamToInGameTeam } = require('../utils/transforms')
9
+ const { mapTeam } = require('../utils/mapGotchi')
10
+ const { battle } = require('..')
11
+
12
+ const main = async (battleId) => {
13
+
14
+ const battleRes = await axios.get(`https://gotchi-battler-backend-blmom6tkla-ew.a.run.app/api/v1/battles/${battleId}`)
15
+
16
+ if (!battleRes || !battleRes.data || !battleRes.data.team1 || !battleRes.data.team2) {
17
+ console.error('Battle not found')
18
+ return
19
+ }
20
+
21
+ // Transform the logs to in-game teams
22
+ const team1 = webappTeamToInGameTeam(battleRes.data.team1Snapshot || battleRes.data.team1)
23
+ const team2 = webappTeamToInGameTeam(battleRes.data.team2Snapshot || battleRes.data.team2)
24
+
25
+ console.log('Team 1', team1.formation.front.filter(g => g).map(g => {
26
+ return {
27
+ name: g.name,
28
+ health: g.health,
29
+ magic: g.magic,
30
+ physical: g.physical
31
+ }
32
+ }).concat(team1.formation.back.filter(g => g).map(g => {
33
+ return {
34
+ name: g.name,
35
+ health: g.health,
36
+ magic: g.magic,
37
+ physical: g.physical
38
+ }
39
+ })))
40
+ console.log('Team 2', team2.formation.front.filter(g => g).map(g => {
41
+ return {
42
+ name: g.name,
43
+ health: g.health,
44
+ magic: g.magic,
45
+ physical: g.physical
46
+ }
47
+ }).concat(team2.formation.back.filter(g => g).map(g => {
48
+ return {
49
+ name: g.name,
50
+ health: g.health,
51
+ magic: g.magic,
52
+ physical: g.physical
53
+ }
54
+ })))
55
+
56
+ mapTeam(team1)
57
+ mapTeam(team2)
58
+
59
+ console.log('Team 1', team1.formation.front.filter(g => g).map(g => {
60
+ return {
61
+ name: g.name,
62
+ health: g.health,
63
+ magic: g.magic,
64
+ physical: g.physical
65
+ }
66
+ }).concat(team1.formation.back.filter(g => g).map(g => {
67
+ return {
68
+ name: g.name,
69
+ health: g.health,
70
+ magic: g.magic,
71
+ physical: g.physical
72
+ }
73
+ })))
74
+ console.log('Team 2', team2.formation.front.filter(g => g).map(g => {
75
+ return {
76
+ name: g.name,
77
+ health: g.health,
78
+ magic: g.magic,
79
+ physical: g.physical
80
+ }
81
+ }).concat(team2.formation.back.filter(g => g).map(g => {
82
+ return {
83
+ name: g.name,
84
+ health: g.health,
85
+ magic: g.magic,
86
+ physical: g.physical
87
+ }
88
+ })))
89
+
90
+ // Run 1000 sims to get the average stats
91
+ let team1Wins = 0
92
+ let team2Wins = 0
93
+ for (let i = 0; i < 1000; i++) {
94
+ const logs = await battle(team1, team2, `${Math.random()}`, false)
95
+ if (logs.result.winner === 1) {
96
+ team1Wins++
97
+ } else {
98
+ team2Wins++
99
+ }
100
+ }
101
+
102
+ console.log(`Team 1 wins: ${team1Wins}`)
103
+ console.log(`Team 2 wins: ${team2Wins}`)
104
+ }
105
+
106
+ module.exports = main
107
+
108
+ // node scripts/simRealBattle.js 19566507-bafe-4a97-a5ee-0926ac9efcd8
109
+ if (require.main === module) {
110
+ const battleId = process.argv[2]
111
+
112
+ main(battleId)
113
+ .then(() => {
114
+ console.log('Done')
115
+ process.exit(0)
116
+ })
117
+ .catch((error) => {
118
+ console.error('Error: ', error)
119
+ process.exit(1)
120
+ })
121
+ }
@@ -1,70 +1,74 @@
1
- const axios = require('axios')
2
- const axiosRetry = require('axios-retry').default
3
- axiosRetry(axios, {
4
- retries: 3,
5
- retryDelay: axiosRetry.exponentialDelay
6
- })
7
-
8
- const { GameError, ValidationError } = require('../utils/errors')
9
- const { logToInGameTeams } = require('../utils/transforms')
10
- const { compareLogs } = require('../utils/validations')
11
- const gameVersions = require('../game-logic')
12
-
13
- const main = async (battleId, seed, gameLogicVersion) => {
14
-
15
- if (!gameLogicVersion) gameLogicVersion = gameVersions.current
16
- if (!gameVersions[gameLogicVersion]) throw new Error('Invalid game logic version')
17
-
18
- const gameLoop = gameVersions[gameLogicVersion].gameLoop
19
-
20
- const res = await axios.get(`https://storage.googleapis.com/gotchi-battler-live_battles/v1/${battleId}.json`)
21
-
22
- if (!res || !res.data || !res.data.layout) {
23
- console.error('Battle not found')
24
- return
25
- }
26
-
27
- // Transform the logs to in-game teams
28
- const teams = logToInGameTeams(res.data)
29
-
30
- try {
31
- // Run the game loop
32
- const logs = await gameLoop(teams[0], teams[1], seed)
33
-
34
- // Validate the results
35
- compareLogs(res.data, logs)
36
-
37
- return logs
38
- } catch (error) {
39
- if (error instanceof GameError) {
40
- console.error('Errored game logs: ', error.logs)
41
- }
42
-
43
- throw error
44
- }
45
- }
46
-
47
- module.exports = main
48
-
49
- // node scripts/validateBattle.js 4d0f3c5c-08a0-42db-bd34-dee44300685a 82807311112923564712218359337695919195403960526804010606215202651499586140469
50
- if (require.main === module) {
51
- const battleId = process.argv[2]
52
- const seed = process.argv[3]
53
- const gameLogicVersion = process.argv[4]
54
-
55
- main(battleId, seed, gameLogicVersion)
56
- .then(() => {
57
- console.log('Results from game logic match the logs ✅')
58
- console.log('Done')
59
- process.exit(0)
60
- })
61
- .catch((error) => {
62
- if (error instanceof ValidationError) {
63
- console.error('Results from game logic do not match the logs ❌')
64
- }
65
-
66
- console.error('Error: ', error.message)
67
-
68
- process.exit(1)
69
- })
70
- }
1
+ const axios = require('axios')
2
+ const axiosRetry = require('axios-retry').default
3
+ axiosRetry(axios, {
4
+ retries: 3,
5
+ retryDelay: axiosRetry.exponentialDelay
6
+ })
7
+ const fs = require('fs')
8
+ const path = require('path')
9
+
10
+ const { GameError, ValidationError } = require('../utils/errors')
11
+ const { logToInGameTeams } = require('../utils/transforms')
12
+ const { compareLogs } = require('../utils/validations')
13
+ const gameVersions = require('../game-logic')
14
+
15
+ const main = async (battleId, seed, gameLogicVersion) => {
16
+
17
+ if (!gameLogicVersion) gameLogicVersion = gameVersions.current
18
+ if (!gameVersions[gameLogicVersion]) throw new Error('Invalid game logic version')
19
+
20
+ const gameLoop = gameVersions[gameLogicVersion].gameLoop
21
+
22
+ const res = await axios.get(`https://storage.googleapis.com/gotchi-battler-live_battles/v1/${battleId}.json`)
23
+
24
+ if (!res || !res.data || !res.data.layout) {
25
+ console.error('Battle not found')
26
+ return
27
+ }
28
+
29
+ // Transform the logs to in-game teams
30
+ const teams = logToInGameTeams(res.data)
31
+
32
+ try {
33
+ // Run the game loop
34
+ const logs = await gameLoop(teams[0], teams[1], seed, true)
35
+
36
+ fs.writeFileSync(path.join(__dirname, 'output', `${battleId}_${Date.now()}.json`), JSON.stringify(logs, null, '\t'))
37
+
38
+ // Validate the results
39
+ compareLogs(res.data, logs)
40
+
41
+ return logs
42
+ } catch (error) {
43
+ if (error instanceof GameError) {
44
+ console.error('Errored game logs: ', error.logs)
45
+ }
46
+
47
+ throw error
48
+ }
49
+ }
50
+
51
+ module.exports = main
52
+
53
+ // node scripts/validateBattle.js 19566507-bafe-4a97-a5ee-0926ac9efcd8 86621513544048786694066938490119673052266855523096067323796322823978866460212
54
+ if (require.main === module) {
55
+ const battleId = process.argv[2]
56
+ const seed = process.argv[3]
57
+ const gameLogicVersion = process.argv[4]
58
+
59
+ main(battleId, seed, gameLogicVersion)
60
+ .then(() => {
61
+ console.log('Results from game logic match the logs ✅')
62
+ console.log('Done')
63
+ process.exit(0)
64
+ })
65
+ .catch((error) => {
66
+ if (error instanceof ValidationError) {
67
+ console.error('Results from game logic do not match the logs ❌')
68
+ }
69
+
70
+ console.error('Error: ', error.message)
71
+
72
+ process.exit(1)
73
+ })
74
+ }
@@ -1,101 +1,101 @@
1
- const fs = require('fs')
2
- const path = require('path')
3
- const axios = require('axios')
4
- const { getTournamentContract } = require('../utils/contracts')
5
- const { ValidationError } = require('../utils/errors')
6
- const tournaments = require('./data/tournaments.json')
7
- const validateBattle = require('./validateBattle')
8
-
9
- const main = async (tournamentId) => {
10
-
11
- console.log(`Getting tournament config for id ${tournamentId}...`)
12
-
13
- const tournamentConfig = tournaments.find(t => `${t.id}` === `${tournamentId}`)
14
-
15
- if (!tournamentConfig || !tournamentConfig.gameLogicVersion) {
16
- console.error('Tournament config not found with id:', tournamentId)
17
- return
18
- }
19
-
20
- console.log(`Found, using game logic version "${tournamentConfig.gameLogicVersion}" ✅`)
21
- console.log(`Getting tournament data for id: ${tournamentId}...`)
22
-
23
- const tournament = await axios.get(`https://gotchi-battler-backend-blmom6tkla-ew.a.run.app/api/v1/tournaments/${tournamentId}`)
24
-
25
- if (!tournament || !tournament.data || !tournament.data.address) {
26
- console.error('Tournament not found with id:', tournamentId)
27
- return
28
- }
29
-
30
- const onchainAddress = tournament.data.address
31
-
32
- const brackets = await axios.get(`https://gotchi-battler-backend-blmom6tkla-ew.a.run.app/api/v1/tournaments/${tournamentId}/brackets`)
33
-
34
- if (!brackets || !brackets.data || !brackets.data.length) {
35
- console.error('Brackets not found')
36
- return
37
- }
38
-
39
- console.log(`Tournament data for id ${tournamentId} found ✅`)
40
- console.log(`Validating "${tournament.data.name}"...`)
41
- console.log('(This process can take up to 20 minutes for large tournaments)')
42
- for (const bracket of brackets.data) {
43
- console.log(`Validating "${bracket.name}"...`)
44
- for (const round of bracket.rounds) {
45
- for (const battle of round.battles) {
46
- // If battle is a BYE then skip
47
- if (!battle.team1Id || !battle.team2Id) {
48
- continue
49
- }
50
-
51
- // Get seed for the battle
52
- const tournamentContract = getTournamentContract(onchainAddress)
53
- const seed = await tournamentContract.roundSeeds(round.roundStage)
54
-
55
- try {
56
- await validateBattle(battle.id, seed.toString(), tournamentConfig.gameLogicVersion)
57
- } catch (error) {
58
- if (error instanceof ValidationError) {
59
- console.error(`Battle ${battle.id} failed validation ❌`)
60
- console.error(`Seed: "${seed.toString()}"`)
61
-
62
- // Write original logs to file
63
- const originalLogsFilename = `${battle.id}-originalLogs.json`
64
- fs.writeFileSync(path.join(__dirname, 'output', originalLogsFilename), JSON.stringify(error.originalLogs, null, '\t'))
65
-
66
- // Write new logs to file
67
- const newLogsFilename = `${battle.id}-newLogs.json`
68
- fs.writeFileSync(path.join(__dirname, 'output', newLogsFilename), JSON.stringify(error.newLogs, null, '\t'))
69
-
70
- console.error(`Original logs written to ${path.join(__dirname, 'output', originalLogsFilename)}`)
71
- console.error(`New logs written to ${path.join(__dirname, 'output', newLogsFilename)}`)
72
- }
73
-
74
- throw error
75
- }
76
-
77
- }
78
- console.log(`Round ${round.roundStage} validated ✅`)
79
- }
80
- console.log(`"${bracket.name}" validated ✅`)
81
- }
82
- console.log(`"${tournament.data.name}" validated ✅`)
83
- }
84
-
85
- module.exports = main
86
-
87
- // node scripts/validateTournament.js 15
88
- if (require.main === module) {
89
- const tournamentId = process.argv[2]
90
-
91
- main(tournamentId)
92
- .then(() => {
93
- console.log('Done')
94
- process.exit(0)
95
- })
96
- .catch((error) => {
97
- console.error(error)
98
-
99
- process.exit(1)
100
- })
101
- }
1
+ const fs = require('fs')
2
+ const path = require('path')
3
+ const axios = require('axios')
4
+ const { getTournamentContract } = require('../utils/contracts')
5
+ const { ValidationError } = require('../utils/errors')
6
+ const tournaments = require('./data/tournaments.json')
7
+ const validateBattle = require('./validateBattle')
8
+
9
+ const main = async (tournamentId) => {
10
+
11
+ console.log(`Getting tournament config for id ${tournamentId}...`)
12
+
13
+ const tournamentConfig = tournaments.find(t => `${t.id}` === `${tournamentId}`)
14
+
15
+ if (!tournamentConfig || !tournamentConfig.gameLogicVersion) {
16
+ console.error('Tournament config not found with id:', tournamentId)
17
+ return
18
+ }
19
+
20
+ console.log(`Found, using game logic version "${tournamentConfig.gameLogicVersion}" ✅`)
21
+ console.log(`Getting tournament data for id: ${tournamentId}...`)
22
+
23
+ const tournament = await axios.get(`https://gotchi-battler-backend-blmom6tkla-ew.a.run.app/api/v1/tournaments/${tournamentId}`)
24
+
25
+ if (!tournament || !tournament.data || !tournament.data.address) {
26
+ console.error('Tournament not found with id:', tournamentId)
27
+ return
28
+ }
29
+
30
+ const onchainAddress = tournament.data.address
31
+
32
+ const brackets = await axios.get(`https://gotchi-battler-backend-blmom6tkla-ew.a.run.app/api/v1/tournaments/${tournamentId}/brackets`)
33
+
34
+ if (!brackets || !brackets.data || !brackets.data.length) {
35
+ console.error('Brackets not found')
36
+ return
37
+ }
38
+
39
+ console.log(`Tournament data for id ${tournamentId} found ✅`)
40
+ console.log(`Validating "${tournament.data.name}"...`)
41
+ console.log('(This process can take up to 20 minutes for large tournaments)')
42
+ for (const bracket of brackets.data) {
43
+ console.log(`Validating "${bracket.name}"...`)
44
+ for (const round of bracket.rounds) {
45
+ for (const battle of round.battles) {
46
+ // If battle is a BYE then skip
47
+ if (!battle.team1Id || !battle.team2Id) {
48
+ continue
49
+ }
50
+
51
+ // Get seed for the battle
52
+ const tournamentContract = getTournamentContract(onchainAddress)
53
+ const seed = await tournamentContract.roundSeeds(round.roundStage)
54
+
55
+ try {
56
+ await validateBattle(battle.id, seed.toString(), tournamentConfig.gameLogicVersion)
57
+ } catch (error) {
58
+ if (error instanceof ValidationError) {
59
+ console.error(`Battle ${battle.id} failed validation ❌`)
60
+ console.error(`Seed: "${seed.toString()}"`)
61
+
62
+ // Write original logs to file
63
+ const originalLogsFilename = `${battle.id}-originalLogs.json`
64
+ fs.writeFileSync(path.join(__dirname, 'output', originalLogsFilename), JSON.stringify(error.originalLogs, null, '\t'))
65
+
66
+ // Write new logs to file
67
+ const newLogsFilename = `${battle.id}-newLogs.json`
68
+ fs.writeFileSync(path.join(__dirname, 'output', newLogsFilename), JSON.stringify(error.newLogs, null, '\t'))
69
+
70
+ console.error(`Original logs written to ${path.join(__dirname, 'output', originalLogsFilename)}`)
71
+ console.error(`New logs written to ${path.join(__dirname, 'output', newLogsFilename)}`)
72
+ }
73
+
74
+ throw error
75
+ }
76
+
77
+ }
78
+ console.log(`Round ${round.roundStage} validated ✅`)
79
+ }
80
+ console.log(`"${bracket.name}" validated ✅`)
81
+ }
82
+ console.log(`"${tournament.data.name}" validated ✅`)
83
+ }
84
+
85
+ module.exports = main
86
+
87
+ // node scripts/validateTournament.js 15
88
+ if (require.main === module) {
89
+ const tournamentId = process.argv[2]
90
+
91
+ main(tournamentId)
92
+ .then(() => {
93
+ console.log('Done')
94
+ process.exit(0)
95
+ })
96
+ .catch((error) => {
97
+ console.error(error)
98
+
99
+ process.exit(1)
100
+ })
101
+ }
@@ -1,13 +1,13 @@
1
- const { ethers } = require('ethers')
2
- const tournamentAbi = require('../constants/tournamentManagerAbi.json')
3
-
4
- const getTournamentContract = (address) => {
5
- const provider = new ethers.JsonRpcProvider('https://polygon-rpc.com')
6
- const contract = new ethers.Contract(address, tournamentAbi, provider)
7
-
8
- return contract
9
- }
10
-
11
- module.exports = {
12
- getTournamentContract
1
+ const { ethers } = require('ethers')
2
+ const tournamentAbi = require('../constants/tournamentManagerAbi.json')
3
+
4
+ const getTournamentContract = (address) => {
5
+ const provider = new ethers.JsonRpcProvider('https://polygon-rpc.com')
6
+ const contract = new ethers.Contract(address, tournamentAbi, provider)
7
+
8
+ return contract
9
+ }
10
+
11
+ module.exports = {
12
+ getTournamentContract
13
13
  }
package/utils/errors.js CHANGED
@@ -1,30 +1,30 @@
1
- class GameError extends Error {
2
- constructor(msg, logs) {
3
- super(msg)
4
- this.name = 'GameError'
5
-
6
- if(logs) {
7
- this.logs = logs
8
- }
9
- }
10
- }
11
-
12
- class ValidationError extends Error {
13
- constructor(msg, originalLogs, newLogs) {
14
- super(msg)
15
- this.name = 'ValidationError'
16
-
17
- if(originalLogs) {
18
- this.originalLogs = originalLogs
19
- }
20
-
21
- if(newLogs) {
22
- this.newLogs = newLogs
23
- }
24
- }
25
- }
26
-
27
- module.exports = {
28
- GameError,
29
- ValidationError
1
+ class GameError extends Error {
2
+ constructor(msg, logs) {
3
+ super(msg)
4
+ this.name = 'GameError'
5
+
6
+ if(logs) {
7
+ this.logs = logs
8
+ }
9
+ }
10
+ }
11
+
12
+ class ValidationError extends Error {
13
+ constructor(msg, originalLogs, newLogs) {
14
+ super(msg)
15
+ this.name = 'ValidationError'
16
+
17
+ if(originalLogs) {
18
+ this.originalLogs = originalLogs
19
+ }
20
+
21
+ if(newLogs) {
22
+ this.newLogs = newLogs
23
+ }
24
+ }
25
+ }
26
+
27
+ module.exports = {
28
+ GameError,
29
+ ValidationError
30
30
  }
@@ -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.85',
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.35',
52
+ multiplier: 5,
53
+ traitKey: 3,
54
+ isNegative: false
55
+ },
56
+ physical: {
57
+ baseFormula: 'brs*0.35',
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
+ }