Compare commits

...

24 commits
dev ... cpp

Author SHA1 Message Date
hppeng 73b5f8fbd0 Merge branch 'cpp' of github.com:hppeng-wynn/hppeng-wynn.github.io into cpp 2023-07-01 13:30:24 -07:00
hppeng 71f1cd659d Minor cleanup, start item class 2023-04-14 16:31:19 -07:00
hppeng a09a901545 Minor cleanup, start item class 2023-04-10 13:08:22 -07:00
hppeng 150fb43847 Fix corner case bug in bitvector implementation for constructing 0 length bv's
that broke atree encoding
2023-04-10 13:08:22 -07:00
hppeng 1c07cd0911 Fix bugs with powders and migrate constants to c++ 2023-04-10 13:08:22 -07:00
hppeng 3a5876ceba small progress 2023-04-10 13:08:22 -07:00
hppeng caa4c87662 Start working on powders translation
taking a pause to clean up load code
2023-04-10 13:08:22 -07:00
hppeng ed60e2ac6d Bitvector c++ implementation 2023-04-10 13:08:22 -07:00
hppeng f656c944e8 More utils files, some reorganization 2023-04-10 13:08:22 -07:00
hppeng b157f79fc0 Trying things out with emscripten
super inefficient for dev for now; set multiple files to compile separately
into frankenstein js files as I migrate code
2023-04-10 13:08:22 -07:00
hppeng a9c96d3629 Add prologue and gleeman's tale
wynn api when
...fix epilgoue
2023-04-10 13:07:29 -07:00
hppeng c5624e3673 Fix mask of the awakened giving outdated stats
e
2023-04-08 14:55:51 -07:00
hppeng 01865fac7c Finally fix satsujin to work with powder specials
thanks to powder special display refactor
2023-04-04 16:35:55 -07:00
hppeng 997373246c Refactor powder special display
fix quake/chain/courage not displaying some powder special information 💀
2023-04-04 16:30:20 -07:00
hppeng c6549fffbb Move powder ingreds to ing load sequence
not used anywhere else
also, remove extra prints in crafter
2023-04-03 11:33:37 -07:00
hppeng f75383dd98 Fix minor incorrectness with fromIntV invocation
don't think this was a bug? but its not the correct number of arguments lol
2023-04-01 17:07:49 -07:00
hppeng 1ce6ec0af6 Fix epilogue displayName 2023-03-25 09:17:44 -07:00
hppeng a63629fc88 Forgot to commit all the 2.0.2.3 data files... 2023-03-25 08:58:59 -07:00
hppeng 9ebac6b59e Partial update to 2.0.2.3 (festival of heroes)
patch:
- ing changes (manual)
- two endgame items (the ones that I got customs for)

bugfix:
- Fix bug in reverse mapping that mapped item "type" to "accessoryType"
2023-03-25 08:35:09 -07:00
hppeng 73324d2a82 Fix typo in better lightweaver
add to the correct dps
2023-03-17 10:30:33 -07:00
hppeng a15020aac8 Clean up testing folder
and add script for quick plotting pairs of ids/item values
2023-03-13 22:57:40 -07:00
hppeng b14cde01c5 Misc bugfix
Fix bug with skillpoints and negative set bonus
Add final multiplier for echo
2023-03-13 08:09:03 -07:00
hppeng 9e72e44ffe Lacerate is blocked by Echo, not Mirror Image 2023-03-06 01:17:51 -08:00
hppeng 74989a7b3c Fix enraged blow typo; allow "or" and "and" in adv search
...forgot to update json
2023-03-05 17:21:59 -08:00
48 changed files with 29144 additions and 26245 deletions

52140
clean.json

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.2.3/atree.json Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
data/2.0.2.3/items.json Normal file

File diff suppressed because one or more lines are too long

1069
data/2.0.2.3/tomes.json Normal file

File diff suppressed because it is too large Load diff

View file

@ -15710,8 +15710,8 @@
"maximum": 7
},
"lq": {
"minimum": 3,
"maximum": 4
"minimum": 4,
"maximum": 5
}
},
"itemIDs": {
@ -16169,7 +16169,7 @@
"lvl": 4,
"ids": {},
"itemIDs": {
"dura": -70,
"dura": -30,
"strReq": 0,
"dexReq": 0,
"intReq": 0,
@ -18451,7 +18451,7 @@
"posMods": {
"left": 0,
"right": 0,
"above": 30,
"above": 25,
"under": 0,
"touching": 0,
"notTouching": 0
@ -23923,7 +23923,7 @@
"lvl": 100,
"ids": {},
"itemIDs": {
"dura": -175,
"dura": -160,
"strReq": 0,
"dexReq": 0,
"intReq": 0,
@ -23934,7 +23934,7 @@
},
"consumableIDs": {
"charges": 0,
"dura": -300
"dura": -275
},
"posMods": {
"left": 110,
@ -24214,8 +24214,8 @@
"right": 0,
"above": 0,
"under": 0,
"touching": 12,
"notTouching": 12
"touching": 15,
"notTouching": 15
},
"id": 569
},
@ -24722,7 +24722,7 @@
"above": 0,
"under": 0,
"touching": -100,
"notTouching": 75
"notTouching": 55
},
"id": 582
},
@ -24913,8 +24913,8 @@
"posMods": {
"left": -100,
"right": -100,
"above": 75,
"under": 75,
"above": 60,
"under": 60,
"touching": 0,
"notTouching": 0
},
@ -24942,10 +24942,10 @@
"dura": -300
},
"posMods": {
"left": -75,
"right": -75,
"above": -50,
"under": -50,
"left": -50,
"right": -50,
"above": -75,
"under": -75,
"touching": 120,
"notTouching": 0
},
@ -25983,7 +25983,7 @@
"above": 0,
"under": 0,
"touching": 0,
"notTouching": 65
"notTouching": 55
},
"id": 610
},
@ -26140,8 +26140,8 @@
"right": 0,
"above": 0,
"under": 0,
"touching": 33,
"notTouching": 33
"touching": 25,
"notTouching": 25
},
"id": 614
},
@ -26688,8 +26688,8 @@
"right": -122,
"above": 0,
"under": 0,
"touching": -29,
"notTouching": -29
"touching": -15,
"notTouching": -15
},
"id": 627
},
@ -26846,7 +26846,7 @@
"posMods": {
"left": 0,
"right": 0,
"above": -180,
"above": -140,
"under": 0,
"touching": 0,
"notTouching": 0
@ -27433,12 +27433,12 @@
"dura": 0
},
"posMods": {
"left": -45,
"right": -45,
"left": -30,
"right": -30,
"above": 0,
"under": 0,
"touching": 45,
"notTouching": 45
"touching": 30,
"notTouching": 30
},
"id": 645
},
@ -28657,8 +28657,8 @@
"right": 0,
"above": 0,
"under": 0,
"touching": 20,
"notTouching": 20
"touching": 15,
"notTouching": 15
},
"id": 691
},
@ -28715,7 +28715,7 @@
"lvl": 80,
"ids": {},
"itemIDs": {
"dura": -150,
"dura": -140,
"strReq": -25,
"dexReq": -25,
"intReq": -25,
@ -28802,8 +28802,8 @@
"right": 0,
"above": 0,
"under": 0,
"touching": 40,
"notTouching": 80
"touching": 25,
"notTouching": 45
},
"id": 674
},
@ -29051,4 +29051,4 @@
},
"id": 679
}
]
]

File diff suppressed because one or more lines are too long

View file

@ -72,7 +72,7 @@ const all_types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(c
//console.log(types)
let item_types = armorTypes.concat(accessoryTypes).concat(weaponTypes).concat(tome_types);
let elementIcons = ["\u2724","\u2726", "\u2749", "\u2739", "\u274b" ];
let elementIcons = ["\u2724", "\u2726", "\u2749", "\u2739", "\u274b" ];
let skpReqs = skp_order.map(x => x + "Req");
let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rSdRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "damMobs", "defMobs",
@ -90,14 +90,16 @@ let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slot
"spPct1Final", "spPct2Final", "spPct3Final", "spPct4Final"
];
// Extra fake IDs (reserved for use in spell damage calculation) : damMult, defMult, poisonPct, activeMajorIDs
let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ]
let str_item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "type", "material", "drop", "quest", "restrict", "category", "atkSpd" ];
//File reading for ID translations for JSON purposes
let reversetranslations = new Map();
let translations = new Map([["name", "name"],["displayName", "displayName"],["tier", "tier"],["set", "set"],["sockets", "slots"],["type", "type"],["armorColor", "color"],["addedLore", "lore"],["dropType", "drop"],["quest", "quest"],["restrictions", "restrict"],["damage", "nDam"],["fireDamage", "fDam"],["waterDamage", "wDam"],["airDamage", "aDam"],["thunderDamage", "tDam"],["earthDamage", "eDam"],["attackSpeed", "atkSpd"],["health", "hp"],["fireDefense", "fDef"],["waterDefense", "wDef"],["airDefense", "aDef"],["thunderDefense", "tDef"],["earthDefense", "eDef"],["level", "lvl"],["classRequirement", "classReq"],["strength", "strReq"],["dexterity", "dexReq"],["intelligence", "intReq"],["agility", "agiReq"],["defense", "defReq"],["healthRegen", "hprPct"],["manaRegen", "mr"],["spellDamageBonus", "sdPct"],["spellElementalDamageBonus", "rSdPct"],["spellNeutralDamageBonus", "nSdPct"],["spellFireDamageBonus", "fSdPct"],["spellWaterDamageBonus", "wSdPct"],["spellAirDamageBonus", "aSdPct"],["spellThunderDamageBonus", "tSdPct"],["spellEarthDamageBonus", "eSdPct"],["mainAttackDamageBonus", "mdPct"],["mainAttackElementalDamageBonus", "rMdPct"],["mainAttackNeutralDamageBonus", "nMdPct"],["mainAttackFireDamageBonus", "fMdPct"],["mainAttackWaterDamageBonus", "wMdPct"],["mainAttackAirDamageBonus", "aMdPct"],["mainAttackThunderDamageBonus", "tMdPct"],["mainAttackEarthDamageBonus", "eMdPct"],["lifeSteal", "ls"],["manaSteal", "ms"],["xpBonus", "xpb"],["lootBonus", "lb"],["reflection", "ref"],["strengthPoints", "str"],["dexterityPoints", "dex"],["intelligencePoints", "int"],["agilityPoints", "agi"],["defensePoints", "def"],["thorns", "thorns"],["exploding", "expd"],["speed", "spd"],["attackSpeedBonus", "atkTier"],["poison", "poison"],["healthBonus", "hpBonus"],["soulPoints", "spRegen"],["emeraldStealing", "eSteal"],["healthRegenRaw", "hprRaw"],["spellDamageBonusRaw", "sdRaw"],["spellElementalDamageBonusRaw", "rSdRaw"],["spellNeutralDamageBonusRaw", "nSdRaw"],["spellFireDamageBonusRaw", "fSdRaw"],["spellWaterDamageBonusRaw", "wSdRaw"],["spellAirDamageBonusRaw", "aSdRaw"],["spellThunderDamageBonusRaw", "tSdRaw"],["spellEarthDamageBonusRaw", "eSdRaw"],["mainAttackDamageBonusRaw", "mdRaw"],["mainAttackElementalDamageBonusRaw", "rMdRaw"],["mainAttackNeutralDamageBonusRaw", "nMdRaw"],["mainAttackFireDamageBonusRaw", "fMdRaw"],["mainAttackWaterDamageBonusRaw", "wMdRaw"],["mainAttackAirDamageBonusRaw", "aMdRaw"],["mainAttackThunderDamageBonusRaw", "tMdRaw"],["mainAttackEarthDamageBonusRaw", "eMdRaw"],["fireDamageBonus", "fDamPct"],["waterDamageBonus", "wDamPct"],["airDamageBonus", "aDamPct"],["thunderDamageBonus", "tDamPct"],["earthDamageBonus", "eDamPct"],["bonusFireDefense", "fDefPct"],["bonusWaterDefense", "wDefPct"],["bonusAirDefense", "aDefPct"],["bonusThunderDefense", "tDefPct"],["bonusEarthDefense", "eDefPct"],["accessoryType", "type"],["identified", "fixID"],["skin", "skin"],["category", "category"],["spellCostPct1", "spPct1"],["spellCostRaw1", "spRaw1"],["spellCostPct2", "spPct2"],["spellCostRaw2", "spRaw2"],["spellCostPct3", "spPct3"],["spellCostRaw3", "spRaw3"],["spellCostPct4", "spPct4"],["spellCostRaw4", "spRaw4"],["sprint", "sprint"],["sprintRegen", "sprintReg"],["jumpHeight", "jh"],["lootQuality", "lq"],["gatherXpBonus", "gXp"],["gatherSpeed", "gSpd"]]);
let _translations_list = [["name", "name"],["displayName", "displayName"],["tier", "tier"],["set", "set"],["sockets", "slots"],["type", "type"],["armorColor", "color"],["addedLore", "lore"],["dropType", "drop"],["quest", "quest"],["restrictions", "restrict"],["damage", "nDam"],["fireDamage", "fDam"],["waterDamage", "wDam"],["airDamage", "aDam"],["thunderDamage", "tDam"],["earthDamage", "eDam"],["attackSpeed", "atkSpd"],["health", "hp"],["fireDefense", "fDef"],["waterDefense", "wDef"],["airDefense", "aDef"],["thunderDefense", "tDef"],["earthDefense", "eDef"],["level", "lvl"],["classRequirement", "classReq"],["strength", "strReq"],["dexterity", "dexReq"],["intelligence", "intReq"],["agility", "agiReq"],["defense", "defReq"],["healthRegen", "hprPct"],["manaRegen", "mr"],["spellDamageBonus", "sdPct"],["spellElementalDamageBonus", "rSdPct"],["spellNeutralDamageBonus", "nSdPct"],["spellFireDamageBonus", "fSdPct"],["spellWaterDamageBonus", "wSdPct"],["spellAirDamageBonus", "aSdPct"],["spellThunderDamageBonus", "tSdPct"],["spellEarthDamageBonus", "eSdPct"],["mainAttackDamageBonus", "mdPct"],["mainAttackElementalDamageBonus", "rMdPct"],["mainAttackNeutralDamageBonus", "nMdPct"],["mainAttackFireDamageBonus", "fMdPct"],["mainAttackWaterDamageBonus", "wMdPct"],["mainAttackAirDamageBonus", "aMdPct"],["mainAttackThunderDamageBonus", "tMdPct"],["mainAttackEarthDamageBonus", "eMdPct"],["lifeSteal", "ls"],["manaSteal", "ms"],["xpBonus", "xpb"],["lootBonus", "lb"],["reflection", "ref"],["strengthPoints", "str"],["dexterityPoints", "dex"],["intelligencePoints", "int"],["agilityPoints", "agi"],["defensePoints", "def"],["thorns", "thorns"],["exploding", "expd"],["speed", "spd"],["attackSpeedBonus", "atkTier"],["poison", "poison"],["healthBonus", "hpBonus"],["soulPoints", "spRegen"],["emeraldStealing", "eSteal"],["healthRegenRaw", "hprRaw"],["spellDamageBonusRaw", "sdRaw"],["spellElementalDamageBonusRaw", "rSdRaw"],["spellNeutralDamageBonusRaw", "nSdRaw"],["spellFireDamageBonusRaw", "fSdRaw"],["spellWaterDamageBonusRaw", "wSdRaw"],["spellAirDamageBonusRaw", "aSdRaw"],["spellThunderDamageBonusRaw", "tSdRaw"],["spellEarthDamageBonusRaw", "eSdRaw"],["mainAttackDamageBonusRaw", "mdRaw"],["mainAttackElementalDamageBonusRaw", "rMdRaw"],["mainAttackNeutralDamageBonusRaw", "nMdRaw"],["mainAttackFireDamageBonusRaw", "fMdRaw"],["mainAttackWaterDamageBonusRaw", "wMdRaw"],["mainAttackAirDamageBonusRaw", "aMdRaw"],["mainAttackThunderDamageBonusRaw", "tMdRaw"],["mainAttackEarthDamageBonusRaw", "eMdRaw"],["fireDamageBonus", "fDamPct"],["waterDamageBonus", "wDamPct"],["airDamageBonus", "aDamPct"],["thunderDamageBonus", "tDamPct"],["earthDamageBonus", "eDamPct"],["bonusFireDefense", "fDefPct"],["bonusWaterDefense", "wDefPct"],["bonusAirDefense", "aDefPct"],["bonusThunderDefense", "tDefPct"],["bonusEarthDefense", "eDefPct"],["accessoryType", "type"],["identified", "fixID"],["skin", "skin"],["category", "category"],["spellCostPct1", "spPct1"],["spellCostRaw1", "spRaw1"],["spellCostPct2", "spPct2"],["spellCostRaw2", "spRaw2"],["spellCostPct3", "spPct3"],["spellCostRaw3", "spRaw3"],["spellCostPct4", "spPct4"],["spellCostRaw4", "spRaw4"],["sprint", "sprint"],["sprintRegen", "sprintReg"],["jumpHeight", "jh"],["lootQuality", "lq"],["gatherXpBonus", "gXp"],["gatherSpeed", "gSpd"]];
let translations = new Map(_translations_list);
//does not include damMobs (wep tomes) and defMobs (armor tomes)
for (const [k, v] of translations) {
for (const [k, v] of _translations_list) {
if (reversetranslations.has(v)) { continue; }
reversetranslations.set(v, k);
}

View file

@ -3665,7 +3665,7 @@ const atrees = {
},
{
"display_name": "Enraged Blow",
"desc": "While Corrupted, every 1% of Health you lose will increase your damage by +2% (Max 80%)",
"desc": "While Corrupted, every 1% of Health you lose will increase your damage by +1.5% (Max 80%)",
"archetype": "Fallen",
"archetype_req": 0,
"base_abil": "Bak'al's Grasp",
@ -6964,7 +6964,7 @@ const atrees = {
"effects": [
{
"type": "add_spell_prop",
"target_part": "Orb Damage",
"target_part": "Orb DPS",
"base_spell": 5,
"hits": {
"Single Orb": 1
@ -7906,7 +7906,6 @@ const atrees = {
"desc": "After leaving Vanish, summon 3 Clones that will follow you and protect you (15s Cooldown). When hit, gain a chance to take 80% less damage and lose 1 Clone.",
"archetype": "Trickster",
"archetype_req": 2,
"base_abil": "Dash",
"parents": [
"Sticky Bomb"
],
@ -7943,7 +7942,7 @@ const atrees = {
],
"dependencies": [],
"blockers": [
"Mirror Image"
"Echo"
],
"cost": 2,
"display": {
@ -8545,7 +8544,7 @@ const atrees = {
"desc": "Your Clones will mimic your spells and abilities. While they are active, deal -60% damage.",
"archetype": "Trickster",
"archetype_req": 6,
"base_abil": "Dash",
"base_abil": "Mirror Image",
"parents": [
"Sandbagging",
"Shurikens"
@ -8581,6 +8580,22 @@ const atrees = {
"behavior": "modify",
"target_part": "Slash Damage",
"multipliers": [ 690, 0, 0, 110, 0, 0 ]
},
{
"type": "stat_scaling",
"slider": true,
"slider_name": "Spell Copies",
"slider_step": 1,
"slider_max": 3,
"output": [
{
"type": "stat",
"name": "damMult.EchoCast"
}
],
"scaling": [
100
]
}
]
},
@ -9123,7 +9138,7 @@ const atrees = {
"desc": "Improve your damage while your Clones are active by +15%",
"archetype": "Trickster",
"archetype_req": 7,
"base_abil": "Dash",
"base_abil": "Mirror Image",
"parents": [
"Cheaper Smoke Bomb 2",
"Blade Fury"
@ -9173,7 +9188,7 @@ const atrees = {
},
{
"display_name": "Satsujin",
"desc": "If an enemy has 3 Marks and 70% of their health or more, your next Multihit or Main Attack will deal triple damage. (20s Cooldown, per enemy)",
"desc": "If an enemy has 3 Marks, your next Multihit or Damaging Powder Special will deal double damage. (20s Cooldown, per enemy)",
"archetype": "Shadestepper",
"archetype_req": 13,
"parents": [
@ -9210,7 +9225,7 @@ const atrees = {
},
{
"type": "stat",
"name": "damMult.Satsujin:0.Melee",
"name": "damMult.Satsujin:0.Powder Special",
"value": 100
}
]
@ -9222,7 +9237,7 @@ const atrees = {
"desc": "Summon +3 additional Clones. (+15s Cooldown)",
"archetype": "Trickster",
"archetype_req": 8,
"base_abil": "Dash",
"base_abil": "Mirror Image",
"parents": [
"Cheaper Smoke Bomb 2"
],
@ -9237,7 +9252,14 @@ const atrees = {
"icon": "node_2"
},
"properties": {},
"effects": []
"effects": [
{
"type": "stat_scaling",
"slider": true,
"slider_name": "Spell Copies",
"slider_max": 3
}
]
},
{
"display_name": "Diversion",
@ -11564,17 +11586,17 @@ const atrees = {
{
"type": "stat",
"name": "damMult.Mask",
"value": 30
"value": 35
},
{
"type": "stat",
"name": "defMult.Mask",
"value": 30
"value": 35
},
{
"type": "stat",
"name": "spd",
"value": 25
"value": 80
},
{
"type": "stat",

File diff suppressed because one or more lines are too long

View file

@ -30,7 +30,8 @@ let atree_data = null;
const wynn_version_names = [
'2.0.1.1',
'2.0.1.2',
'2.0.2.1'
'2.0.2.1',
'2.0.2.3'
];
const WYNN_VERSION_LATEST = wynn_version_names.length - 1;
// Default to the newest version.

View file

@ -117,7 +117,7 @@ class PowderSpecialDisplayNode extends ComputeNode {
const powder_specials = input_map.get('powder-specials');
const stats = input_map.get('stats');
const weapon = input_map.get('build').weapon;
displayPowderSpecials(document.getElementById("powder-special-stats"), powder_specials, stats, weapon.statMap, true);
displayPowderSpecials(document.getElementById("powder-special-stats"), powder_specials, stats, weapon.statMap);
}
}

1
js/c++/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.out

20
js/c++/Makefile Normal file
View file

@ -0,0 +1,20 @@
CXX=emcc
#CXXFLAGS=-sENVIRONMENT=web -sSINGLE_FILE -sMODULARIZE -sWASM_ASYNC_COMPILATION=0 -sALLOW_MEMORY_GROWTH -lembind --closure 1
CXXFLAGS=-sENVIRONMENT=web -sSINGLE_FILE -sMODULARIZE -sWASM_ASYNC_COMPILATION=0 -sALLOW_MEMORY_GROWTH -lembind -g
all: utils.js powders.js
%.js: %.js.out
cat $@.out $@.in > ../$@
%.js.out:
$(CXX) $(CXXFLAGS) -sEXPORT_NAME=create_$* -o $@ $^
powders.js.out: powders.cpp definitions.cpp
utils.js.out: utils.cpp utils/math_utils.cpp utils/base64.cpp utils/bitvector.cpp
.PHONY: clean
clean:
rm -f utils.js.out powders.js.out

1
js/c++/definitions.cpp Normal file
View file

@ -0,0 +1 @@
#include "definitions.h"

7
js/c++/definitions.h Normal file
View file

@ -0,0 +1,7 @@
#pragma once
#include <vector>
#include <string>
static const std::vector<std::string> skp_order { "str", "dex", "int", "def", "agi" };
static const std::vector<std::string> skp_elements {"e","t","w","f","a"};

3
js/c++/item.cpp Normal file
View file

@ -0,0 +1,3 @@
#include "item.h"

43
js/c++/item.h Normal file
View file

@ -0,0 +1,43 @@
#pragma once
#include <string>
#include <vector>
extern std::vector<std::string> numeric_IDs;
extern const std::vector<std::string> non_rolled_ids;
// TODO enum?
#define item_tier_t std::string
#define item_type_t std::string
#define item_drop_t std::string
#define item_restrict_t std::string
class Item {
std::string name;
std::string display_name;
std::string lore;
std::string color;
item_tier_t tier;
std::string set;
int slots;
item_type_t type;
std::string material;
item_drop_t drop;
std::string quest;
item_restrict_t restrict;
//let item_fields = [ "name", "displayName", "lore", "color", "tier", "set", "slots", "type", "material", "drop", "quest", "restrict", "nDam", "fDam", "wDam", "aDam", "tDam", "eDam", "atkSpd", "hp", "fDef", "wDef", "aDef", "tDef", "eDef", "lvl", "classReq", "strReq", "dexReq", "intReq", "defReq", "agiReq", "hprPct", "mr", "sdPct", "mdPct", "ls", "ms", "xpb", "lb", "ref", "str", "dex", "int", "agi", "def", "thorns", "expd", "spd", "atkTier", "poison", "hpBonus", "spRegen", "eSteal", "hprRaw", "sdRaw", "mdRaw", "fDamPct", "wDamPct", "aDamPct", "tDamPct", "eDamPct", "fDefPct", "wDefPct", "aDefPct", "tDefPct", "eDefPct", "fixID", "category", "spPct1", "spRaw1", "spPct2", "spRaw2", "spPct3", "spRaw3", "spPct4", "spRaw4", "rSdRaw", "sprint", "sprintReg", "jh", "lq", "gXp", "gSpd", "id", "majorIds", "damMobs", "defMobs",
//
//// wynn2 damages.
//"eMdPct","eMdRaw","eSdPct","eSdRaw",/*"eDamPct,"*/"eDamRaw","eDamAddMin","eDamAddMax",
//"tMdPct","tMdRaw","tSdPct","tSdRaw",/*"tDamPct,"*/"tDamRaw","tDamAddMin","tDamAddMax",
//"wMdPct","wMdRaw","wSdPct","wSdRaw",/*"wDamPct,"*/"wDamRaw","wDamAddMin","wDamAddMax",
//"fMdPct","fMdRaw","fSdPct","fSdRaw",/*"fDamPct,"*/"fDamRaw","fDamAddMin","fDamAddMax",
//"aMdPct","aMdRaw","aSdPct","aSdRaw",/*"aDamPct,"*/"aDamRaw","aDamAddMin","aDamAddMax",
//"nMdPct","nMdRaw","nSdPct","nSdRaw","nDamPct","nDamRaw","nDamAddMin","nDamAddMax", // neutral which is now an element
///*"mdPct","mdRaw","sdPct","sdRaw",*/"damPct","damRaw","damAddMin","damAddMax", // These are the old ids. Become proportional.
//"rMdPct","rMdRaw","rSdPct",/*"rSdRaw",*/"rDamPct","rDamRaw","rDamAddMin","rDamAddMax", // rainbow (the "element" of all minus neutral). rSdRaw is rainraw
//"critDamPct",
//"spPct1Final", "spPct2Final", "spPct3Final", "spPct4Final"
//];
}

36
js/c++/js_helpers.h Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#ifdef __EMSCRIPTEN__
#include <emscripten/bind.h>
using namespace emscripten;
#define MAP_TO_JS_FUNC(map_name) \
auto make_ ## map_name () { \
return map_to_js(map_name); \
}
#define VEC_TO_JS_FUNC(vec_name) \
auto make_ ## vec_name () { \
return vector_to_js(vec_name); \
}
// NOTE!!! For some stupid reason you can'd declare constants with these...
template<class Container>
val map_to_js(const Container& input_map) {
static val Map = val::global("Map");
val retval = Map.new_();
for (const auto& [k, v] : input_map) {
retval.call<val>("set", k, v);
}
return retval;
}
template<class Container>
val vector_to_js(const Container& input_vec) {
val retval = val::array();
for (const auto& x : input_vec) {
retval.call<void>("push", x);
}
return retval;
}
#endif

68
js/c++/powders.cpp Normal file
View file

@ -0,0 +1,68 @@
#ifdef __EMSCRIPTEN__
#include <emscripten/bind.h>
using namespace emscripten;
#endif
#include "powders.h"
#include "definitions.h"
#include <cstring>
const std::map<std::string, int> powder_IDs = []{
std::map<std::string, int> m;
int powder_id = 0;
for (const auto& x : skp_elements) {
for (int i = 1; i <= 6; ++i) {
m[x + std::to_string(i)] = powder_id;
m[(char)toupper(x[0]) + std::to_string(i)] = powder_id;
powder_id++;
}
}
return m;
}();
const std::map<int, std::string> powder_names = []{
std::map<int, std::string> m;
for (const auto& kv : powder_IDs) {
m[kv.second] = kv.first;
}
return m;
}();
Powder::Powder() {}
Powder::Powder(int min, int max, int conv, int defPlus, int defMinus) :
min(min), max(max), convert(conv), defPlus(defPlus), defMinus(defMinus) {}
const std::vector<Powder> powder_stats = []{
auto p = [](int a, int b, int c, int d, int e){ return Powder(a,b,c,d,e); };
return std::vector<Powder> {
p(3,6,17,2,1), p(5,8,21,4,2), p(6,10,25,8,3), p(7,10,31,14,5), p(9,11,38,22,9), p(11,13,46,30,13),
p(1,8,9,3,1), p(1,12,11,5,1), p(2,15,13,9,2), p(3,15,17,14,4), p(4,17,22,20,7), p(5,20,28,28,10),
p(3,4,13,3,1), p(4,6,15,6,1), p(5,8,17,11,2), p(6,8,21,18,4), p(7,10,26,28,7), p(9,11,32,40,10),
p(2,5,14,3,1), p(4,8,16,5,2), p(5,9,19,9,3), p(6,9,24,16,5), p(8,10,30,25,9), p(10,12,37,36,13),
p(2,6,11,3,1), p(3,10,14,6,2), p(4,11,17,10,3), p(5,11,22,16,5), p(7,12,28,24,9), p(8,14,35,34,13)
};
}();
#ifdef __EMSCRIPTEN__
#include "js_helpers.h"
#include <iostream>
MAP_TO_JS_FUNC(powder_IDs);
MAP_TO_JS_FUNC(powder_names);
VEC_TO_JS_FUNC(powder_stats);
EMSCRIPTEN_BINDINGS(powders) {
function("powderIDs", &make_powder_IDs);
function("powderNames", &make_powder_names);
value_object<Powder>("Powder")
.field("min", &Powder::min)
.field("max", &Powder::max)
.field("convert", &Powder::convert)
.field("defPlus", &Powder::defPlus)
.field("defMinus", &Powder::defMinus)
;
function("powderStats", make_powder_stats);
}
#endif

20
js/c++/powders.h Normal file
View file

@ -0,0 +1,20 @@
#pragma once
#include <map>
#include <string>
#include <vector>
extern const std::map<std::string, int> powder_IDs;
extern const std::map<int, std::string> powder_names;
struct Powder {
int min;
int max;
int convert;
int defPlus;
int defMinus;
Powder();
Powder(int min, int max, int conv, int defPlus, int defMinus);
};
extern const std::vector<Powder> powder_stats;

197
js/c++/powders.js.in Normal file
View file

@ -0,0 +1,197 @@
const _module_powders = create_powders();
const powderIDs = _module_powders.powderIDs();
const powderNames = _module_powders.powderNames();
const powderStats = _module_powders.powderStats();
//Ordering: [weapon special name, weapon special effects, armor special name, armor special effects]
class PowderSpecial{
constructor(wSpName, wSpEff, aSpName, aSpEff, cap){
this.weaponSpecialName = wSpName;
this.weaponSpecialEffects = wSpEff;
this.armorSpecialName = aSpName;
this.armorSpecialEffects = aSpEff;
this.cap = cap;
}
}
function _ps(a,b,c,d,e) { return new PowderSpecial(a,b,c,d,e); } //bruh moment
let powderSpecialStats = [
_ps("Quake",new Map([["Radius",[4.5,5,5.5,6,6.5]], ["Damage",[240,280,320,360,400]] ]),"Rage",new Map([ ["Damage", [0.2,0.4,0.6,0.8,1.0]],["Description", "% " + "\u2764" + " Missing below 75%"] ]),300), //e
_ps("Chain Lightning",new Map([ ["Chains", [5,6,7,8,9]], ["Damage", [200,225,250,275,300]] ]),"Kill Streak",new Map([ ["Damage", [3,4.5,6,7.5,9]],["Duration", [5,5,5,5,5]],["Description", "Mob Killed"] ]),150), //t
_ps("Curse",new Map([ ["Duration", [4,4,4,4,4]],["Damage Boost", [10,15,20,25,30]] ]),"Concentration",new Map([ ["Damage", [0.05,0.1,0.15,0.2,0.25]],["Duration",[1,1,1,1,1]],["Description", "Mana Used"] ]),100), //w
_ps("Courage",new Map([ ["Duration", [4,4,4,4,4]],["Damage", [60, 70, 80, 90, 100]],["Damage Boost", [10,12.5,15,17.5,20]] ]),"Endurance",new Map([ ["Damage", [2,3,4,5,6]],["Duration", [8,8,8,8,8]],["Description", "Hit Taken"] ]),100), //f
_ps("Wind Prison",new Map([ ["Duration", [3,3.5,4,4.5,5]],["Damage Boost", [100,125,150,175,200]],["Knockback", [8,12,16,20,24]] ]),"Dodge",new Map([ ["Damage",[2,3,4,5,6]],["Duration",[2,3,4,5,6]],["Description","Near Mobs"] ]),150) //a
];
/**
* Apply armor powders.
* Encoding shortcut assumes that all powders give +def to one element
* and -def to the element "behind" it in cycle ETWFA, which is true
* as of now and unlikely to change in the near future.
*/
function applyArmorPowders(expandedItem) {
const powders = expandedItem.get('powders');
for(const id of powders){
let powder = powderStats[id];
let name = powderNames.get(id).charAt(0);
let prevName = skp_elements[(skp_elements.indexOf(name) + 4 )% 5];
expandedItem.set(name+"Def", (expandedItem.get(name+"Def") || 0) + powder["defPlus"]);
expandedItem.set(prevName+"Def", (expandedItem.get(prevName+"Def") || 0) - powder["defMinus"]);
}
}
const damage_keys = [ "nDam_", "eDam_", "tDam_", "wDam_", "fDam_", "aDam_" ];
const damage_present_key = 'damagePresent';
/**
* Apply weapon powders. MUTATES THE ITEM!
* Adds entries for `damage_keys` and `damage_present_key`
* For normal items, `damage_keys` is 6x2 list (elem: [min, max])
* For crafted items, `damage_keys` is 6x2x2 list (elem: [minroll: [min, max], maxroll: [min, max]])
*/
function apply_weapon_powders(item) {
let present;
if (item.get("tier") !== "Crafted") {
let weapon_result = calc_weapon_powder(item);
let damages = weapon_result[0];
present = weapon_result[1];
for (const i in damage_keys) {
item.set(damage_keys[i], damages[i]);
}
} else {
let base_low = [item.get("nDamBaseLow"),item.get("eDamBaseLow"),item.get("tDamBaseLow"),item.get("wDamBaseLow"),item.get("fDamBaseLow"),item.get("aDamBaseLow")];
let results_low = calc_weapon_powder(item, base_low);
let damage_low = results_low[0];
let base_high = [item.get("nDamBaseHigh"),item.get("eDamBaseHigh"),item.get("tDamBaseHigh"),item.get("wDamBaseHigh"),item.get("fDamBaseHigh"),item.get("aDamBaseHigh")];
let results_high = calc_weapon_powder(item, base_high);
let damage_high = results_high[0];
present = results_high[1];
for (const i in damage_keys) {
item.set(damage_keys[i], [damage_low[i], damage_high[i]]);
}
}
item.set(damage_present_key, present);
}
/**
* Calculate weapon damage from powder.
*
* Params:
* weapon: Weapon to apply powder to
* damageBases: used by crafted
*
* Return:
* [damages, damage_present]
*/
function calc_weapon_powder(weapon, damageBases) {
let powders = weapon.get("powders").slice();
// Array of neutral + ewtfa damages. Each entry is a pair (min, max).
let damages = [
weapon.get('nDam').split('-').map(Number),
weapon.get('eDam').split('-').map(Number),
weapon.get('tDam').split('-').map(Number),
weapon.get('wDam').split('-').map(Number),
weapon.get('fDam').split('-').map(Number),
weapon.get('aDam').split('-').map(Number)
];
// Applying spell conversions
let neutralBase = damages[0].slice();
let neutralRemainingRaw = damages[0].slice();
//powder application for custom crafted weapons is inherently fucked because there is no base. Unsure what to do.
//Powder application for Crafted weapons - this implementation is RIGHT YEAAAAAAAAA
//1st round - apply each as ingred, 2nd round - apply as normal
if (weapon.get("tier") === "Crafted" && !weapon.get("custom")) {
for (const p of powders.concat(weapon.get("ingredPowders"))) {
let powder = powderStats[p]; //use min, max, and convert
let element = Math.floor((p+0.01)/6); //[0,4], the +0.01 attempts to prevent division error
let diff = Math.floor(damageBases[0] * powder.convert/100);
damageBases[0] -= diff;
damageBases[element+1] += diff + Math.floor( (powder.min + powder.max) / 2 );
}
//update all damages
for (let i = 0; i < damages.length; i++) {
damages[i] = [Math.floor(damageBases[i] * 0.9), Math.floor(damageBases[i] * 1.1)];
}
neutralRemainingRaw = damages[0].slice();
neutralBase = damages[0].slice();
}
//apply powders to weapon (1.21 fked implementation)
let powder_apply_order = [];
let powder_apply_map = new Map();
for (const powderID of powders) {
const powder = powderStats[powderID];
// Bitwise to force conversion to integer (integer division).
const element = (powderID/6) | 0;
const conversion_ratio = powder.convert/100;
if (powder_apply_map.has(element)) {
let apply_info = powder_apply_map.get(element);
apply_info.conv += conversion_ratio;
apply_info.min += powder.min;
apply_info.max += powder.max;
}
else {
let apply_info = {
conv: conversion_ratio,
min: powder.min,
max: powder.max
};
powder_apply_order.push(element);
powder_apply_map.set(element, apply_info);
}
}
for (const element of powder_apply_order) {
const apply_info = powder_apply_map.get(element);
const conversion_ratio = apply_info.conv;
const min_diff = Math.min(neutralRemainingRaw[0], conversion_ratio * neutralRemainingRaw[0]);
const max_diff = Math.min(neutralRemainingRaw[1], conversion_ratio * neutralRemainingRaw[1]);
neutralRemainingRaw[0] -= min_diff;
neutralRemainingRaw[1] -= max_diff;
damages[element+1][0] += min_diff;
damages[element+1][1] += max_diff;
damages[element+1][0] += apply_info.min;
damages[element+1][1] += apply_info.max;
}
/*
//apply powders to weapon
for (const powderID of powders) {
const powder = powderStats[powderID];
// Bitwise to force conversion to integer (integer division).
const element = (powderID/6) | 0;
let conversionRatio = powder.convert/100;
if (neutralRemainingRaw[1] > 0) {
let min_diff = Math.min(neutralRemainingRaw[0], conversionRatio * neutralBase[0]);
let max_diff = Math.min(neutralRemainingRaw[1], conversionRatio * neutralBase[1]);
damages[element+1][0] = Math.floor(round_near(damages[element+1][0] + min_diff));
damages[element+1][1] = Math.floor(round_near(damages[element+1][1] + max_diff));
neutralRemainingRaw[0] = Math.floor(round_near(neutralRemainingRaw[0] - min_diff));
neutralRemainingRaw[1] = Math.floor(round_near(neutralRemainingRaw[1] - max_diff));
//damages[element+1][0] += min_diff;
//damages[element+1][1] += max_diff;
//neutralRemainingRaw[0] -= min_diff;
//neutralRemainingRaw[1] -= max_diff;
}
damages[element+1][0] += powder.min;
damages[element+1][1] += powder.max;
}
*/
// The ordering of these two blocks decides whether neutral is present when converted away or not.
damages[0] = neutralRemainingRaw;
// The ordering of these two blocks decides whether neutral is present when converted away or not.
let present_elements = []
for (const damage of damages) {
present_elements.push(damage[1] > 0);
}
return [damages, present_elements];
}

95
js/c++/utils.cpp Normal file
View file

@ -0,0 +1,95 @@
#ifdef __EMSCRIPTEN__
#include <emscripten/bind.h>
#endif
#include "utils.h"
#include "utils/math_utils.h"
#include "utils/base64.h"
#include "utils/bitvector.h"
#include <algorithm>
#include <memory>
#include <vector>
namespace utils {
// Permutations in js reference (also cool algorithm):
// Uses Heap's method: https://stackoverflow.com/a/37580979
// https://en.wikipedia.org/wiki/Heap%27s_algorithm
// Note: different from old wynnbuilder permutation generator (https://stackoverflow.com/a/41068709)
template<typename T>
std::vector<std::vector<T>> perm(std::vector<T> a) {
std::vector<std::vector<T>> result;
result.push_back(a); // copy assignment
int l = a.size(); // haha signed int
std::vector<int> c(l, 0);
int i = 1;
while (i < l) {
if (c[i] < i) {
if (i % 2) {
// odd.
std::iter_swap(a.begin()+c[i], a.begin()+i);
}
else {
// even.
std::iter_swap(a.begin(), a.begin()+i);
}
result.push_back(a);
c[i] += 1;
i = 1;
}
else {
c[i] = 0;
i++;
}
}
return result;
}
#ifdef __EMSCRIPTEN__
using namespace emscripten;
val __perm_wrap(val a) {
const size_t l = a["length"].as<size_t>();
std::vector<val> things;
for (size_t i = 0; i < l; ++i) {
things.push_back(a[i]);
}
std::vector<std::vector<val>> res = perm(things);
val return_array = val::array();
for (auto it = res.begin(); it != res.end(); ++it) {
auto& subarray = *it;
val return_subarray = val::array();
for (auto it2 = subarray.begin(); it2 != subarray.end(); ++it2) {
return_subarray.call<void>("push", *it2);
}
return_array.call<void>("push", return_subarray);
}
return return_array;
}
EMSCRIPTEN_BINDINGS(utils) {
function("clamp", &clamp);
function("round_near", &round_near);
function("b64_fromIntV", &Base64::fromIntV);
function("b64_fromIntN", &Base64::fromIntN);
function("b64_toInt", &Base64::toInt);
function("b64_toIntSigned", &Base64::toIntSigned);
function("perm", &__perm_wrap);
class_<BitVector>("BitVector")
.constructor<std::string>()
.constructor<size_t, size_t>()
.property("length", &BitVector::length)
.function("read_bit", &BitVector::read_bit)
.function("slice", &BitVector::slice)
.function("set_bit", &BitVector::set_bit)
.function("clear_bit", &BitVector::clear_bit)
.function("toB64", &BitVector::toB64)
.function("toString", &BitVector::toString)
.function("toStringR", &BitVector::toStringR)
.function("append", select_overload<void(std::string)>(&BitVector::append))
.function("append", select_overload<void(bitvec_data_t, size_t)>(&BitVector::append))
;
}
#endif
} // namespace utils

13
js/c++/utils.h Normal file
View file

@ -0,0 +1,13 @@
#pragma once
#include <vector>
/**
* Generate all permutations of a vector.
*
* @param a: vector containing the elements to permute.
*
* @return a vector with all permutations of `a`.
*/
template<typename T>
std::vector<std::vector<T>> perm(std::vector<T> a);

699
js/c++/utils.js.in Normal file
View file

@ -0,0 +1,699 @@
let getUrl = window.location;
const url_base = getUrl.protocol + "//" + getUrl.host + "/" + getUrl.pathname.split('/')[1];
const _module_utils = create_utils();
const clamp = _module_utils.clamp;
const round_near = _module_utils.round_near;
const log = _module_utils.log;
const Base64 = {
fromIntV: _module_utils.b64_fromIntV,
fromIntN: _module_utils.b64_fromIntN,
toInt: _module_utils.b64_toInt,
toIntSigned: _module_utils.b64_toIntSigned
};
const BitVector = _module_utils.BitVector;
// const perm = _module_utils.perm; way too garbage to use... we supply JS perm.
// Permutations in js reference (also cool algorithm):
// https://stackoverflow.com/a/41068709
function perm(a){
if (a.length == 0) return [[]];
var r = [[a[0]]],
t = [],
s = [];
if (a.length == 1) return r;
for (var i = 1, la = a.length; i < la; i++){
for (var j = 0, lr = r.length; j < lr; j++){
r[j].push(a[i]);
t.push(r[j]);
for(var k = 1, lrj = r[j].length; k < lrj; k++){
for (var l = 0; l < lrj; l++) s[l] = r[j][(k+l)%lrj];
t[t.length] = s;
s = [];
}
}
r = t;
t = [];
}
return r;
}
// huge regex :doom:
// replace with navigator.userAgentData.mobile once it has wider support
const isMobile = function() {
let check = false;
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera);
return check;
}(); // runs immediately, so mobileCheck is a boolean not a function
const zip2 = (a, b) => a.map((k, i) => [k, b[i]]);
const zip3 = (a, b, c) => a.map((k, i) => [k, b[i], c[i]]);
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function setText(id, text) {
document.getElementById(id).textContent = text;
}
function setHTML(id, html) {
document.getElementById(id).innerHTML = html;
}
function setValue(id, value) {
let el = document.getElementById(id);
if (el == null) {
console.log("WARN tried to set text value of id {"+id+"} to ["+value+"] but did not exist!");
return;
}
el.value = value;
el.dispatchEvent(new Event("change"));
}
function getValue(id) {
return document.getElementById(id).value;
}
/*
Turns a raw stat and a % stat into a final stat on the basis that - raw and >= 100% becomes 0 and + raw and <=-100% becomes negative.
Pct would be 0.80 for 80%, -1.20 for 120%, etc
Example Outputs:
raw: -100
pct: +0.20, output = -80
pct: +1.20, output = 0
pct: -0.20, output = -120
pct: -1.20, output = -220
raw: +100
pct: +0.20, output = 120
pct: +1.20, output = 220
pct: -0.20, output = 80
pct: -1.20, output = -20
*/
function rawToPct(raw, pct){
final = 0;
if (raw < 0){
final = (Math.min(0, raw - (raw * pct) ));
}else if(raw > 0){
final = raw + (raw * pct);
}else{ //do nothing - final's already 0
}
return final;
}
/*
* Clipboard utilities
* From: https://stackoverflow.com/a/30810322
*/
function fallbackCopyTextToClipboard(text) {
var textArea = document.createElement("textarea");
//
// *** This styling is an extra step which is likely not required. ***
//
// Why is it here? To ensure:
// 1. the element is able to have focus and selection.
// 2. if the element was to flash render it has minimal visual impact.
// 3. less flakyness with selection and copying which **might** occur if
// the textarea element is not visible.
//
// The likelihood is the element won't even render, not even a
// flash, so some of these are just precautions. However in
// Internet Explorer the element is visible whilst the popup
// box asking the user for permission for the web page to
// copy to the clipboard.
//
// Place in the top-left corner of screen regardless of scroll position.
textArea.style.position = 'fixed';
textArea.style.top = 0;
textArea.style.left = 0;
// Ensure it has a small width and height. Setting to 1px / 1em
// doesn't work as this gives a negative w/h on some browsers.
textArea.style.width = '2em';
textArea.style.height = '2em';
// We don't need padding, reducing the size if it does flash render.
textArea.style.padding = 0;
// Clean up any borders.
textArea.style.border = 'none';
textArea.style.outline = 'none';
textArea.style.boxShadow = 'none';
// Avoid flash of the white box if rendered for any reason.
textArea.style.background = 'transparent';
textArea.value = text;
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
console.log('Copying text command was ' + msg);
} catch (err) {
console.log('Oops, unable to copy');
}
document.body.removeChild(textArea);
}
function copyTextToClipboard(text) {
if (!navigator.clipboard) {
fallbackCopyTextToClipboard(text);
return;
}
navigator.clipboard.writeText(text).then(function() {
console.log('Async: Copying to clipboard was successful!');
}, function(err) {
console.error('Async: Could not copy text: ', err);
});
}
/**
* Generates a random color using the #(R)(G)(B) format.
*/
function randomColor() {
return '#' + Math.round(Math.random() * 0xFFFFFF).toString(16);
}
/**
* Generates a random color, but lightning must be relatively high (>0.5).
*
* @returns a random color in RGB 6-bit form.
*/
function randomColorLight() {
return randomColorHSL([0,1],[0,1],[0.5,1]);
}
/** Generates a random color given HSL restrictions.
*
* @returns a random color in RGB 6-bit form.
*/
function randomColorHSL(h,s,l) {
var letters = '0123456789abcdef';
let h_var = h[0] + (h[1]-h[0])*Math.random(); //hue
let s_var = s[0] + (s[1]-s[0])*Math.random(); //saturation
let l_var = l[0] + (l[1]-l[0])*Math.random(); //lightness
let rgb = hslToRgb(h_var,s_var,l_var);
let color = "#";
for (const c of rgb) {
color += letters[Math.floor(c/16)] + letters[c%16];
}
return color;
}
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255]. Not written by wynnbuilder devs.
*
* @param {number} h The hue
* @param {number} s The saturation
* @param {number} l The lightness
* @return {Array} The RGB representation
*/
function hslToRgb(h, s, l){
var r, g, b;
if(s == 0){
r = g = b = l; // achromatic
}else{
var hue2rgb = function hue2rgb(p, q, t){
if(t < 0) t += 1;
if(t > 1) t -= 1;
if(t < 1/6) return p + (q - p) * 6 * t;
if(t < 1/2) return q;
if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
}
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
/** Creates a tooltip.
*
* @param {DOM Element} elem - the element to make a tooltip
* @param {String} element_type - the HTML element type that the tooltiptext should be.
* @param {String} tooltiptext - the text to display in the tooltip.
* @param {DOM Element} parent - the parent elem. optional.
* @param {String[]} classList - a list of classes to add to the element.
*/
function createTooltip(elem, element_type, tooltiptext, parent, classList) {
elem = document.createElement(element_type);
elem.classList.add("tooltiptext");
if (tooltiptext.includes("\n")) {
let texts = tooltiptext.split("\n");
for (const t of texts) {
let child = document.createElement(element_type);
child.textContent = t;
elem.appendChild(child);
}
} else {
elem.textContent = tooltiptext;
}
for (const c of classList) {
elem.classList.add(c);
}
if (parent) {
parent.classList.add("tooltip");
parent.appendChild(elem);
}
return elem;
}
/** A generic function that toggles the on and off state of a button.
*
* @param {String} button_id - the id name of the button.
*/
function toggleButton(button_id) {
let elem = document.getElementById(button_id);
if (elem.tagName === "BUTTON") {
if (elem.classList.contains("toggleOn")) { //toggle the pressed button off
elem.classList.remove("toggleOn");
} else {
elem.classList.add("toggleOn");
}
}
}
/**
* If the input object is undefined, make it "match" the target type
* with default value (0 or empty str).
*/
function matchType(object, target) {
if (typeof object === 'undefined') {
switch (target) {
case 'string':
return "";
case 'number':
return 0;
case 'undefined':
return undefined;
default:
throw new Error(`Incomparable type ${target}`);
}
}
return object;
}
/** A utility function that reloads the page forcefully.
*
*/
async function hardReload() {
//https://gist.github.com/rmehner/b9a41d9f659c9b1c3340
try {
const dbs = await window.indexedDB.databases();
await dbs.forEach(db => { window.indexedDB.deleteDatabase(db.name) });
} catch (error) {
// Hacky patch for firefox...
console.log(error);
const db_names = ['item_db', 'ing_db', 'map_db', 'tome_db'];
await db_names.forEach(db => { window.indexedDB.deleteDatabase(db) });
}
location.reload(true);
}
function capitalizeFirst(str) {
return str[0].toUpperCase() + str.substring(1);
}
/** https://stackoverflow.com/questions/16839698/jquery-getscript-alternative-in-native-javascript
* If we ever want to write something that needs to import other js files
*/
const getScript = url => new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.async = true;
script.onerror = reject;
script.onload = script.onreadystatechange = function () {
const loadState = this.readyState;
if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
script.onload = script.onreadystatechange = null;
resolve();
}
document.head.appendChild(script);
})
/*
GENERIC TEST FUNCTIONS
*/
/** The generic assert function. Fails on all "false-y" values. Useful for non-object equality checks, boolean value checks, and existence checks.
*
* @param {*} arg - argument to assert.
* @param {String} msg - the error message to throw.
*/
function assert(arg, msg) {
if (!arg) {
throw new Error(msg ? msg : "Assert failed.");
}
}
/** Asserts object equality of the 2 parameters. For loose and strict asserts, use assert().
*
* @param {*} arg1 - first argument to compare.
* @param {*} arg2 - second argument to compare.
* @param {String} msg - the error message to throw.
*/
function assert_equals(arg1, arg2, msg) {
if (!Object.is(arg1, arg2)) {
throw new Error(msg ? msg : "Assert Equals failed. " + arg1 + " is not " + arg2 + ".");
}
}
/** Asserts object inequality of the 2 parameters. For loose and strict asserts, use assert().
*
* @param {*} arg1 - first argument to compare.
* @param {*} arg2 - second argument to compare.
* @param {String} msg - the error message to throw.
*/
function assert_not_equals(arg1, arg2, msg) {
if (Object.is(arg1, arg2)) {
throw new Error(msg ? msg : "Assert Not Equals failed. " + arg1 + " is " + arg2 + ".");
}
}
/** Asserts proximity between 2 arguments. Should be used for any floating point datatype.
*
* @param {*} arg1 - first argument to compare.
* @param {*} arg2 - second argument to compare.
* @param {Number} epsilon - the margin of error (<= del difference is ok). Defaults to -1E5.
* @param {String} msg - the error message to throw.
*/
function assert_near(arg1, arg2, epsilon = 1E-5, msg) {
if (Math.abs(arg1 - arg2) > epsilon) {
throw new Error(msg ? msg : "Assert Near failed. " + arg1 + " is not within " + epsilon + " of " + arg2 + ".");
}
}
/** Asserts that the input argument is null.
*
* @param {*} arg - the argument to test for null.
* @param {String} msg - the error message to throw.
*/
function assert_null(arg, msg) {
if (arg !== null) {
throw new Error(msg ? msg : "Assert Near failed. " + arg + " is not null.");
}
}
/** Asserts that the input argument is undefined.
*
* @param {*} arg - the argument to test for undefined.
* @param {String} msg - the error message to throw.
*/
function assert_undefined(arg, msg) {
if (arg !== undefined) {
throw new Error(msg ? msg : "Assert Near failed. " + arg + " is not undefined.");
}
}
/** Asserts that there is an error when a callback function is run.
*
* @param {Function} func_binding - a function binding to run. Can be passed in with func.bind(null, arg1, ..., argn)
* @param {String} msg - the error message to throw.
*/
function assert_error(func_binding, msg) {
try {
func_binding();
} catch (err) {
return;
}
throw new Error(msg ? msg : "Function didn't throw an error.");
}
/**
* Deep copy object/array of basic types.
*/
function deepcopy(obj, refs=undefined) {
if (refs === undefined) {
refs = new Map();
}
if (typeof(obj) !== 'object' || obj === null) { // null or value type
return obj;
}
let ret = Array.isArray(obj) ? [] : {};
for (let key in obj) {
let val;
try {
val = obj[key];
} catch (exc) {
console.trace();
val = undefined;
}
if (typeof(obj) === 'object') {
if (refs.has(val)) {
ret[key] = refs.get(val);
}
else {
refs.set(val, val);
ret[key] = deepcopy(val, refs);
}
}
else {
ret[key] = val;
}
}
return ret;
}
/**
*
*/
function gen_slider_labeled({label_name, label_classlist = [], min = 0, max = 100, step = 1, default_val = min, id = undefined, color = "#FFFFFF", classlist = []}) {
let slider_container = document.createElement("div");
slider_container.classList.add("col");
let buf_col = document.createElement("div");
let label = document.createElement("div");
label.classList.add(...label_classlist);
label.textContent = label_name + ": " + default_val;
let slider = gen_slider(min, max, step, default_val, id, color, classlist, label);
//we set IDs here because the slider's id is potentially only meaningful after gen_slider() is called
label.id = slider.id + "_label";
slider_container.id = slider.id + "-container";
buf_col.append(slider, label);
slider_container.appendChild(buf_col);
return slider_container;
}
/** Creates a slider input (input type = range) given styling parameters
*
* @param {Number | String} min - The minimum value for the slider. defaults to 0
* @param {Number | String} max - The maximum value for the slider. defaults to 100
* @param {Number | String} step - The granularity between possible values. defaults to 1
* @param {Number | String} default_val - The default value to set the slider to.
* @param {String} id - The element ID to use for the slider. defaults to the current date time
* @param {String} color - The hex color to use for the slider. Needs the # character.
* @param {Array<String>} classlist - A list of classes to add to the slider.
* @returns
*/
function gen_slider(min = 0, max = 100, step = 1, default_val = min, id = undefined, color = "#FFFFFF", classlist = [], label = undefined) {
//simple attribute vals
let slider = document.createElement("input");
slider.type = "range";
slider.min = min;
slider.max = max;
slider.step = step;
slider.value = default_val;
slider.autocomplete = "off";
if (id) {
if (document.getElementById(id)) {
throw new Error("ID " + id + " already exists within the DOM.")
} else {
slider.id = id;
}
} else {
slider.id = new Date().toLocaleTimeString();
}
slider.color = color;
slider.classList.add(...classlist); //special spread operator -
//necessary for display purposes
slider.style.webkitAppearance = "none";
slider.style.borderRadius = "30px";
slider.style.height = "0.5rem";
slider.classList.add("px-0", "slider");
//set up recoloring
slider.addEventListener("change", function(e) {
recolor_slider(slider, label);
});
//do recoloring for the default val
let pct = Math.round(100 * (parseInt(slider.value) - parseInt(slider.min)) / (parseInt(slider.max) - parseInt(slider.min)));
slider.style.background = `rgba(0, 0, 0, 0) linear-gradient(to right, ${color}, ${color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`;
//return slider
return slider;
}
/** Recolors a slider. If the corresponding label exists, also update that.
*
* @param {slider} slider - the slider element
* @param {label} label - the label element
*/
function recolor_slider(slider, label) {
let color = slider.color;
let pct = Math.round(100 * (parseInt(slider.value) - parseInt(slider.min)) / (parseInt(slider.max) - parseInt(slider.min)));
slider.style.background = `rgba(0, 0, 0, 0) linear-gradient(to right, ${color}, ${color} ${pct}%, #AAAAAA ${pct}%, #AAAAAA 100%)`;
if (label) {
//convention is that the number goes at the end... I parse by separating it at ':'
label.textContent = label.textContent.split(":")[0] + ": " + slider.value;
}
}
/**
* Shorthand for making an element in html.
*
* @param {String} type : type of element
* @param {List[String]} classlist : css classes for element
* @param {Map[String, String]} args : Properties for the element
*/
function make_elem(type, classlist = [], args = {}) {
const ret_elem = document.createElement(type);
ret_elem.classList.add(...classlist);
for (const i in args) {
if (i === 'style') {
const style_obj = args[i];
if (typeof style_obj === 'string' || style_obj instanceof String) {
ret_elem.style = style_obj;
continue;
}
for (const k in style_obj) {
ret_elem.style[k] = style_obj[k];
}
continue;
}
ret_elem[i] = args[i];
}
return ret_elem;
}
/**
* Nodes must have:
* node: {
* parents: List[node]
* children: List[node]
* }
*
* This function will define: "visited, assigned, scc" properties
* Assuming a connected graph. (only one root)
*/
function make_SCC_graph(root_node, nodes) {
for (const node of nodes) {
node.visited = false;
node.assigned = false;
node.scc = null;
}
const res = []
/*
* SCC graph construction.
* https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm
*/
function visit(u, res) {
if (u.visited) { return; }
u.visited = true;
for (const child of u.children) {
if (!child.visited) { visit(child, res); }
}
res.push(u);
}
visit(root_node, res);
res.reverse();
const sccs = [];
function assign(node, cur_scc) {
if (node.assigned) { return; }
cur_scc.nodes.push(node);
node.scc = cur_scc;
node.assigned = true;
for (const parent of node.parents) {
assign(parent, cur_scc);
}
}
for (const node of res) {
if (node.assigned) { continue; }
const cur_scc = {
nodes: [],
children: new Set(),
parents: new Set()
};
assign(node, cur_scc);
sccs.push(cur_scc);
}
for (const scc of sccs) {
for (const node of scc.nodes) {
for (const child of node.children) {
scc.children.add(child.scc);
}
for (const parent of node.parents) {
scc.parents.add(parent.scc);
}
}
}
return sccs;
}
// Toggles display of a certain element, given the ID.
function toggle_tab(tab) {
let elem = document.getElementById(tab);
if (elem.style.display == "none") {
elem.style.display = "";
} else {
elem.style.display = "none";
}
}
// Toggle display of a certain tab, in a group of tabs, given the target tab ID, and a list of associated tabs.
// Also sets visual display of an element with ID of target + "-btn" to selected.
function show_tab(target, tabs) {
//hide all tabs, then show the tab of the div clicked and highlight the correct button
for (const i in tabs) {
document.getElementById(tabs[i]).style.display = "none";
document.getElementById(tabs[i] + "-btn").classList.remove("selected-btn");
}
document.getElementById(target).style.display = "";
document.getElementById(target + "-btn").classList.add("selected-btn");
}
// mobile navbar appearance control
let scrollPos = 0
if (screen.width < 992) {
document.addEventListener('scroll', (e) => {
if (document.documentElement.scrollTop - scrollPos > 20) {
document.getElementById("mobile-navbar").style.display = "none";
document.getElementById("mobile-navbar-dropdown").style.display = "none";
} else if (document.documentElement.scrollTop - scrollPos < -50 || scrollPos < 70) {
document.getElementById("mobile-navbar").style.display = "";
}
scrollPos = document.documentElement.scrollTop;
});
}

42
js/c++/utils/base64.cpp Normal file
View file

@ -0,0 +1,42 @@
#include "base64.h"
namespace Base64 {
std::string fromIntV(unsigned int int32) {
std::string result;
while (true) {
result = digits[int32 & 0x3f] + result;
int32 >>= 6;
if (int32 == 0) { break; }
}
return result;
}
std::string fromIntN(int int32, int n) {
std::string result;
for (int i = 0; i < n; ++i) {
result = digits[int32 & 0x3f] + result;
int32 >>= 6;
}
return result;
}
unsigned int toInt(std::string digits) {
unsigned int result = 0;
for (size_t i = 0; i < digits.length(); ++i) {
result = (result << 6) + digitsMap.find(digits[i])->second;
}
return result;
}
int toIntSigned(std::string digits) {
int result = 0;
if (digits.length() > 0 && digitsMap.find(digits[0])->second & 0x20) {
result = -1;
}
for (size_t i = 0; i < digits.length(); ++i) {
result = (result << 6) + digitsMap.find(digits[i])->second;
}
return result;
}
}

32
js/c++/utils/base64.h Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include <string>
#include <map>
// Base 64 encoding tools
// https://stackoverflow.com/a/27696695
// Modified for fixed precision
// Base64.fromInt(-2147483648); // gives "200000"
// Base64.toInt("200000"); // gives -2147483648
namespace Base64 {
const std::string digits =
// 0 8 16 24 32 40 48 56 63
// v v v v v v v v v
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+-";
static const std::map<char, size_t> digitsMap = []{
std::map<char, size_t> m;
for (size_t i = 0; i < digits.length(); ++i) {
m[digits[i]] = i;
}
return m;
}();
std::string fromIntV(unsigned int i);
std::string fromIntN(int i, int n);
unsigned int toInt(std::string digitsStr);
int toIntSigned(std::string digitsStr);
}

201
js/c++/utils/bitvector.cpp Normal file
View file

@ -0,0 +1,201 @@
#include "bitvector.h"
#include "base64.h"
#include <algorithm>
#include <stdexcept>
#include <sstream>
BitVector::BitVector() {};
BitVector::BitVector(const BitVector& other) : data(other.data), _length(other.length()) {};
BitVector::BitVector(const std::string b64_data) {
_length = b64_data.length() * 6;
data.reserve(_length/bitvec_data_s + 1);
bitvec_data_t scratch = 0;
size_t bitvec_index = 0;
for (size_t i = 0; i < b64_data.length(); ++i) {
size_t char_num = Base64::digitsMap.find(b64_data[i])->second;
unsigned int pre_pos = bitvec_index % bitvec_data_s;
scratch |= char_num << pre_pos;
bitvec_index += 6; // b64 is 6 bits per character.
unsigned int post_pos = bitvec_index % bitvec_data_s;
if (post_pos < pre_pos) { //we have to have filled up the integer
data.push_back(scratch);
scratch = (char_num >> (6 - post_pos));
}
if (i == b64_data.length()-1 && post_pos != 0) {
data.push_back(scratch);
}
}
}
BitVector::BitVector(bitvec_data_t num, size_t length) {
if (length < 0) {
throw std::range_error("BitVector must have nonnegative length.");
}
if (length > 0) {
data.push_back(num);
}
this->_length = length;
}
/** Return value of bit at index idx.
*
* @param {Number} idx - The index to read
*
* @returns The bit value at position idx
*/
bool BitVector::read_bit(size_t idx) const {
if (idx < 0 || idx >= length()) {
std::stringstream ss;
ss << "Cannot read bit outside the range of the BitVector. (" << idx << " > " << length() << ")";
throw std::range_error(ss.str());
}
return (data[idx / bitvec_data_s] & (1 << (idx % bitvec_data_s))) == 0 ? 0 : 1;
}
/** Returns an integer value (if possible) made from the range of bits [start, end). Undefined behavior if the range to read is too big.
*
* @param {Number} start - The index to start slicing from. Inclusive.
* @param {Number} end - The index to end slicing at. Exclusive.
*
* @returns An integer representation of the sliced bits.
*/
bitvec_data_t BitVector::slice(size_t start, size_t end) const {
if (end < start) {
throw std::range_error("Cannot slice a range where the end is before the start.");
} else if (end == start) {
return 0;
} else if (end - start > bitvec_data_s) {
//requesting a slice of longer than the size of a single data element (safe integer "length")
std::stringstream ss;
ss << "Cannot slice a range of longer than " << bitvec_data_s << " bits (unsafe to store in an integer).";
throw std::range_error(ss.str());
}
bitvec_data_t res = 0;
if ((end-1) / bitvec_data_s == start / bitvec_data_s) {
//the range is within 1 uint32 section - do some relatively fast bit twiddling
//res = (this.bits[Math.floor(start / 32)] & ~((((~0) << ((end - 1))) << 1) | ~((~0) << (start)))) >>> (start % 32);
bitvec_data_t mask = (~(((~0) << ((end - 1) % bitvec_data_s + 1)))) & ((~0) << (start % bitvec_data_s));
res = (data[start / bitvec_data_s] & mask) >> (start % bitvec_data_s);
}
else {
//the number of bits in the uint32s
//let start_pos = (start % 32);
//let int_idx = Math.floor(start/32);
//res = (this.bits[int_idx] & ((~0) << (start))) >>> (start_pos);
//res |= (this.bits[int_idx + 1] & ~((~0) << (end))) << (32 - start_pos);
unsigned int start_pos = start % bitvec_data_s;
unsigned int int_idx = start / bitvec_data_s;
res = (data[int_idx] & ((~0) << start_pos)) >> start_pos;
// IMPORTANT: (end % bitvec_data_s) is never zero.
res |= (data[int_idx + 1] & ~((~0) << (end % bitvec_data_s))) << (bitvec_data_s - start_pos);
}
return res;
// General code - slow
// for (let i = start; i < end; i++) {
// res |= (get_bit(i) << (i - start));
// }
}
/** Assign bit at index idx to 1.
*
* @param {Number} idx - The index to set.
*/
void BitVector::set_bit(size_t idx) {
if (idx < 0 || idx >= length()) {
throw std::range_error("Cannot set bit outside the range of the BitVector.");
}
data[idx / bitvec_data_s] |= (1 << (idx % bitvec_data_s));
}
/** Assign bit at index idx to 0.
*
* @param {Number} idx - The index to clear.
*/
void BitVector::clear_bit(size_t idx) {
if (idx < 0 || idx >= length()) {
throw std::range_error("Cannot clear bit outside the range of the BitVector.");
}
data[idx / bitvec_data_s] &= ~(1 << (idx % bitvec_data_s));
}
/** Creates a string version of the bit vector in B64. Does not keep the order of elements a sensible human readable format.
*
* @returns A b64 string representation of the BitVector.
*/
std::string BitVector::toB64() const {
if (length() == 0) {
return "";
}
std::stringstream b64_str;
size_t i = 0;
while (i < length()) {
b64_str << Base64::fromIntN(this->slice(i, i + 6), 1);
i += 6;
}
return b64_str.str();
}
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
*
* @returns A bit string representation of the BitVector. Goes from higher-indexed bits to lower-indexed bits. (n ... 0)
*/
std::string BitVector::toString() const {
std::stringstream ret_str;
for (size_t i = length(); i != 0; --i) {
ret_str << (this->read_bit(i-1) ? "1": "0");
}
return ret_str.str();
}
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
*
* @returns A bit string representation of the BitVector. Goes from lower-indexed bits to higher-indexed bits. (0 ... n)
*/
std::string BitVector::toStringR() const {
std::stringstream ret_str;
for (size_t i = 0; i < length(); ++i) {
ret_str << (this->read_bit(i) ? "1": "0");
}
return ret_str.str();
}
#include <iostream>
void BitVector::append(const BitVector& other) {
data.reserve(data.size() + other.data.size());
size_t other_index = 0;
if (this->length() % bitvec_data_s != 0) {
// fill in the last block.
bitvec_data_t scratch = data[data.size() - 1];
size_t bits_remaining = bitvec_data_s - (this->length() % bitvec_data_s);
size_t n = std::min(other.length(), bits_remaining);
scratch |= (other.slice(0, n) << (this->length() % bitvec_data_s));
data[data.size() - 1] = scratch;
other_index += n;
}
while (other_index != other.length()) {
size_t n = std::min(other.length() - other_index, (size_t)bitvec_data_s);
data.push_back(other.slice(other_index, other_index + n));
other_index += n;
}
this->_length += other.length();
}
void BitVector::append(const std::string b64_data) {
BitVector tmp(b64_data);
this->append(tmp);
}
void BitVector::append(bitvec_data_t num, size_t length) {
BitVector tmp(num, length);
this->append(tmp);
}
size_t BitVector::length() const { return this->_length; }

93
js/c++/utils/bitvector.h Normal file
View file

@ -0,0 +1,93 @@
#pragma once
#include <string>
#include <vector>
#ifdef __EMSCRIPTEN__
#define bitvec_data_s 32
#define bitvec_data_t uint32_t
#else
#define bitvec_data_s 64
#define bitvec_data_t uint64_t
#endif
class BitVector {
/** A class used to represent an arbitrary length bit vector. Very useful for encoding and decoding.
*
*/
public:
/** Constructs an arbitrary-length bit vector.
* @class
* @param {String | Number} data - The data to append.
* @param {Number} length - A set length for the data. Ignored if data is a string.
*
* The structure of the Uint32Array should be [[last, ..., first], ..., [last, ..., first], [empty space, last, ..., first]]
*/
BitVector();
BitVector(const BitVector& other);
BitVector(const std::string b64_data);
BitVector(bitvec_data_t num, size_t length);
/** Return value of bit at index idx.
*
* @param {Number} idx - The index to read
*
* @returns The bit value at position idx
*/
bool read_bit(size_t idx) const;
/** Returns an integer value (if possible) made from the range of bits [start, end). Undefined behavior if the range to read is too big.
*
* @param {Number} start - The index to start slicing from. Inclusive.
* @param {Number} end - The index to end slicing at. Exclusive.
*
* @returns An integer representation of the sliced bits.
*/
bitvec_data_t slice(size_t start, size_t end) const;
/** Assign bit at index idx to 1.
*
* @param {Number} idx - The index to set.
*/
void set_bit(size_t idx);
/** Assign bit at index idx to 0.
*
* @param {Number} idx - The index to clear.
*/
void clear_bit(size_t idx);
/** Creates a string version of the bit vector in B64. Does not keep the order of elements a sensible human readable format.
*
* @returns A b64 string representation of the BitVector.
*/
std::string toB64() const;
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
*
* @returns A bit string representation of the BitVector. Goes from higher-indexed bits to lower-indexed bits. (n ... 0)
*/
std::string toString() const;
/** Returns a BitVector in bitstring format. Probably only useful for dev debugging.
*
* @returns A bit string representation of the BitVector. Goes from lower-indexed bits to higher-indexed bits. (0 ... n)
*/
std::string toStringR() const;
/** Appends data to the BitVector.
*
* @param {Number | String} data - The data to append.
* @param {Number} length - The length, in bits, of the new data. This is ignored if data is a string.
*/
void append(const BitVector& other);
void append(const std::string b64_data);
void append(bitvec_data_t num, size_t length);
// length getter.
size_t length() const;
private:
std::vector<bitvec_data_t> data;
size_t _length;
};

View file

@ -0,0 +1,40 @@
#include "math_utils.h"
#include <algorithm>
#include <cmath>
/**
* Clamp number between low and high values.
*
* @param num: value to clamp
* @param low
* @param high
*
* @return clamped value
*/
double clamp(double num, double low, double high) {
return std::min(std::max(num, low), high);
}
/**
* Round a value to an integer if it is veeeery close to one.
* (epsilon=0.00000001)
*
* @param value : value to round
*
* @return the same value, or rounded to an integer if it is close to one.
*/
double round_near(double value) {
const double eps = 0.00000001;
const double rounded = std::round(value);
if (std::abs(value - rounded) < eps) {
return rounded;
}
return value;
}
/**
* Compute log_b(n).
*/
double log(double b, double n) {
return std::log(n) / std::log(b);
}

28
js/c++/utils/math_utils.h Normal file
View file

@ -0,0 +1,28 @@
#pragma once
/**
* Clamp number between low and high values.
*
* @param num: value to clamp
* @param low
* @param high
*
* @return clamped value
*/
double clamp(double num, double low, double high);
/**
* Round a value to an integer if it is veeeery close to one.
* (epsilon=0.00000001)
*
* @param value : value to round
*
* @return the same value, or rounded to an integer if it is close to one.
*/
double round_near(double value);
/**
* Compute log_b(n).
*/
double log(double b, double n);

View file

@ -136,8 +136,6 @@ function calculateCraft() {
}
let ingreds = [];
for (i = 1; i < 7; i++) {
console.log("ing-choice-"+i);
// console.log(getValue("ing-choice-"+i));
getValue("ing-choice-" + i) === "" ? ingreds.push(expandIngredient(ingMap.get("No Ingredient"))) : ingreds.push(expandIngredient(ingMap.get(getValue("ing-choice-" + i))));
}
let atkSpd = "NORMAL"; //default attack speed will be normal.

View file

@ -246,7 +246,6 @@ class Custom {
}
}
let type = this.statMap.get("type").toLowerCase();
console.log(type);
if (weaponTypes.includes(type)) {
for (const n of ["nDam", "eDam", "tDam", "wDam", "fDam", "aDam"]) {
if (!(this.statMap.has(n) && this.statMap.get(n))) {

View file

@ -340,17 +340,3 @@ const default_spells = {
]
}]
};
const spell_table = {
"powder": [ //This is how instant-damage powder specials are implemented.
{ title: "Quake", cost: 0, parts:[
{ subtitle: "Total Damage", type: "damage", multiplier: [155, 220, 285, 350, 415], conversion: [0,100,0,0,0,0], summary: true},
] },
{ title: "Chain Lightning", cost: 0, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: [200, 225, 250, 275, 300], conversion: [0,0,100,0,0,0], summary: true},
]},
{ title: "Courage", cost: 0, parts: [
{ subtitle: "Total Damage", type: "damage", multiplier: [75, 87.5, 100, 112.5, 125], conversion: [0,0,0,0,100,0], summary: true},
]}, //[75, 87.5, 100, 112.5, 125]
]
};

View file

@ -1231,7 +1231,7 @@ function displayDefenseStats(parent_elem, statMap, insertSummary){
}
}
function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overall=false) {
function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon) {
parent_elem.textContent = "";
if (powderSpecials.length === 0) {
parent_elem.style = "display: none";
@ -1252,129 +1252,66 @@ function displayPowderSpecials(parent_elem, powderSpecials, stats, weapon, overa
//each entry of powderSpecials is [ps, power]
for (special of specials) {
//iterate through the special and display its effects.
let powder_special = make_elem("p", ["pt-3"]);
let powder_special_elem = make_elem("p", ["pt-3"]);
let specialSuffixes = new Map([ ["Duration", " sec"], ["Radius", " blocks"], ["Chains", ""], ["Damage", "%"], ["Damage Boost", "%"], ["Knockback", " blocks"] ]);
let specialTitle = make_elem("p");
let specialEffects = make_elem("p");
specialTitle.classList.add(damageClasses[powderSpecialStats.indexOf(special[0]) + 1]);
let effects = special[0]["weaponSpecialEffects"];
// TODO janky and depends on the order of powder specials being ETWFA. This should be encoded in the powder special object.
let element_num = powderSpecialStats.indexOf(special[0]) + 1;
specialTitle.classList.add(damageClasses[element_num]);
let powder_special = special[0];
let power = special[1];
specialTitle.textContent = special[0]["weaponSpecialName"] + " " + Math.floor((power-1)*0.5 + 4) + (power % 2 == 0 ? ".5" : "");
specialTitle.textContent = powder_special.weaponSpecialName + " " + Math.floor((power-1)*0.5 + 4) + (power % 2 == 0 ? ".5" : "");
if (!overall || powderSpecialStats.indexOf(special[0]) == 2 || powderSpecialStats.indexOf(special[0]) == 3 || powderSpecialStats.indexOf(special[0]) == 4) {
for (const [key,value] of effects) {
for (const [key,value] of powder_special.weaponSpecialEffects) {
if(key === "Damage"){
//if this special is an instant-damage special (Quake, Chain Lightning, Courage Burst), display the damage.
let specialDamage = document.createElement("p");
// specialDamage.classList.add("item-margin");
let conversions = [0, 0, 0, 0, 0, 0];
conversions[element_num] = powder_special.weaponSpecialEffects.get("Damage")[power-1];
let _results = calculateSpellDamage(stats, weapon, conversions, false, true, "0.Powder Special");
let critChance = skillPointsToPercentage(skillpoints[1]);
let save_damages = [];
let totalDamNormal = _results[0];
let totalDamCrit = _results[1];
let results = _results[2];
for (let i = 0; i < 6; ++i) {
for (let j in results[i]) {
results[i][j] = results[i][j].toFixed(2);
}
}
let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0;
let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0;
let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0;
let averageWrap = document.createElement("p");
let averageLabel = document.createElement("span");
averageLabel.textContent = "Average: ";
let averageLabelDmg = document.createElement("span");
averageLabelDmg.classList.add("Damage");
averageLabelDmg.textContent = averageDamage.toFixed(2);
averageWrap.appendChild(averageLabel);
averageWrap.appendChild(averageLabelDmg);
specialDamage.appendChild(averageWrap);
specialEffects.append(specialDamage);
}
else {
let effect = document.createElement("p");
effect.textContent += key + ": " + value[power-1] + specialSuffixes.get(key);
if(key === "Damage"){
effect.textContent += elementIcons[powderSpecialStats.indexOf(special[0])];
}
if(special[0]["weaponSpecialName"] === "Wind Prison" && key === "Damage Boost") {
effect.textContent += " (only 1st hit)";
}
specialEffects.appendChild(effect);
}
}
powder_special.appendChild(specialTitle);
powder_special.appendChild(specialEffects);
//if this special is an instant-damage special (Quake, Chain Lightning, Courage Burst), display the damage.
let specialDamage = document.createElement("p");
// specialDamage.classList.add("item-margin");
let spells = spell_table["powder"];
if (powderSpecialStats.indexOf(special[0]) == 0 || powderSpecialStats.indexOf(special[0]) == 1 || powderSpecialStats.indexOf(special[0]) == 3) { //Quake, Chain Lightning, or Courage
let spell = (powderSpecialStats.indexOf(special[0]) == 3 ? spells[2] : spells[powderSpecialStats.indexOf(special[0])]);
let part = spell["parts"][0];
powder_special_elem.appendChild(specialTitle);
powder_special_elem.appendChild(specialEffects);
let tmp_conv = [];
for (let i in part.conversion) {
tmp_conv.push(part.conversion[i] * part.multiplier[power-1] / 100);
}
console.log(tmp_conv);
let _results = calculateSpellDamage(stats, weapon, tmp_conv, false, true);
let critChance = skillPointsToPercentage(skillpoints[1]);
let save_damages = [];
let totalDamNormal = _results[0];
let totalDamCrit = _results[1];
let results = _results[2];
for (let i = 0; i < 6; ++i) {
for (let j in results[i]) {
results[i][j] = results[i][j].toFixed(2);
}
}
let nonCritAverage = (totalDamNormal[0]+totalDamNormal[1])/2 || 0;
let critAverage = (totalDamCrit[0]+totalDamCrit[1])/2 || 0;
let averageDamage = (1-critChance)*nonCritAverage+critChance*critAverage || 0;
let averageWrap = document.createElement("p");
let averageLabel = document.createElement("span");
averageLabel.textContent = "Average: ";
let averageLabelDmg = document.createElement("span");
averageLabelDmg.classList.add("Damage");
averageLabelDmg.textContent = averageDamage.toFixed(2);
averageWrap.appendChild(averageLabel);
averageWrap.appendChild(averageLabelDmg);
specialDamage.appendChild(averageWrap);
if (!overall) {
let nonCritLabel = document.createElement("p");
nonCritLabel.textContent = "Non-Crit Average: "+nonCritAverage.toFixed(2);
nonCritLabel.classList.add("damageSubtitle");
nonCritLabel.classList.add("item-margin");
specialDamage.append(nonCritLabel);
for (let i = 0; i < 6; i++){
if (results[i][1] > 0){
let p = document.createElement("p");
p.classList.add("damagep");
p.classList.add(damageClasses[i]);
p.textContent = results[i][0]+"-"+results[i][1];
specialDamage.append(p);
}
}
let normalDamage = document.createElement("p");
normalDamage.textContent = "Total: " + totalDamNormal[0].toFixed(2) + "-" + totalDamNormal[1].toFixed(2);
normalDamage.classList.add("itemp");
specialDamage.append(normalDamage);
let nonCritChanceLabel = document.createElement("p");
nonCritChanceLabel.textContent = "Non-Crit Chance: " + ((1-critChance)*100).toFixed(2) + "%";
specialDamage.append(nonCritChanceLabel);
let critLabel = document.createElement("p");
critLabel.textContent = "Crit Average: "+critAverage.toFixed(2);
critLabel.classList.add("damageSubtitle");
critLabel.classList.add("item-margin");
specialDamage.append(critLabel);
for (let i = 0; i < 6; i++){
if (results[i][1] > 0){
let p = document.createElement("p");
p.classList.add("damagep");
p.classList.add(damageClasses[i]);
p.textContent = results[i][2]+"-"+results[i][3];
specialDamage.append(p);
}
}
let critDamage = document.createElement("p");
critDamage.textContent = "Total: " + totalDamCrit[0].toFixed(2) + "-" + totalDamCrit[1].toFixed(2);
critDamage.classList.add("itemp");
specialDamage.append(critDamage);
let critChanceLabel = document.createElement("p");
critChanceLabel.textContent = "Crit Chance: " + (critChance*100).toFixed(2) + "%";
specialDamage.append(critChanceLabel);
save_damages.push(averageDamage);
}
powder_special.append(specialDamage);
}
parent_elem.appendChild(powder_special);
parent_elem.appendChild(powder_special_elem);
}
}

View file

@ -50,6 +50,14 @@ const ExprParser = (function() {
case '=':
pushSymbol(exprStr[col]);
continue;
case 'or':
tokens.push({ type: '|' });
col += 2;
continue;
case 'and':
tokens.push({ type: '&' });
col += 2;
continue;
case '>':
pushSymbol(exprStr[col + 1] === '=' ? '>=' : '>');
continue;

View file

@ -1,4 +1,4 @@
const DB_VERSION = 122;
const DB_VERSION = 126;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.jsA
let db;

View file

@ -1,4 +1,4 @@
const ING_DB_VERSION = 20;
const ING_DB_VERSION = 21;
// @See https://github.com/mdn/learning-area/blob/master/javascript/apis/client-side-storage/indexeddb/video-store/index.js
@ -201,39 +201,29 @@ function init_ing_maps() {
ingList.push(ing.displayName);
ingIDMap.set(ing.id, ing.displayName);
let numerals = new Map([[1, "I"], [2, "II"], [3, "III"], [4, "IV"], [5, "V"], [6, "VI"]]);
// pairs of (dura, req)
let powder_ing_info = [
[-35,0],[-52.5,0],[-70,10],[-91,20],[-112,28],[-133,36]
];
for (let i = 0; i < 5; i ++) {
for (const powderIng of powderIngreds) {
for (let powder_tier = 0; powder_tier < 6; ++powder_tier) {
powder_info = powder_ing_info[powder_tier];
let ing = {
name: "" + damageClasses[i+1] + " Powder " + numerals.get(powderIngreds.indexOf(powderIng) + 1),
name: "" + damageClasses[i+1] + " Powder " + numerals.get(powder_tier + 1),
tier: 0,
lvl: 0,
skills: ["ARMOURING", "TAILORING", "WEAPONSMITHING", "WOODWORKING", "JEWELING"],
ids: {},
isPowder: true,
pid: 6*i + powderIngreds.indexOf(powderIng),
itemIDs: {"dura": powderIng["durability"], "strReq": 0, "dexReq": 0,"intReq": 0,"defReq": 0,"agiReq": 0},
pid: 6*i + powder_tier,
itemIDs: {"dura": powder_info[0], "strReq": 0, "dexReq": 0,"intReq": 0,"defReq": 0,"agiReq": 0},
consumableIDs: {"dura": 0, "charges": 0},
posMods: {"left": 0, "right": 0, "above": 0, "under": 0, "touching": 0, "notTouching": 0}
};
ing.id = 4001 + ing.pid;
ing.displayName = ing.name;
switch(i) {
case 0:
ing.itemIDs["strReq"] = powderIng["skpReq"];
break;
case 1:
ing.itemIDs["dexReq"] = powderIng["skpReq"];
break;
case 2:
ing.itemIDs["intReq"] = powderIng["skpReq"];
break;
case 3:
ing.itemIDs["defReq"] = powderIng["skpReq"];
break;
case 4:
ing.itemIDs["agiReq"] = powderIng["skpReq"];
break;
}
ing.itemIDs[skp_order[i] + "Req"] = powder_info[1];
ingMap.set(ing.displayName, ing);
ingList.push(ing.displayName);
ingIDMap.set(ing.id, ing.displayName);

View file

@ -31,17 +31,6 @@ let powderStats = [
_p(2,6,11,3,1), _p(3,10,14,6,2), _p(4,11,17,10,3), _p(5,11,22,16,5), _p(7,12,28,24,9), _p(8,14,35,34,13)
];
class PowderIngredient {
constructor(durability, skpReq) {
this.durability = durability;
this.skpReq = skpReq;
}
}
function _pi(a,b) { return new PowderIngredient(a,b)}
let powderIngreds = [
_pi(-35,0),_pi(-52.5,0),_pi(-70,10),_pi(-91,20),_pi(-112,28),_pi(-133,36)
];
//Ordering: [weapon special name, weapon special effects, armor special name, armor special effects]
class PowderSpecial{
constructor(wSpName, wSpEff, aSpName, aSpEff, cap){

View file

@ -240,15 +240,31 @@ function construct_scc_graph(items_to_consider) {
parents: [],
};
for (const item of items_to_consider) {
nodes.push({item: item, children: [terminal_node], parents: [root_node]});
const set_neg = [false, false, false, false, false];
const set_pos = [false, false, false, false, false];
const set_name = item.set;
if (set_name) {
const bonuses = sets.get(set_name).bonuses;
for (const bonus of bonuses) {
for (const i in skp_order) {
if (bonus[skp_order[i]] > 0) { set_pos[i] = true; }
if (bonus[skp_order[i]] < 0) { set_neg[i] = true; }
}
}
}
nodes.push({item: item, children: [terminal_node], parents: [root_node], set_pos: set_pos, set_neg: set_neg});
}
// Dependency graph construction.
for (const node_a of nodes) {
const {item: a, children: a_children} = node_a;
const {item: a, children: a_children, set_pos: a_set_pos} = node_a;
for (const node_b of nodes) {
const {item: b, parents: b_parents} = node_b;
const {item: b, parents: b_parents, set_neg: b_set_neg} = node_b;
const setName = b.set;
for (let i = 0; i < 5; ++i) {
if (a.skillpoints[i] > 0 && (a.reqs[i] < b.reqs[i] || b.skillpoints[i] < 0)) {
if ((a.skillpoints[i] > 0 || a_set_pos[i] > 0)
&& (a.reqs[i] < b.reqs[i] || b.skillpoints[i] < 0 || b_set_neg[i] < 0)) {
a_children.push(node_b);
b_parents.push(node_a);
break;
@ -259,3 +275,4 @@ function construct_scc_graph(items_to_consider) {
const sccs = make_SCC_graph(root_node, nodes);
return [root_node, terminal_node, sccs];
}

View file

@ -278,7 +278,7 @@ Base64 = (function () {
let b64_str = "";
let i = 0;
while (i < this.length) {
b64_str += Base64.fromIntV(this.slice(i, i + 6), 1);
b64_str += Base64.fromIntN(this.slice(i, i + 6), 1);
i += 6;
}
@ -997,4 +997,4 @@ if (screen.width < 992) {
}
scrollPos = document.documentElement.scrollTop;
});
}
}

View file

@ -3798,5 +3798,9 @@
"Athanasia": 3796,
"Provenance": 3797,
"Veneration": 3798,
"Reckoning": 3799
"Reckoning": 3799,
"Eleventh Hour": 3800,
"Epilogue": 3801,
"Prologue": 3802,
"Gleeman's Tale": 3803
}

View file

@ -0,0 +1,137 @@
import json
import numpy as np
import matplotlib.pyplot as plt
def max_id(item, id_name, invert=False):
"""
Calculate the "max roll" for a given ID.
Parameters
Name type desc
----------------------------------------------------------
item json Item json data
id_name string name of the ID to get
invert bool Whether to "invert" (raw cost and %cost have funny
0.7-1.3 positive roll and 0.3-1.3 negative roll)
Return:
val: float -- max roll id value.
"""
id_val = item.get(id_name, 0)
if id_val == 0: # if the ID isn't present, its just going to be zero
return 0
if item.get('fixID', False):
# If the item is a fixed roll item, don't roll the ID.
return id_val
# roll the ID. Negative roll (and invert) max roll is 0.7; positive max is 1.3.
if bool(id_val < 0) != bool(invert): # logical XOR
val = round(id_val * 0.7)
else: #if bool(id_val > 0) != bool(invert):
val = round(id_val * 1.3)
if val == 0: # if we rounded to zero, then restore the id as sign(base_val).
val = id_val / abs(id_val)
return val
def mv(item, base_costs):
"""
Compute mana value for an item.
Takes a maximum mana value
- assuming 1 melee value (3/3 mana steal = 1 mana value)
- assuming spells 1, 3, and 4 are cycle spells.
Ignores spell 2 for spell cost purposes.
Parameters
Name type desc
----------------------------------------------------------
item json Item json data
base_costs list[float] base spell cost [spell1, spell2, spell3, spell4]
Return:
val: float -- mana value.
"""
cost_reductions = sorted([
max_id(item, 'spRaw1', True) + base_costs[0]*max_id(item, 'spPct1', True)/100,
#max_id(item, 'spRaw2', True) + base_costs[1]*max_id(item, 'spPct2', True)/100,
max_id(item, 'spRaw3', True) + base_costs[2]*max_id(item, 'spPct3', True)/100,
max_id(item, 'spRaw4', True) + base_costs[3]*max_id(item, 'spPct4', True)/100,
])
cost_mv = -sum(cost_reductions[:2])
return (
max_id(item, 'ms')/3
+ max_id(item, 'mr')/5
+ cost_mv
)
###########################
# constants for damage calc.
elements = 'rnetwfa'
raw_ids = ['sdRaw'] + [x+'SdRaw' for x in elements] + [x+'DamRaw' for x in elements]
# these %boosts apply to all damages.
percent_all_ids = ['sdPct', 'rSdPct']
# this one is a list of lists.
# the mini lists are sub-sums, the big list gets max'd over (elemental damage works like this.)
percent_max_id_groups = list(zip([x+'DamPct' for x in 'etwfa'] + [x+'SdPct' for x in 'etwfa'])) # exclude neutral lel
###########################
def damage(item, weapon_base):
"""
Compute effective damage bonus.
Note that this assumes the weapon aligns with whatever bonus this item is giving.
Parameters
Name type desc
----------------------------------------------------------
item json Item json data
weapon_base float weapon base dps
Return:
val: float -- raw damage bonus given (approximate) for the weapon.
"""
total = sum(max_id(item, x) for x in raw_ids)
total += weapon_base * sum(max_id(item, x) for x in percent_all_ids) / 100
total += weapon_base * max(sum(max_id(item, y) for y in x) for x in percent_max_id_groups) / 100
return total
#################################
# NOTE: Edit these parameters! LOL i was lazy to make a CLI
level_threshold = 80
weapon_base = 700
base_costs = [35, 20, 35, 35]
item_type = 'leggings'
# TODO: Changeme to point to a copy of wynnbuilder's compress.json file!
items = json.load(open("../../compress.json"))['items']
#################################
# collect data from items.
points = []
names = dict()
for item in items:
if item['type'] == item_type and item['lvl'] > level_threshold:
# Edit me to see other comparisons!
#point = (mv(item, base_costs), damage(item, weapon_base))
point = (max_id(item, 'spd'), item.get('hp', 0) + max_id(item, 'hpBonus'))
points.append(point)
# just some shenanigans to aggregate text that happens to fall on the same point.
if point in names:
names[point] += '\n'+item.get('displayName', item['name'])
else:
names[point] = item.get('displayName', item['name'])
points = np.array(points)
# plot points.
plt.figure()
plt.scatter(points[:, 0], points[:, 1])
# and add annotations.
for point, txt in names.items():
plt.annotate(txt, point)
plt.show()