-
Notifications
You must be signed in to change notification settings - Fork 2
/
cryptoblades.js
194 lines (159 loc) · 6.31 KB
/
cryptoblades.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
import Web3 from 'web3';
import config from './config.js';
import {
characterABI,
characterAddress,
cryptoBladesABI,
cryptoBladesAddress,
weaponABI,
weaponAddress
} from './constants.js';
const web3 = new Web3(config.blockchainProvider);
const characterContract = new web3.eth.Contract(characterABI, characterAddress);
const cryptoBladesContract = new web3.eth.Contract(cryptoBladesABI, cryptoBladesAddress);
const weaponContract = new web3.eth.Contract(weaponABI, weaponAddress);
export async function getAccountCharacters(address) {
const numberOfCharacters = parseInt(await characterContract.methods.balanceOf(address).call(), 10);
const characters = await Promise.all(
[...Array(numberOfCharacters).keys()]
.map((_, i) => characterContract.methods.tokenOfOwnerByIndex(address, i).call())
);
return characters;
}
export async function getAccountWeapons(address) {
const numberOfWeapons = parseInt(await weaponContract.methods.balanceOf(address).call(), 10);
const weapons = await Promise.all(
[...Array(numberOfWeapons).keys()]
.map((_, i) => weaponContract.methods.tokenOfOwnerByIndex(address, i).call())
);
return weapons;
}
export async function getCharacterStamina(characterId) {
return parseInt(await characterContract.methods.getStaminaPoints(characterId).call(), 10);
}
async function fetchEnemies(characterId, weaponId) {
const enemies = await cryptoBladesContract.methods.getTargets(characterId, weaponId).call();
return enemies.map(enemy => {
const parsedData = parseInt(enemy, 10);
return {
// eslint-disable-next-line no-bitwise
power: parsedData & 0b11111111_11111111_11111111,
// eslint-disable-next-line no-bitwise
trait: parsedData >> 24
};
});
}
function calculateCharacterPower(level) {
return (1000 + 10 * level) * (Math.floor(level / 10) + 1);
}
const numberOfTraits = 4;
function getTraitEffectiveness(firstTrait, secondTrait) {
if ((firstTrait + 1) % numberOfTraits === secondTrait) {
return 1;
}
if ((secondTrait + 1) % numberOfTraits === firstTrait) {
return -1;
}
return 0;
}
function calculateTraitBonus(characterTrait, weaponTrait, enemyTrait) {
let traitBonus = 1;
if (characterTrait === weaponTrait) {
traitBonus += 0.075;
}
traitBonus += 0.075 * getTraitEffectiveness(characterTrait, enemyTrait);
return traitBonus;
}
async function fetchCharacter(characterId) {
const {1: level, 2: trait} = await characterContract.methods.get(characterId).call();
return {
level: parseInt(level, 10),
trait: parseInt(trait, 10)
};
}
function getWeaponStatTypes(weaponProperties) {
// eslint-disable-next-line no-bitwise
const pattern = (weaponProperties >> 5) & 0x7f;
return [
pattern % 5,
Math.floor(pattern / 5) % 5,
Math.floor(Math.floor(pattern / 5) / 5) % 5
];
}
const powerTrait = 4;
function calculateWeaponStat(type, value, characterTrait) {
let statValue = value;
if (type !== characterTrait) {
statValue *= 0.0025;
} else if (type === characterTrait) {
statValue *= 0.002675;
}
if (type === powerTrait) {
statValue *= 0.002575;
}
return statValue;
}
function calculateTotalWeaponStats(stats, properties, characterTrait) {
const statTypes = getWeaponStatTypes(properties);
return statTypes
.map((statType, i) => calculateWeaponStat(statType, stats[i], characterTrait))
.reduce((previous, current) => previous + current, 0);
}
async function fetchWeapon(weaponId, characterTrait) {
const {
_properties,
_stat1: stat1,
_stat2: stat2,
_stat3: stat3,
_bonusPower
} = await weaponContract.methods.get(weaponId).call();
const bonusPower = parseInt(_bonusPower, 10);
const properties = parseInt(_properties, 10);
// eslint-disable-next-line no-bitwise
const trait = (properties >> 3) & 0x3;
const stats = [stat1, stat2, stat3].map(stat => parseInt(stat, 10));
const totalStats = calculateTotalWeaponStats(stats, properties, characterTrait);
return {
trait,
bonusPower,
totalStats
};
}
function calculateFightWinPercentage(characterPower, characterTrait, weaponTrait, enemy) {
const traitBonus = calculateTraitBonus(characterTrait, weaponTrait, enemy.trait);
const minCharacterRoll = Math.floor(characterPower * traitBonus * 0.9);
const maxCharacterRoll = Math.floor(characterPower * traitBonus * 1.1);
const minEnemyRoll = Math.floor(enemy.power * 0.9);
const maxEnemyRoll = Math.floor(enemy.power * 1.1);
// TODO: currently using same method as cbtracker for calculating win percentage
// TODO: find a way of doing this without nested loops
let winRolls = 0;
let loseRolls = 0;
for (let characterRoll = minCharacterRoll; characterRoll <= maxCharacterRoll; characterRoll++) {
for (let enemyRoll = minEnemyRoll; enemyRoll <= maxEnemyRoll; enemyRoll++) {
if (characterRoll >= enemyRoll) {
winRolls++;
} else {
loseRolls++;
}
}
}
const winPercentage = (winRolls / (winRolls + loseRolls)) * 100;
// Only show two decimal places
return Math.floor(winPercentage * 100) / 100;
}
export async function calculateHighestFightWinPercentage(characterId, weaponId) {
const enemies = await fetchEnemies(characterId, weaponId);
const character = await fetchCharacter(characterId);
const characterPower = calculateCharacterPower(character.level);
const weapon = await fetchWeapon(weaponId, character.trait);
const alignedCharacterPower = ((weapon.totalStats + 1) * characterPower) + weapon.bonusPower;
const enemiesWithWinFightPercentages = enemies.map((enemy, i) => ({...enemy, index: i + 1, winPercentage: calculateFightWinPercentage(alignedCharacterPower, character.trait, weapon.trait, enemy)}));
let highestPercentageEnemy;
for (let i = 0; i < enemiesWithWinFightPercentages.length; i++) {
if (!highestPercentageEnemy || enemiesWithWinFightPercentages[i].winPercentage > highestPercentageEnemy.winPercentage) {
highestPercentageEnemy = enemiesWithWinFightPercentages[i];
}
}
return highestPercentageEnemy;
}