├── .gitignore ├── favicon.png ├── favicon666.png ├── img ├── potion.png ├── darkcaves.png └── perk_reroll.png ├── lib ├── noita_random.wasm └── src │ ├── build.bat │ ├── LICENCE │ └── noita_random.cpp ├── data ├── temple-locations.json ├── rain.json ├── starting-flask-materials.json ├── biome_names.json ├── fungal-materials.json ├── biome_modifiers.json └── perks.json ├── LICENCE ├── scripts └── random.js ├── worker.js ├── index.css ├── index.js ├── index.html └── requirements.js /.gitignore: -------------------------------------------------------------------------------- 1 | beta/ 2 | -------------------------------------------------------------------------------- /favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cr4xy/noita-seed-tool/HEAD/favicon.png -------------------------------------------------------------------------------- /favicon666.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cr4xy/noita-seed-tool/HEAD/favicon666.png -------------------------------------------------------------------------------- /img/potion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cr4xy/noita-seed-tool/HEAD/img/potion.png -------------------------------------------------------------------------------- /img/darkcaves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cr4xy/noita-seed-tool/HEAD/img/darkcaves.png -------------------------------------------------------------------------------- /img/perk_reroll.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cr4xy/noita-seed-tool/HEAD/img/perk_reroll.png -------------------------------------------------------------------------------- /lib/noita_random.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cr4xy/noita-seed-tool/HEAD/lib/noita_random.wasm -------------------------------------------------------------------------------- /data/temple-locations.json: -------------------------------------------------------------------------------- 1 | [{"x":-32,"y":1410},{"x":-32,"y":2946},{"x":-32,"y":4994},{"x":-32,"y":6530},{"x":-32,"y":8578},{"x":-32,"y":10626},{"x":2560,"y":13181}] 2 | -------------------------------------------------------------------------------- /data/rain.json: -------------------------------------------------------------------------------- 1 | [{"chance":1.0,"rain_material":"water"},{"chance":0.05,"rain_material":"water"},{"chance":0.001,"rain_material":"blood"},{"chance":0.0002,"rain_material":"acid"},{"chance":0.0001,"rain_material":"slime"}] 2 | -------------------------------------------------------------------------------- /data/starting-flask-materials.json: -------------------------------------------------------------------------------- 1 | ["mud", "water_swamp", "water_salt", "swamp", "snow", "water", "blood", "acid", "magic_liquid_polymorph", "magic_liquid_random_polymorph", "magic_liquid_berserk", "magic_liquid_charm", "magic_liquid_movement_faster", "urine", "gold", "slime", "gunpowder_unstable"] 2 | -------------------------------------------------------------------------------- /lib/src/build.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | pushd ".." 3 | emcc noita_random.cpp -s "EXPORTED_FUNCTIONS=['_main','_SetWorldSeed','_SetRandomSeed','_Random','_ProceduralRandomf','_ProceduralRandomi']" -s "EXPORTED_RUNTIME_METHODS=['ccall','cwrap']" -Wno-c++11-narrowing -Wno-writable-strings -o noita_random.js 4 | popd 5 | -------------------------------------------------------------------------------- /data/biome_names.json: -------------------------------------------------------------------------------- 1 | [{"translated_name":"mines","id":"coalmine"},{"translated_name":"","id":"mountain_hall"},{"translated_name":"collapsed mines","id":"coalmine_alt"},{"translated_name":"Coal Pits","id":"excavationsite"},{"translated_name":"Fungal Caverns","id":"fungicave"},{"translated_name":"Snowy Depths","id":"snowcave"},{"translated_name":"Hiisi Base","id":"snowcastle"},{"translated_name":"Underground Jungle","id":"rainforest"},{"translated_name":"","id":"rainforest_open"},{"translated_name":"The Vault","id":"vault"},{"translated_name":"Temple of the Art","id":"crypt"}] -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Cr4xy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /lib/src/LICENCE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 kaliuresis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /scripts/random.js: -------------------------------------------------------------------------------- 1 | function randomFromArray(arr) { 2 | return arr[Random(1, arr.length) - 1]; 3 | } 4 | 5 | function random_create(x, y) { 6 | var result = {}; 7 | result.x = x; 8 | result.y = y; 9 | return result 10 | } 11 | 12 | function random_next(rnd, min, max) { 13 | let result = ProceduralRandomf(rnd.x, rnd.y, min, max); 14 | rnd.y++; 15 | return result; 16 | } 17 | 18 | function pick_random_from_table_backwards(t, rnd) { 19 | let result = null; 20 | let len = t.length; 21 | 22 | for (let i = len - 1; i >= 0; i--) { 23 | if (random_next(rnd, 0.0, 1.0) <= t[i].chance) { 24 | result = t[i]; 25 | break; 26 | } 27 | } 28 | 29 | if (result == null) { 30 | result = t[0]; 31 | } 32 | 33 | return result; 34 | } 35 | 36 | function pick_random_from_table_weighted(rnd, t) { 37 | if (t.length == 0) { return null }; 38 | 39 | var weight_sum = 0.0; 40 | for (var i = 0; i < t.length; i++) { 41 | var it = t[i]; 42 | it.weight_min = weight_sum 43 | it.weight_max = weight_sum + it.probability 44 | weight_sum = it.weight_max 45 | } 46 | 47 | var val = random_next(rnd, 0.0, weight_sum); 48 | var result = t[0]; 49 | for (var i = 0; i < t.length; i++) { 50 | var it = t[i]; 51 | if (val >= it.weight_min && val <= it.weight_max) { 52 | result = it; 53 | break 54 | } 55 | } 56 | 57 | return result 58 | } 59 | 60 | function random_nexti(rnd, min, max) { 61 | var result = ProceduralRandomi(rnd.x, rnd.y, min, max); 62 | rnd.y = rnd.y + 1; 63 | return result 64 | } -------------------------------------------------------------------------------- /worker.js: -------------------------------------------------------------------------------- 1 | self.importScripts('requirements.js'); 2 | self.importScripts('lib/noita_random_worker.js'); 3 | self.importScripts('scripts/random.js'); 4 | 5 | self.onmessage = function(e) { 6 | let [seed, offset, numWorkers, seedCriteriaStr] = e.data; 7 | seed = Number(seed); 8 | seed += offset; 9 | let workerId = offset; 10 | let count = 0; 11 | let seedCriteria = parseSeedCriteria(seedCriteriaStr); 12 | 13 | Promise.all(loadingInfoProviders).then(() => { 14 | let now = Date.now(); 15 | let nextProgress = now; 16 | let success; 17 | let orOk; 18 | let test; 19 | while (true) { 20 | SetWorldSeed(seed); 21 | success = true; 22 | orOk = false; 23 | for (let criteria of seedCriteria) { 24 | if (orOk) { 25 | if (!criteria.or) { 26 | orOk = false; 27 | } 28 | continue; 29 | } 30 | test = criteria.test(); 31 | if (criteria.not && test || !criteria.not && !test) { 32 | if (criteria.or) { 33 | orOk = false; 34 | continue; 35 | } 36 | success = false; 37 | break; 38 | } 39 | if (criteria.or) { 40 | orOk = true; 41 | } 42 | } 43 | if (success) break; 44 | seed += numWorkers; 45 | if (seed > 0xFFFFFFFF) break; 46 | now = Date.now(); 47 | count++; 48 | if (nextProgress <= now) { 49 | nextProgress = now + 500; 50 | self.postMessage([workerId, 0, count]); 51 | } 52 | } 53 | 54 | if (seed <= 0xFFFFFFFF) 55 | self.postMessage([workerId, 1, seed]); 56 | else 57 | self.postMessage([workerId, 2, 0xFFFFFFFF]); 58 | }); 59 | } -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | .fungal-shift:nth-child(even) { 2 | background-color: hsla(var(--b2)/var(--tw-bg-opacity,1)); 3 | } 4 | .fungal-shift > div { 5 | position: relative; 6 | } 7 | .fungal-flask:before { 8 | content: " "; 9 | background: url('img/potion.png') center / contain no-repeat; 10 | position: absolute; 11 | image-rendering: pixelated; 12 | image-rendering: -moz-crisp-edges; 13 | width: 20px; 14 | height: 20px; 15 | top: -13px; 16 | right: -10px; 17 | } 18 | .fungal-shift > .fungal-flask:nth-child(4):before { 19 | left: -10px; 20 | } 21 | .perk { 22 | position: relative; 23 | } 24 | .perk > img { 25 | image-rendering: pixelated; 26 | image-rendering: -moz-crisp-edges; 27 | } 28 | .perk-active { 29 | background-color: hsla(var(--a)/var(--tw-bg-opacity,1)); 30 | } 31 | .perk-gambled { 32 | background-color: hsla(var(--wa)/var(--tw-bg-opacity,1)); 33 | cursor: default; 34 | } 35 | .perk-preserved:before { 36 | content: "🍀"; 37 | position: absolute; 38 | top: 0; 39 | right: 0; 40 | } 41 | .perk-reroll { 42 | background: url('img/perk_reroll.png') center -17px/contain no-repeat; 43 | image-rendering: pixelated; 44 | image-rendering: -moz-crisp-edges; 45 | user-select: none; 46 | } 47 | .biome-modifier { 48 | image-rendering: pixelated; 49 | image-rendering: -moz-crisp-edges; 50 | } 51 | .biome-modifier:nth-child(even) { 52 | background-color: hsla(var(--b2)/var(--tw-bg-opacity,1)); 53 | } 54 | .dark-cave { 55 | width: 256px; 56 | height: 256px; 57 | background: url('img/darkcaves.png') 0 0 / 100% no-repeat; 58 | } 59 | .link { 60 | color: #6495ED; 61 | } 62 | @media (max-width: 768px) { 63 | .seed-criteria { 64 | display: block; 65 | } 66 | td.seed-critera-left { 67 | padding-right: 0.25rem; 68 | } 69 | td.seed-critera-middle { 70 | padding-right: 0.25rem; 71 | padding-left: 0.25rem; 72 | } 73 | td.seed-critera-right { 74 | padding-left: 0.25rem; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /data/fungal-materials.json: -------------------------------------------------------------------------------- 1 | {"materials_from":[{"probability":1.0,"materials":["water","water_static","water_salt","water_ice"],"name_material":"water"},{"probability":1.0,"materials":["lava"]},{"probability":1.0,"materials":["radioactive_liquid","poison","material_darkness"],"name_material":"radioactive_liquid"},{"probability":1.0,"materials":["oil","swamp","peat"],"name_material":"oil"},{"probability":1.0,"materials":["blood"]},{"probability":1.0,"materials":["blood_fungi","fungi","fungisoil"],"name_material":"fungi"},{"probability":1.0,"materials":["blood_cold","blood_worm"]},{"probability":1.0,"materials":["acid"]},{"probability":0.4,"materials":["acid_gas","acid_gas_static","poison_gas","fungal_gas","radioactive_gas","radioactive_gas_static"],"name_material":"acid_gas"},{"probability":0.4,"materials":["magic_liquid_polymorph","magic_liquid_unstable_polymorph"],"name_material":"magic_liquid_polymorph"},{"probability":0.4,"materials":["magic_liquid_berserk","magic_liquid_charm","magic_liquid_invisibility"]},{"probability":0.6,"materials":["diamond"]},{"probability":0.6,"materials":["silver","brass","copper"]},{"probability":0.2,"materials":["steam","smoke"]},{"probability":0.4,"materials":["sand"]},{"probability":0.4,"materials":["snow_sticky"]},{"probability":0.05,"materials":["rock_static"]},{"probability":0.0003,"materials":["gold","gold_box2d"],"name_material":"gold"}],"materials_to":[{"probability":1.00,"material":"water"},{"probability":1.00,"material":"lava"},{"probability":1.00,"material":"radioactive_liquid"},{"probability":1.00,"material":"oil"},{"probability":1.00,"material":"blood"},{"probability":1.00,"material":"blood_fungi"},{"probability":1.00,"material":"acid"},{"probability":1.00,"material":"water_swamp"},{"probability":1.00,"material":"alcohol"},{"probability":1.00,"material":"sima"},{"probability":1.00,"material":"blood_worm"},{"probability":1.00,"material":"poison"},{"probability":1.00,"material":"vomit"},{"probability":1.00,"material":"pea_soup"},{"probability":1.00,"material":"fungi"},{"probability":0.80,"material":"sand"},{"probability":0.80,"material":"diamond"},{"probability":0.80,"material":"silver"},{"probability":0.80,"material":"steam"},{"probability":0.50,"material":"rock_static"},{"probability":0.50,"material":"gunpowder"},{"probability":0.50,"material":"material_darkness"},{"probability":0.50,"material":"material_confusion"},{"probability":0.20,"material":"rock_static_radioactive"},{"probability":0.02,"material":"magic_liquid_polymorph"},{"probability":0.02,"material":"magic_liquid_random_polymorph"},{"probability":0.15,"material":"magic_liquid_teleportation"},{"probability":0.01,"material":"urine"},{"probability":0.01,"material":"poo"},{"probability":0.01,"material":"void_liquid"},{"probability":0.01,"material":"cheese_static"}]} 2 | -------------------------------------------------------------------------------- /lib/src/noita_random.cpp: -------------------------------------------------------------------------------- 1 | // Thanks to kaliuresis! 2 | // Check out his orb atlas repository: https://github.com/kaliuresis/noa 3 | #include 4 | #include 5 | #include 6 | 7 | typedef unsigned int uint; 8 | 9 | typedef uint8_t byte; 10 | typedef int8_t int8; 11 | typedef int16_t int16; 12 | typedef int32_t int32; 13 | typedef int64_t int64; 14 | typedef uint8_t uint8; 15 | typedef uint16_t uint16; 16 | typedef uint32_t uint32; 17 | typedef uint64_t uint64; 18 | typedef uint8 bool8; 19 | 20 | uint world_seed = 0; 21 | double random_seed = 0; 22 | 23 | EM_JS(void, call_emscripten_ready, (), { 24 | emscripten_ready(); 25 | }); 26 | 27 | int main(int argc, char** argv) { 28 | call_emscripten_ready(); 29 | return 0; 30 | } 31 | 32 | uint64 SetRandomSeedHelper(double r) 33 | { 34 | uint64 e = *(uint64*)&r; 35 | if (((e >> 0x20 & 0x7fffffff) < 0x7FF00000) 36 | && (-9.223372036854776e+18 <= r) && (r < 9.223372036854776e+18)) 37 | { 38 | //should be same as e &= ~(1<<63); which should also just clears the sign bit, 39 | //or maybe it does nothing, 40 | //but want to keep it as close to the assembly as possible for now 41 | e <<= 1; 42 | e >>= 1; 43 | double s = *(double*)&e; 44 | uint64 i = 0; 45 | if (s != 0.0) 46 | { 47 | uint64 f = (((uint64)e) & 0xfffffffffffff) | 0x0010000000000000; 48 | uint64 g = 0x433 - ((uint64)e >> 0x34); 49 | uint64 h = f >> g; 50 | 51 | int j = -(uint)(0x433 < ((e >> 0x20) & 0xFFFFFFFF) >> 0x14); 52 | i = (uint64)j << 0x20 | j; 53 | i = ~i & h | f << (((uint64)s >> 0x34) - 0x433) & i; 54 | i = ~- (uint64)(r == s) & -i | i & -(uint64)(r == s); 55 | // error handling, whatever 56 | // f = f ^ 57 | // if((int) g > 0 && f ) 58 | } 59 | return i & 0xFFFFFFFF; 60 | } 61 | 62 | //error! 63 | uint64 error_ret_val = 0x8000000000000000; 64 | return *(double*)&error_ret_val; 65 | } 66 | 67 | uint SetRandomSeedHelper2(int param_1, int param_2, uint param_3) 68 | { 69 | uint uVar1; 70 | uint uVar2; 71 | uint uVar3; 72 | 73 | uVar2 = (param_1 - param_2) - param_3 ^ param_3 >> 0xd; 74 | uVar1 = (param_2 - uVar2) - param_3 ^ uVar2 << 8; 75 | uVar3 = (param_3 - uVar2) - uVar1 ^ uVar1 >> 0xd; 76 | uVar2 = (uVar2 - uVar1) - uVar3 ^ uVar3 >> 0xc; 77 | uVar1 = (uVar1 - uVar2) - uVar3 ^ uVar2 << 0x10; 78 | uVar3 = (uVar3 - uVar2) - uVar1 ^ uVar1 >> 5; 79 | uVar2 = (uVar2 - uVar1) - uVar3 ^ uVar3 >> 3; 80 | uVar1 = (uVar1 - uVar2) - uVar3 ^ uVar2 << 10; 81 | return (uVar3 - uVar2) - uVar1 ^ uVar1 >> 0xf; 82 | } 83 | 84 | double Random(double a, double b) { 85 | int iVar1; 86 | 87 | iVar1 = (int)random_seed * 0x41a7 + ((int)random_seed / 0x1f31d) * -0x7fffffff; 88 | if (iVar1 < 1) { 89 | iVar1 = iVar1 + 0x7fffffff; 90 | } 91 | random_seed = (double)iVar1; 92 | return a - ((b - a) * (double)iVar1 * -4.656612875e-10); 93 | } 94 | 95 | extern "C" 96 | { 97 | 98 | void SetWorldSeed(uint worldseed) { 99 | world_seed = worldseed; 100 | } 101 | 102 | void SetRandomSeed(double x, double y) 103 | { 104 | uint a = world_seed ^ 0x93262e6f; 105 | uint b = a & 0xfff; 106 | uint c = (a >> 0xc) & 0xfff; 107 | 108 | double x_ = x + b; 109 | 110 | double y_ = y + c; 111 | 112 | double r = x_ * 134217727.0; 113 | uint64 e = SetRandomSeedHelper(r); 114 | 115 | uint64 _x = (*(uint64*)&x_ & 0x7FFFFFFFFFFFFFFF); 116 | uint64 _y = (*(uint64*)&y_ & 0x7FFFFFFFFFFFFFFF); 117 | if (102400.0 <= *((double*)&_y) || *((double*)&_x) <= 1.0) 118 | { 119 | r = y_ * 134217727.0; 120 | } 121 | else 122 | { 123 | double y__ = y_ * 3483.328; 124 | double t = e; 125 | y__ += t; 126 | y_ *= y__; 127 | r = y_; 128 | } 129 | 130 | uint64 f = SetRandomSeedHelper(r); 131 | 132 | uint g = SetRandomSeedHelper2(e, f, world_seed); 133 | double s = g; 134 | s /= 4294967295.0; 135 | s *= 2147483639.0; 136 | s += 1.0; 137 | 138 | if (2147483647.0 <= s) { 139 | s = s * 0.5; 140 | } 141 | random_seed = s; 142 | 143 | Random(0, 0); 144 | 145 | uint h = world_seed & 3; 146 | while (h) 147 | { 148 | Random(0, 0); 149 | h--; 150 | } 151 | } 152 | 153 | } 154 | 155 | extern "C" 156 | { 157 | 158 | int Random(int a, int b) 159 | { 160 | return (int)Random((double)a, (double)b + 1); 161 | } 162 | 163 | double ProceduralRandomf(double x, double y, double a, double b) { 164 | SetRandomSeed(x, y); 165 | return Random(a, b); 166 | } 167 | 168 | int ProceduralRandomi(double x, double y, double a, double b) { 169 | SetRandomSeed(x, y); 170 | return (int)Random(a, b); 171 | } 172 | 173 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var workers; 2 | var app; 3 | 4 | function nthify(num) { 5 | let n = num % 10; 6 | if (num < 10 || (num > 20 && num < 30)) { 7 | if (n == 1) return num + "st"; 8 | if (n == 2) return num + "nd"; 9 | if (n == 3) return num + "rd"; 10 | } 11 | return num + "th"; 12 | } 13 | 14 | app = new Vue({ 15 | el: "#app", 16 | data: { 17 | seed: 0, 18 | showSeedCriteria: false, 19 | seedCriteria: [], 20 | availableRequirements: AVAILABLE_REQUIREMENTS, 21 | searchingSeed: false, 22 | searchUseAllCores: false, 23 | seedSearchCounts: [], 24 | seedLimitHit: false, 25 | 26 | seedInfo: { 27 | rainType: null, 28 | startingFlask: null, 29 | startingBombWand: null, 30 | perks: null, 31 | perkLottery: null, 32 | fungalShifts: null 33 | }, 34 | 35 | displayPerkDeck: false, 36 | 37 | pickedPerks: [], 38 | perkRerolls: [], 39 | perkWorldOffset: 0, 40 | }, 41 | methods: { 42 | searchSeed() { 43 | if (this.seed == 0) this.seed++; 44 | this.seedSearchCounts = []; 45 | this.seedLimitHit = false; 46 | let crit = this.serializeSeedCriteria(); 47 | this.initWorkers(); 48 | let startSeed = Number(this.seed) + 1; 49 | for (let i = 0; i < workers.length; i++) 50 | workers[i].postMessage([startSeed, i, workers.length, crit]); 51 | this.searchingSeed = true; 52 | }, 53 | cancelSeedSearch() { 54 | for (let i = 0; i < workers.length; i++) 55 | workers[i].terminate(); 56 | this.workers = []; 57 | this.searchingSeed = false; 58 | }, 59 | serializeSeedCriteria() { 60 | return this.seedCriteria.map(e => (e.not ? "!" : "") + e.serialize() + (e.or ? ";" : "")).join() 61 | }, 62 | copySeedSearchLink() { 63 | let url = new URL(document.URL); 64 | url.searchParams.set("search", this.serializeSeedCriteria()); 65 | this.copyString(url.toString()); 66 | }, 67 | parseSeedSearchLink() { 68 | let str = new URL(document.URL).searchParams.get("search"); 69 | if (!str) return; 70 | let criteria = parseSeedCriteria(str); 71 | if (criteria) this.seedCriteria = criteria; 72 | return true; 73 | }, 74 | generateSeed() { 75 | this.seed = Math.floor(Math.random() * 0x7ffffffd); 76 | }, 77 | findSeed() { 78 | this.showSeedCriteria = !this.showSeedCriteria; 79 | }, 80 | addCriteria() { 81 | this.seedCriteria.push(new this.availableRequirements[0]()) 82 | }, 83 | changeCriteriaType(index, i) { 84 | this.$set(this.seedCriteria, index, new this.availableRequirements[i]()); 85 | //this.$forceUpdate(); 86 | }, 87 | copyCriteria(index) { 88 | let criteria = this.seedCriteria[index]; 89 | let newCriteria = criteria.constructor.deserialize(criteria.serialize()); 90 | //if (criteria.or) this.seedCriteria[this.seedCriteria.length - 1].or = true; 91 | newCriteria.not = criteria.not; 92 | this.seedCriteria.push(newCriteria); 93 | }, 94 | removeCriteria(index) { 95 | this.seedCriteria.splice(index, 1); 96 | this.seedCriteria[this.seedCriteria.length - 1].or = false; 97 | }, 98 | translateMaterial(matName) { 99 | return infoProviders.MATERIAL.provide(matName).translated_name; 100 | }, 101 | translateBiome(biomeId) { 102 | return infoProviders.BIOME.provide(biomeId).translated_name; 103 | }, 104 | translateModifier(modifierId) { 105 | return modifierId.replace(/_/g, " ").toLowerCase(); 106 | }, 107 | isPrimaryBiome(biomeId) { 108 | return infoProviders.BIOME.isPrimary(biomeId); 109 | }, 110 | getMaterialColorHex(matName) { 111 | let color = infoProviders.MATERIAL.provide(matName).color; 112 | let r = color & 0xff0000; 113 | let g = color & 0x00ff00; 114 | let b = color & 0x0000ff; 115 | r >>= 16; 116 | b <<= 16; 117 | let rgbHex = (r + g + b).toString(16); 118 | return "#" + "000000".substr(rgbHex.length) + rgbHex; 119 | }, 120 | showPerkDeck() { 121 | this.displayPerkDeck = true; 122 | Vue.nextTick(() => this.$refs['perk-deck-modal'].focus()) 123 | }, 124 | hidePerkDeck() { 125 | this.displayPerkDeck = false; 126 | }, 127 | perksGoEast() { 128 | this.perkWorldOffset++; 129 | this.refreshPerks(); 130 | }, 131 | perksGoWest() { 132 | this.perkWorldOffset--; 133 | this.refreshPerks(); 134 | }, 135 | onClickPerk(level, perk) { 136 | if (perk.gambled) return; 137 | let perkId = perk.id; 138 | if (!this.pickedPerks[this.perkWorldOffset]) this.pickedPerks[this.perkWorldOffset] = []; 139 | if (this.pickedPerks[this.perkWorldOffset][level]) { 140 | if (this.pickedPerks[this.perkWorldOffset][level].some(e => e === perkId)) { 141 | this.pickedPerks[this.perkWorldOffset][level].splice(this.pickedPerks[this.perkWorldOffset][level].findIndex(e => e === perkId), 1); 142 | } else { 143 | this.pickedPerks[this.perkWorldOffset][level].push(perkId); 144 | } 145 | } else { 146 | //this.pickedPerks[this.perkWorldOffset][level] = perkId; 147 | this.$set(this.pickedPerks[this.perkWorldOffset], level, [perkId]); 148 | } 149 | let changed; 150 | do { 151 | changed = false; 152 | this.refreshPerks(); 153 | for (let i = 0; i < this.pickedPerks[this.perkWorldOffset].length; i++) { 154 | if (!this.pickedPerks[this.perkWorldOffset][i]) continue; 155 | for (let j = 0; j < this.pickedPerks[this.perkWorldOffset][i].length; j++) { 156 | if (this.pickedPerks[this.perkWorldOffset][i][j] && !this.seedInfo.perks[i].some(e => e.perk.id === this.pickedPerks[this.perkWorldOffset][i][j])) { 157 | this.pickedPerks[this.perkWorldOffset][i].splice(j, 1); 158 | changed = true; 159 | break; 160 | } 161 | } 162 | } 163 | } while (changed); 164 | }, 165 | increasePerkRerolls(level) { 166 | if (!this.perkRerolls[this.perkWorldOffset]) this.$set(this.perkRerolls, this.perkWorldOffset, []); 167 | if (isNaN(this.perkRerolls[this.perkWorldOffset][level])) this.perkRerolls[this.perkWorldOffset][level] = 0; 168 | this.$set(this.perkRerolls[this.perkWorldOffset], level, this.perkRerolls[this.perkWorldOffset][level] + 1); 169 | this.refreshPerks(); 170 | }, 171 | decreasePerkRerolls(level) { 172 | if (!this.perkRerolls[this.perkWorldOffset]) this.$set(this.perkRerolls, this.perkWorldOffset, []); 173 | if (isNaN(this.perkRerolls[this.perkWorldOffset][level])) this.perkRerolls[this.perkWorldOffset][level] = 0; 174 | this.$set(this.perkRerolls[this.perkWorldOffset], level, Math.max(0, this.perkRerolls[this.perkWorldOffset][level] - 1)); 175 | this.refreshPerks(); 176 | }, 177 | refreshPerks() { 178 | this.seedInfo.perks = infoProviders.PERK.provide(this.pickedPerks, null, this.perkWorldOffset, this.perkRerolls); 179 | this.seedInfo.perksLottery = infoProviders.PERK.perkLottery(this.pickedPerks, this.seedInfo.perks, this.perkWorldOffset); 180 | }, 181 | copyString(str) { 182 | let txtCopy = document.createElement('input'); 183 | txtCopy.value = str; 184 | document.body.appendChild(txtCopy); 185 | txtCopy.select(); 186 | document.execCommand('copy'); 187 | document.body.removeChild(txtCopy); 188 | }, 189 | parseURL() { 190 | if (this.parseSeedSearchLink()) { 191 | this.showSeedCriteria = true; 192 | } 193 | let url = new URL(document.URL); 194 | let seed; 195 | if (seed = url.searchParams.get("seed")) { 196 | this.seed = seed; 197 | } 198 | }, 199 | initWorkers() { 200 | workers = []; 201 | let numCores = 1; 202 | if (this.searchUseAllCores) numCores = navigator.hardwareConcurrency || 1; 203 | for (let i = 0; i < numCores; i++) { 204 | let worker = new Worker("worker.js"); 205 | workers.push(worker); 206 | worker.addEventListener('message', e => { 207 | let [id, type, value] = e.data; 208 | if (type === 0) { // progress report 209 | this.$set(this.seedSearchCounts, id, value); 210 | } else if (type === 1) { // found seed 211 | this.seed = value; 212 | //this.searchingSeed = false; 213 | this.cancelSeedSearch(); 214 | } else if (type === 2) { // hit seed limit 215 | workers.splice(workers.indexOf(worker), 1); 216 | if (workers.length === 0) { 217 | this.seed = value; 218 | this.seedLimitHit = true; 219 | this.cancelSeedSearch(); 220 | } 221 | } 222 | }); 223 | } 224 | } 225 | }, 226 | watch: { 227 | seed(val, oldVal) { 228 | this.perkWorldOffset = 0; 229 | this.pickedPerks = []; 230 | this.perkRerolls = []; 231 | //this.fungalHoldingFlaskAll = false; 232 | let url = new URL(document.URL); 233 | if (this.seed) 234 | url.searchParams.set("seed", this.seed); 235 | else 236 | url.searchParams.delete("seed"); 237 | if (url.search) 238 | history.replaceState(null, null, url.search); 239 | else 240 | history.replaceState(null, null, url.pathname); 241 | if (this.seed == 666) document.querySelector("link[rel='shortcut icon']").href = "favicon666.png"; 242 | else if (oldVal == 666) document.querySelector("link[rel='shortcut icon']").href = "favicon.png"; 243 | 244 | if (!this.seed) { 245 | this.seedInfo = { 246 | rainType: [false] 247 | }; 248 | } 249 | 250 | SetWorldSeed(Number(this.seed)); 251 | 252 | this.seedInfo = { 253 | rainType: infoProviders.RAIN.provide(), 254 | startingFlask: infoProviders.STARTING_FLASK.provide(), 255 | startingSpell: infoProviders.STARTING_SPELL.provide(), 256 | startingBombSpell: infoProviders.STARTING_BOMB_SPELL.provide(), 257 | perkDeck: infoProviders.PERK.getPerkDeck(true), 258 | perks: null, 259 | perksLottery: null, 260 | fungalShifts: infoProviders.FUNGAL_SHIFT.provide(null), 261 | biomeModifiers: infoProviders.BIOME_MODIFIER.provide(), 262 | waterCave: infoProviders.WATER_CAVE.provide(), 263 | }; 264 | this.refreshPerks(); 265 | }, 266 | }, 267 | computed: { 268 | perkWorldOffsetText() { 269 | if (this.perkWorldOffset == 0) return 'main world'; 270 | if (this.perkWorldOffset < 0) return 'west ' + Math.abs(this.perkWorldOffset); 271 | return 'east ' + this.perkWorldOffset; 272 | }, 273 | seedCriteriaText() { 274 | let str = "\n"; 275 | for (let i = 0; i < this.seedCriteria.length; i++) { 276 | let criteria = this.seedCriteria[i]; 277 | str += "- " + (criteria.not ? "DON'T " : "") + criteria.textify() + (criteria.or ? " OR:" : "") + "\n"; 278 | } 279 | return str; 280 | }, 281 | seedSearchCountStr() { 282 | return this.seedSearchCounts.reduce((acc, val) => acc + val, 0).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 283 | }, 284 | sortedPerks() { 285 | return infoProviders.PERK.perks.slice(0).sort((a, b) => a.ui_name.localeCompare(b.ui_name)) 286 | } 287 | }, 288 | async created() { 289 | await Promise.all(loadingInfoProviders); 290 | this.parseURL(); 291 | } 292 | }); 293 | -------------------------------------------------------------------------------- /data/biome_modifiers.json: -------------------------------------------------------------------------------- 1 | [{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACOElEQVRYw+1ZMY7CMBCcRXBcQU0XxC/uC+gknhGEdCU8I5QgBM9AOvEFfoGgo6bAR7FXxA7GsU2S4uTArYQMURLtTDae2YWYGWWCiPIH55dyN/mr+HrPJVsWb9NLxvqY3jOOyHrC6sC4dgGxWQZJ0PzCaJ2AUY8c+BgAcRxVI8gbqwPjvFPEbIMkSGwAIXN1kPQoGg+qZyK/TxzkbIMl5/bgtjjvllgd2MCnME0k1koVlGjrLDt67Zrn7QMlqO/J2Y6taAUBYABT+VHva7rJtU51IOc+t9Yp26AVFg0fVyHIHfmnEX5UzLlR4BVLVBUBAC0EQ2yWaA/HtSGnPRxDbJa0EHqlTDV8jwmi9VEvPQAggygCgM+3NgBs5VqLsORMBjGkS7++aXs3ad3/qIu+fwQADORai7jPue3E5lQxU9I5jmbmhT4zVbfgOLKSYkj/jOMIxMyZY7ZVjtOB3nzQPnA++gAG6HyMC+HSfnIcZXuQVdJfKVzSX1rmaSHS6qmbip13poqVblYTQ/5eMXLYGz5Jt0j/s8i8TdKt0t/0SfpjyayvzBe1NbqK2WRwopy0kv5nUTEp6apLmNmkP1Mx3eMYficx1mfqxXLYbDw0PUaQtR4sofWROY6IiID5xXxC4Y87kI6L5RujS7rCSjYeqk0UW6d0UudIJMi4H9FUknlXN5+Xv1GP5FRRHQl1qjgAAHQ+xpaRayE74yOItPYjMazAjaR0aB+uQbQP7XVsyGG76zf+//bxxi9FqTNNPdYX3gAAAABJRU5ErkJggg==","probability":0.7,"id":"MOIST","ui_description":"The air feels extremely humid"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACeElEQVRYw+1ZPY/aMBh+XuskxMIWJiSiDh3p3+BH3Noo0uWWZmSwM9yYWxoklFv5Efc3ytihAokJNhbElA61I8dxEqJWVRLduxg52PHz8LwffqEsy9DGiKg09/Xb93ab/Cd7e30uHbY13r8h6PHpJRuPJjgcd5suEjSfLfzr7YLtekX/lCAvTABAPYgA8DQOSM2/vT7T49NLdjrvFTHv6KYtAWDquP52vSKpdErjAF6YZAqb4iKNg2aCNHIiywsFAFxvF0VOV4kpETV1XH88mpCOwzBuI+mhBTlqYzEeTcz5fUeJcdUH7cyi4rsRgMwLkwJJrOULRRoHuN4ufSCncLbr7QIJXLTZgLVQDyy/Rm/szjMrFd2tIK6NQgW3w3G3mc8Wfl/Imc8W/uG423hhkmkq4gZGu4IUW3IhGQsjnVkA+PL5EwC8y7EXZjlzZmDjtkDthQkepCr0RfpCU3748fMXACzl2AuznNmKTY7CC5OcKFYTe7gtxQ/ERAPWPBYxCzncIj3lgkjjgKaO63e1erbZ4bjbTB3XT+OAdCyWUMJNkpiFycgiPe6FyWAUJLFwi7tFppqYEZjr0p+Qm2en8753Wex03m/k9UK5WFM5wwGQqqRJczVTRUjjYEjxJy8YLV7BNQ5Ir4NERdrTJYkBpXnUkFPwGFYTxQvzSp5DSPOaq6EiFuXzrOKhrV6goWQxheUe7MxSejfWDgO4izXF1PxqxUyF1BFlSLPUUuhyu6MBg+luVOgHadcNobFrY5lU66BHJME4M1Vg07HnxSRrcKnCbV492K5XNHVcH39amssO87KE7Cbqfek6bCVFfDTtG/B+/O1Tb78BJeNHBz2xlr4AAAAASUVORK5CYII=","probability":1,"id":"FOG_OF_WAR_REAPPEARS","does_not_apply_to_biome":["mountain_hall"],"ui_description":"A mysterious darkness lingers in this place"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACQElEQVRYhe2ZMW/iMBTHf46osrAgdeaY2Vi6kENi60dhv0wMl3IDE93zUdgqQaZbumXmmE9iyRIx+AZiMEkodntEpPQnRUns95z3f3LsxBYAUkpMGQwGhbLpw9C8gQoZ/34R+bLlcmnVRgNAiEI7peQTOe4NZfMO4k0aWj21IqYPQ5lsYfpaTJQpDTgWHvkBQATgzSbeKcdxbyjXyT4x8/cGcEniTQrsYtWTZKpXCIGjGenGfaAf+UGk6nS05My50uRkzIH5OknDce8wDNjobeSclLGiD0SRH3gA3mwCQPOuEMjqw1IuQ0dd5GM20QvsepDyyRkfOambxWJBsj2qX9nFXCkrdZFsd7FrGOl1zhiXOpX0oKsnF7OxXpUgDxDZ8aQZPqlyNYBFfiDjTRp2W+7oP8RdCd2WO4o3aRj5gRqHjPXqr5gR991vAPPsXAs+EnMDDoNv2Yyl6hR/4z8Aj9m5FuRjttFr3YNuDesEebOJaDd37/QlAroE8SYN20135M0m1l/UXz3oDNYJivxArpP6zWLr5DCLRX5w8v9TCLE/gMOvhqnDreEAfH/+9abR8sfP/fVnmOZt9Dp6wTlH+BzTvI1eozFIb+gWZjFdrwPFj6Nz1P1fzEavA+YrilC65Noxdq6ejn6jYrfR2zhvUiS33FEI5BopidmIowTpo/dbTF9fRLaqqIqudVXxEaDddEdl69Imeo+cynY3hBBIKfdn/RW79kX7bssd5Rft37WrAae3flR5Wb16cJ22fWz5B9nLGg87FCHXAAAAAElFTkSuQmCC","probability":0.5,"id":"HIGH_GRAVITY","ui_description":"The air feels heavy..."},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACYUlEQVRYhe2ZMW+jMBTH/0bJhiIhdYyijJW3LF1KmbvkE/QD4P0yZbl0yUR28gH6CW5hDmS55TZ0YxRlPClSxdbBNwRTxyGNje4oXO4nIeD5Yb/3ZD/DgyCHcw4dPM87kT3dzfUerpmX71OiyuI4NuqjIy4IIaVBEvKy9vFoyntdG+k+DY1GrYmnuzl/fcvw7cf8JFC6dOSb5SQBgK8AZrloBuCZkNP+x6Mp32VbEZioqgF/k3SfAjjYWhak8EsMKP6yxcOzrGPJN2zxoDWwFJwIDQ1OTgQg2mXbcDyaniwPHX87FzVK6HVtVbSp0k8NDMVFic1aWIB+ggaA1WqlijaVRq6HjXwjbDfx1wKK3KPN61tmpN8EZJtN/LUuqxTJTHTOd9k2pA5lBvZ9KtShbJdtw+Uk0Zo6sr8W8J6s5IZz3NIbAIjycytQbTbx19JRlLP9z/QXADzm51ag2mzirwUAfuCeTVyc8+K4RrRykIwfuKRvD1hT357LSPdp2LcHzA9cAphNCOMAXRvGAbqGXUzm/wy6QAf4+MVJtPmBC+CwZaZrRLf0hqXrGiz8A8g2A2b+Gs+gf2GbN0EEKAHA82Mmtc+EfDlJEuCwi1GHtm4Xow4tdjEY+CsC5AL4aMGscx0A7f8Wg4G/8hI799BRcDzPU0sHQzNTa2UoLnpdWy0Xa/lb1IP8wMVykrg4TL97WVkkLEHJDBqqgqah2qzjL1sABDiuj+RZXOSb48jgvWjfhpIrgEcA6NsDJkqucRxr+0sIgVExm3N+NE2bXrSnDmVq0b7SX42qH6Ji4Db99jHlN84ESTxn3qyQAAAAAElFTkSuQmCC","probability":0.5,"id":"LOW_GRAVITY","ui_description":"The air feels light..."},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACu0lEQVRYw+1ZS27bMBB9IwdQtwaM7Br4Fj5CBRW+h24h8ha+h2FCPYJvESQ7w4C31iKdLkxKQ2rkwi1QSEEHCPwJzXBe3rw3HBMz45EgouGbh+tjm/yr+P5lcNhH8336qwM0b4yPZ+Cy300SoMOVsTgBxQv96RZ3ASLXAkDNZW5VcM7HAEwzSYAu+/6sCkjk2hqA5TIf3SIj14JcyxIU/8EQ5g44zWTB6f9xDc7HHZo3rbaMBMsTIrxmcm3HoBRdG8mOa5nLvF/z8Zz+odeJArQeO7MnBY3k3GGSaSWlIe2ZZZgZWJzmAE58tsUJzAxyrfF5GmV9xCIAyLjMkdSgBcDk2lq8byNKDhk0/ejPbGROXOZBUliyKOCSjekzAJtoUU/Ny36H5baaDTjLbYXLfie1NhVqRWZ6kb4TRpRczWWO8mcOAI1/nEXIM/uq6HIaKbVOcp7CE8Xqao8qC7Csy1oAKPzjLCI+cy7LzPgc61SkA3GyRNU1CySx0WeJNCejOFwEUMqWOrXAwDAuc8JqU022e9Ybxh1Wmyq0Kj4XmwAWyo7l6yyhF8m6FGVnfqNV86KPsHmZY0IMC6DOlAbJcJnTWPtNrr110XNzsfNRdTFh6aQIts3kItE5p3cxfLYIbpbkSgkWcaMofyFKygagyLWzt3mZS6geKR8SCy7zvg9KNIYVyzdDy5yjzQ/6u0HOEhPC4SqVG8qHgrt1ukQ/3uVt/nXi+KwBFFhtKv72lQQAUW5juWfCvVhBVd9g3nex+7ndMGDtNk/B2hJRJgDcsWc4cl1PnD1ibkNSZyJwxBUkupdpE0V5y9XLLx53TB2ksTMPpENpeaJOOnIwMRsa1mfxcuumgcL/TDWKoD8jc2kKjqY0wvVtwU2kJWPkAjuwf1liUx/aL7dVOrSX32qIfOt0FtQ52f+vfe7HL/PWct6ywmzLAAAAAElFTkSuQmCC","probability":0.2,"id":"CONDUCTIVE","does_not_apply_to_biome":["mountain_hall"],"ui_description":"The air smells ionized"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACUUlEQVRYw+1ZoW7DMBA9Rx0LaTQVVsVlJUVVcXC/IqCgIKikDSkqGBjIVwQXR0EjY8FTYTWlJKyVbmD1drnZWb1OkxPtSCSf49x7vvOdLwIRwUSm0+mXsfFiY7bIH8nTw1LwsTRNjdbo8IEwyQAAEABgO5uIupdH8yXedVw4HvLYRoLGiw2eziU8P25qcYRJJjdYbGeTik4gIoRJBtvZhJIjF0T+kvSg0XyJZbGXxOzATvEBAFyvH0iSqAfV4ZWcOGyylDUlJUyyip6Qs7OYHLlxu7LYx6P5EnV4LljXKi46moXXdV+967h86MVSggY1Nqswr/igw1hUEkR1aZrC6Vw2gZyKbadzWQkvftZQzFSnDTF+cNOXrtgN64TaTM5bJWaqc0xDLEwyPB7yuNsbBk0hp9sbBsdDHpNsdTVmRzKqCzGuG3r3AAC7y7MRorL5WszGHpQXrwAA/uXZCLnSZr0HmWaxlsrvELSdTYTr9QNbq2eVHA957Hr94Jubwb8H/diDLpXlSpfyqC5MMiyLfeOyWFnsK1ksTLKVrrShOupBUQ2bUQs9JqrxoIiHGC+xkdcM0ovakOZZtMjbPK+RVpQgyRgqDjKkc1qS5iOGDUh7p4KXhtgHOTUktSmLqcjhJH1W0rRxpCrJ23YXU12h6FNbSVNyVHWDouU6sJiXwTe2VzCqHIP2gwR3O9qKpOOs3WE7STqbhSoZcbwOO9WFbjI9+Z8fN8L1+gG8tzR9i3nxAcCnLVdTvOKWvxq2N+27vWHAm/Y3/9UwEfnhJv32MZU3xjxVkVwLVUMAAAAASUVORK5CYII=","probability":0,"id":"FREEZING","ui_description":"The air feels freezing"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACR0lEQVRYw+2ZsW7iQBCG/zWhuCKCwj2peAGfaCJRUwQh5RFSufcDWO6PniqPgBS5obaUBp1fgCrU54IoxRUI9hrPaTxZG/YiRWsu09jaXa/3/zw7Y4+V1ho2Nh6P37UtZz27ST7J7p9elWzLssxqjivZUMynAKABwI9S1XTx4+217vse1vl+4SKg5aynd8URD89vjTqK+ZQesPKjtNKntNYEJQaQlHBoQjqP/ShNuAc93l7rzfZAYFZw0yYAMBx0QoJEHlTMp416ASR+lEL9+nFHAyEGyXMAiO+fXhMGx1Uw70ARpOWsZ6X3SgzmF5ksAYC+7wHbA29/cRTMDZ30fa+i4Vy9HqclQZj6sizDrji2AU5lbbviKAN0bHr4so/HoDqaqgzYlaBWBuaV44DIiyajoBvypMM0G/WSZq9pK9VF/HW+X4yCbtiS+INR0A3X+X7BstXZmj0iJdJbzCnyvm/BdwBYlcdWmGnN52q29qDf+U8AmJTHVtiZa673IIPF+P8stgFU60F+lKrhoBO6+vZssnW+XwwHnfDEl8GXB32KBxXzqd5sD63LYpvt4d+z2JcH2XsQDC9UF5XmTdoaAVGZwxDI9AWneS0TEADNwXEP+gtHQuJ791KymIxHQrs2bjG66EQw41/GrbFTa67T7tUNNHkSYCy53jj+odq0djR5FVAtuSq5J3kpkreLcofrkOrWTPHGtFuUCZARkoQDAA/Pb6qsKlJTa0qutnrVR/5quF60HwXdUBbtP/xXw8boxm367WNrfwCwVTlw2AcxVQAAAABJRU5ErkJggg==","probability":0.6,"id":"HOT","does_not_apply_to_biome":["mountain_hall"],"ui_description":"The air feels hot"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACCElEQVRYw91ZIW/CQBh9V2AJWRYMCQKDIBgEZlg0CWDmZlHlVzB+xapmcRggQWOZQWAIAoMgwSwIkpFyM23Tlrv2DhJ215eQkgaO7728+971g1BKIYNGo3F1bzjKyS3yILy//ZDwvfl8LrVG+p4Cvj5faLZgYDk5WyoKNBzl6Gl/Qbd3JLeukb5HnPXCtgAbAGYqCrScnL1abxWJKdBm1XK3DClXpxHiqCmMDzMAWC/sSJGi+PIcNIj61WzBgOMcF1tFBSoFa+aCy5eEm7RPTfBU3axafgdtFXdRCUCzUk+Z5eqURLiHydeI+TAA0M2qxXCQXmDVLMLXYO1Dx3ID5z0AeAvtDh26nJytWjtj6iJOrZ0xl5OztTt0qCxfnhX6zusKv9lXAJg5Vy0gUDOXr8heIT5l8XT6BoCmc9UCkjUH+N6UYglEfIo5jYnVtBKZYqJ8DYa9YlMsQYjl6wnkUy02xdYLW7sUWy/sQIqJ8g04yPclbldPEkT4Bs5BnINTImNelK90imkf889yfNOhhkVDnb0fbmbF/Jic6i2q6gyIM/awKvWUWcyPiSxfkRQjIs81Oj6LifAVTTEPjJFrSfEzELN2Ub7pcFd3zgCu1T5CiwEATvuLTiLxahbiS24d2vumioC6k8UmAFTqKdOdJj5saN/tHYlvaK/sAfHfhvauSIBef/vI4g96wR0sxx0dEQAAAABJRU5ErkJggg==","probability":0.01,"id":"GOLD_VEIN","does_not_apply_to_biome":["snowcastle"],"ui_description":"You sense lucrative opportunities"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACCElEQVRYw91ZIW/CQBh9V2AJWRYMCQKDIBgEZlg0CWDmZlHlVzB+xapmcRggQWOZQWAIAoMgwSwIkpFyM23Tlrv2DhJ215eQkgaO7728+971g1BKIYNGo3F1bzjKyS3yILy//ZDwvfl8LrVG+p4Cvj5faLZgYDk5WyoKNBzl6Gl/Qbd3JLeukb5HnPXCtgAbAGYqCrScnL1abxWJKdBm1XK3DClXpxHiqCmMDzMAWC/sSJGi+PIcNIj61WzBgOMcF1tFBSoFa+aCy5eEm7RPTfBU3axafgdtFXdRCUCzUk+Z5eqURLiHydeI+TAA0M2qxXCQXmDVLMLXYO1Dx3ID5z0AeAvtDh26nJytWjtj6iJOrZ0xl5OztTt0qCxfnhX6zusKv9lXAJg5Vy0gUDOXr8heIT5l8XT6BoCmc9UCkjUH+N6UYglEfIo5jYnVtBKZYqJ8DYa9YlMsQYjl6wnkUy02xdYLW7sUWy/sQIqJ8g04yPclbldPEkT4Bs5BnINTImNelK90imkf889yfNOhhkVDnb0fbmbF/Jic6i2q6gyIM/awKvWUWcyPiSxfkRQjIs81Oj6LifAVTTEPjJFrSfEzELN2Ub7pcFd3zgCu1T5CiwEATvuLTiLxahbiS24d2vumioC6k8UmAFTqKdOdJj5saN/tHYlvaK/sAfHfhvauSIBef/vI4g96wR0sxx0dEQAAAABJRU5ErkJggg==","probability":0.00025,"id":"GOLD_VEIN_SUPER","apply_only_to_biome":["coalmine","coalmine_alt","excavationsite","snowcave"],"ui_description":"You sense extremely lucrative opportunities"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACMUlEQVRYw+1ZsW7CMBB9jhBbKrEQqVOl/ADf0CkbWxmZWmVi4QNKP4CFKWJja9mYmolvyA9E6lQpLEhkY3GXBDmXM07L4kQ9KUpk2da957tn+yKklACAw+GAJuZ5Xq1tsg4kLLSPl1jQtizLGo0dDocAgN4tDjwtH6Vw+zglWWQjQZN1IGV+xna+F3+do0bQbDcFALkab4SJnDw9lsTENhJ0SrKLryaSZrupBCBW402l3WH6SgCiGGAiJ7aVHGXh4jw9Rk/LR2kip8DOR1AZOUpHMdtN2UgSbp82fVlK0MMVnzlyysAQ78+f1QgqQktQkjiRk/m5DeRUfJP5WSfQNcxqmjkqk5Sk1XgDLtV0q2GzcT43waxqkKo7CxJ+lUlPSRbdjbywLeTcjbzwlGQRs9hGzFyKXTpzqu7fjwAgLt6tMJ3PTTBzKXbpzKVY+p0AQFC8W2E6n5tgdhhFp+HWaTNhdoiag4QbbcdqvBGuPwhtPT1rDoyR6w9C5shixOxoBiw4cjpqVzGrBFXyUfmWNCTz9Ni6XSxPj9wuJhWxXijfZg1STtZdN1lgNWoQNPnY2W2+CeZfi3SXtvlbRPpNJ9Id28WMmHtUsBR71UVRV+5iTTD3SMPClJue52GyDmhJwfpyR+k7udEbMfeuMKgVL1LuqDli5VZV97kRZsc0Mb2sAsB2vheuPwgBBMVjqwUAAtcfhFzJlcNG7c9F++18L5SivbUHxFuL9uL/tw9v5W+fHypKRBOmIobDAAAAAElFTkSuQmCC","probability":1,"id":"PLANT_INFESTED","does_not_apply_to_biome":["snowcastle","snowcave","rainforest","rainforest_open","mountain_hall"],"ui_description":"It smells like soil after rain"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACPklEQVRYw+1ZsWrrMBQ9MoYupUsggcKDeH60/Y1At3bK2uXZU9+Qvik0MXRqPbRTEvoBntot0N9IQ2cXCgEHsoQsme5bpKIqsqMkUGTTA0JGjpR7TnR1r24YEWE6ncIUtVptZez+6oxgIf7ePTN1LE1T4/nVahXuLgbc/Dml/YM9jEeTvo0C3V+d0WK+RHswZNuu4e4iTpLMhDAvNgo0Hk0+bd1WJEZEePh3TgC6ea3Z6jHhYpI4VgqjQcPzKn57MGTCxeIoWMv58vaJuQAgyAMIdX0cBaH8bfsHe6oB75YKU8+xmTVbvUzOone5AB2uWha6YkKapni8viiCOMK2OgAs5kv1gO7EUZDL+fL2KXQlAfJ8lCRldb+G9dDYbMTZUQY7/IVoHXVWHAU0Hk36RyeHflHEOTo59MejSZ+fO9iEs5OjKuOH85cP/Pp9DAAvvC8Esmzm3FY4awVShcjCx9srADR4XwhsarOshZP3krsUygqVm26TOJsu2mz1mOdVfFuz54yEse95FV9KZ4zhbLLdygYTbs46V1LH4yigJJkVLoolyWwliplwXoli0iK0JnksC7qcq3z9yBQo5KFOtNA0ZBYxzJtwdjTZI2meyxzmczm7aoSSL3MZoZAtri+oLFFsHWdZIDLNeUpyFzPi7CgpNmu2ep9NHhcTNCXXusW6fLFNsd2Is6uWNDSKdtWDazFfFkmkLJuNOLNti/ZFKLkCaACAqCYC31i0bw+GTCraW5sg7lq0Zz9/++TvoP+Knja73UzHXQAAAABJRU5ErkJggg==","probability":0.5,"id":"FURNISHED","does_not_apply_to_biome":["mountain_hall"],"ui_description":"It feels cozy in here"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACbklEQVRYw+WZMY/aMBTH/0bcxBKpOlhvQgws16FBYrwlS/sRbkPKlv06hAztzhbpNj5Cb/EXQIIOveUGlKkroEpZmBh8Q+3UeXFIuFuc9C9FESY27/3we7ZfmBACSvv9HpuFBwChG/AIBg0Gg0Lbj4dbAQv15fszo2273c747GbhhQAiN+Do9/tZO9MBPX39GALIgXEDXgpoORsL5/oK61Ua2whoMnX89HDC/eMLKwMkJ4Su8PO3XxmDLvkycgOuOgk1gBvwwj+xnI3FNjnGSAAA3EZA61Wa2apD0uDoM59J33MTpEPpaUSZushA/+D8BWMlHCkOgG+TY7ycjYUBDtOuLL3ozxVmkAJFQ02Xc30FOXOUflsK6CZn83kpn8/OIApKhVouhtPDqQlwcralhxPNP0ylEYPPlYBycGiirvFvWCdqs/SJQroIkBHOZuGJ9SqNJ1PHbwqcydTx16s0prm0DqROCRgVVoIO+uH2EwBweW+EymyWvgkNkqgClMs3KlZ1SH+efwKAJ++NkMlmbRU7l5cqc9B/rwIgLeeEpp20G3A2GvZ8W3fPJRvGeDTs+XTDW+WrEZA8k5R+bpPq+Np5w6Bimxwbt4ptk2NhFXtTiFVtnFqq6BJA9DQftXWZN5R0oneHWFuW+brqlrTP5X5gTuslbsBZergTbVjFpG+Zr3UAzbWNEtM6zitO8407i9X1tWPoxAhVpncylFxvLOaSs43YXumraQYx8nBoOn6QcoftkMpsruWrKQexcwUkALh/fGGyqqiabK0qegAwGvZ8U8m1jq+MvtWoUtuK9ibpbzW67zFA/XCTXvtcqlekjz3msbB8ngAAAABJRU5ErkJggg==","probability":0.75,"id":"BOOBY_TRAPPED","does_not_apply_to_biome":["mountain_hall"],"ui_description":"You feel wary"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACI0lEQVRYw+1ZsW6DMBQ8RwztkJWRRO2YfgjfkLVDUbIgdWiHSDykLhkqZQnK0rXf0A9pxlYNI6uHZHOHQuWA7YCSVgblJIRk4OXd8XzPOEwIgSbo9/uVsdv7ebMg/4SX5wdWHuOcN4rBjhFoPJ2Jy4s+Nul6ZaNAA28UbHccr8sndjKBQkoAgABgQRPSCTSezkSWpYUwb7ATPgC4rhcUIqkECikpeNKCJnvXHE1gMv2qJI6twkB+cVmWYjydCbmS6vJ1FNXz+0BISUVRALi8qPjQl6UCDQ05V2YMAAopgcy5ZwgeqQY559jueBvE2cttu+Mm/4l0F/YEUlWLVFU49DZshipnFbeyBr0DcUkRVGzS9WrgjYK2iDPwRsEmXa9CSkRTvzUJFOeltxf05voKAN7ycytgyFnkHONaAkklF+UHy49fvH98AoCfn1sBQ84Fv4JvZdo5CkVhUrSjiEv8mW6KMenmSLVwWtCEua4X2Lp6VmGTrleu6wULmjCFIZPUxeLyjOkZTCsGEGmMrRPIuckeRHVNWutBISUiy9LWdbEsS1VdrOJBxpX02YNO4EFdavN/4kFdavN1PMg58G3CFEtxdvc4F13oYhI/UVegWh7UlW+xYzyoqCBR9iDFluvQYl2GptxzbkKaNRUPcjQeJLf5CkrbHbaLpMtZLgrRdB0EnTgA8Lp8Yq7rBfjZ0vQt1sUH4Mtbrk25njftD4Cd//Yx4xvNZRPTA+yPUQAAAABJRU5ErkJggg==","probability":0.75,"id":"PERFORATED","does_not_apply_to_biome":["vault","vault_frozen","crypt","snowcave"],"ui_description":"It feels roomy in here"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACE0lEQVRYw+1ZMWjCQBR9F6TgJFJIhi7iqEvX7h2km4OTUJqpgXYVR+lYXDtksxQ6ZcjU4uDu2kVHcelwQhEnweV3iSW5XmJMsL2k/RBCLsnx/sv7/9/9MCLCYrFAXDMM49uYffFAUNCs11smjnHOY7+v6zoKaQDcn/epdFTEmE9sFQmyLx5otVmjO+qwpHMU0pAzXc62xAxVJGjMJ19Yk5KkpSRnqCo5vg83nC5n9v15n35MQaWjojg0V5SgSgTmwyiIc47VZp0FcgLYVpv1Xgk6oCDXdPxjfil+xW1z0ELar/GbJmKO4/P1y01AQSQ8CMk1XNOhMZ/YZ0bdygo5Z0bdGvOJ7ZqOLA/JfCYxxKISWODeyekxAAy9cyYsAvNOvzVfCG2ldSdcB+z97QMAGt45E7YLc5Tvmj90xFASYzKHxnb4Li3zvagZm4MWW7XXpOrqOWTBaNfKVSssKqJ81ySMshgT5c48n5kYMWIOIgDkmg6FkeSaDk2Xs8xVselyFlbF0By0mHePANCuHHQniUvKoWgoju+aJBH3/nCZ74kJXJOtIMOyfc7KfCx/2b4NMwB4bD/5d/NzxfmpAGjUylXr6vlyr+Kj63qydkce9mIH281LWq4VxdUThf0w/SCh3aE6SWGYD6MgAOiOOqxWrloAGt6hqjW2+SdpyzVxT7o76jBf017ZBWLapj37/+0TXcU+AWn3Az2BVly+AAAAAElFTkSuQmCC","probability":0.5,"id":"SPOOKY","does_not_apply_to_biome":["mountain_hall"],"ui_description":"The hair in the back of your neck stands up"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACYElEQVRYw+1ZMWjjMBR9MoHSyXBDoJvJEjJ0zZybmsVQr+Wmm5ypQ9eSyKVrh072dNPRsTkui7tlznpDyGKyHWQoeAqZ1EXy/Sh2WieXIpd+EAZZEf+9fOn9/82EEChjjUZjY+7+7K7cJu9kl09XTJ9LkqTUHrV9HLjp3Ar76BiTxTQykaD7szuRrpboj6/ZrnvU9iFnls4VMbGJBE0W08zXXUmy9iQnNpUc8sfFs3Qe3XRuxbtFkH10rE/NDSXI2eLzYSIoSRKkq2UVyFnzLV0tS1/QGxE06g0BgANQ4cjc0ONviCDjrcjnUW+4hhcAd0Mve8+EEJQYXrA/V0SNekMxWUyjdr3lTxbTc8MjCACcdr31S/nshh4jxBTiVURZBeQM5Mh+IDfEyekXAIjlsxKm+5xDzgZeiTm7gzhZCACBHHSOA8DfP88A0JXPSliOz2/Ga2lMqkWCnMuAnNcOKm4ahq14AXCLvBiQxVy7rLPwc0OPNW3HNzV7LkgYo6btZPdPCbzCwqe9KvNMCy2myZ5+zMQsnSsVi6sAUvoajXpDAeBrCbzM0nR/QBYy/XgBGH+AoBjnHLMNvJITbinZkxNBjuwF9Fb/CDJPhCkXrxt6KhX4J/OEJF321hKnqsu8iowc9Q5U5NA8if32H/OyyLXUmygYAODn9wdazRufSQPoNm3H//bjgpGS6lW8AFCjdYcWfqXrmqrUYhpm/l+r+ZyWq2N49Gzz/TD9IK3dYTpJRT4fph8EAP3xNWvajg+gK4ep1lX3z64tV7bPVw3Tm/btesvXm/Zlm2bs87PPdnsBSgsrFyuTfOMAAAAASUVORK5CYII=","probability":0.3,"id":"GRAVITY_FIELDS","does_not_apply_to_biome":["mountain_hall"],"ui_description":"You feel an invisible force pushing and pulling you"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACK0lEQVRYw+WZsWoCMRjH/8kJcptjwUUySQ8cCh1bEBykb+Ab3FsY38I38A2KQ0Goo+AgHHXp0UVwdDscjnTJlRiTO0+Lzdn/Ek1i+P4/Qr7kkwghUEaMsaO+pT8vt8iV9JA8Eb0vjuNSa9QuCeDVfxN3qGOSLsYuAlr6c7HFHi9Jj5y7Ru0SOLN0lYGZughoki5+Yj0XUu1COE6CUTQFgFm6OhvSWYDuUNe7vhwF1MqJuRygdRADwBAAl10cwKgdsaNDTs51HU4WWwsAttgbD+gi31SZK7RJXPadsoOcV07MQvEL3TeVFIUyqLbqGABgF2zEJF2MB95jWBU4A+8xnKSL8S7YCG33qN8PvGdjdB3EQ8MkKyT/OQGAqWwrIVPMFji67yHVBlH0o+TdB4C+bCshS8zc8vlgDi2YzAEQACPcnkbSW553kI/7z6GBINehqNlsHcTqPejLcRAtAP2u1wnbESNa9lJl5EAVksRAFO2IQU/1tyCLL66xGFHbLrGB2QUbMUtXlctis3R1lMVMPnXP1AZCT++3LJPXjAvNyV781JRZxTR/quc8QEayN5Tmi+5DxYBMakRN0vU6oas1IEvZY9z1OmEjaha95nnua16mPqK8Rcg/eIsd+NXTf81yeltJM8aw9Of6PcP5ckcWu/6il77Jr9aDttjnBuKiDDGXqweV0UvSI7KqmHW5WlnsA0DX64RXLbmqkGTR3tkL4p8V7TNIQLX+9imrb/fEEOPyoEhaAAAAAElFTkSuQmCC","probability":0.5,"id":"FUNGAL","does_not_apply_to_biome":["mountain_hall","snowcastle","snowcave","fungicave"],"ui_description":"The air is filled with fungal spores"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACOElEQVRYw+1ZMY7CMBCcRXBcQU0XxC/uC+gknhGEdCU8I5QgBM9AOvEFfoGgo6bAR7FXxA7GsU2S4uTArYQMURLtTDae2YWYGWWCiPIH55dyN/mr+HrPJVsWb9NLxvqY3jOOyHrC6sC4dgGxWQZJ0PzCaJ2AUY8c+BgAcRxVI8gbqwPjvFPEbIMkSGwAIXN1kPQoGg+qZyK/TxzkbIMl5/bgtjjvllgd2MCnME0k1koVlGjrLDt67Zrn7QMlqO/J2Y6taAUBYABT+VHva7rJtU51IOc+t9Yp26AVFg0fVyHIHfmnEX5UzLlR4BVLVBUBAC0EQ2yWaA/HtSGnPRxDbJa0EHqlTDV8jwmi9VEvPQAggygCgM+3NgBs5VqLsORMBjGkS7++aXs3ad3/qIu+fwQADORai7jPue3E5lQxU9I5jmbmhT4zVbfgOLKSYkj/jOMIxMyZY7ZVjtOB3nzQPnA++gAG6HyMC+HSfnIcZXuQVdJfKVzSX1rmaSHS6qmbip13poqVblYTQ/5eMXLYGz5Jt0j/s8i8TdKt0t/0SfpjyayvzBe1NbqK2WRwopy0kv5nUTEp6apLmNmkP1Mx3eMYficx1mfqxXLYbDw0PUaQtR4sofWROY6IiID5xXxC4Y87kI6L5RujS7rCSjYeqk0UW6d0UudIJMi4H9FUknlXN5+Xv1GP5FRRHQl1qjgAAHQ+xpaRayE74yOItPYjMazAjaR0aB+uQbQP7XVsyGG76zf+//bxxi9FqTNNPdYX3gAAAABJRU5ErkJggg==","probability":0.75,"id":"FLOODED","does_not_apply_to_biome":["mountain_hall","rainforest","rainforest_open","snowcave","vault"],"ui_description":"Where did all this water come from?"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACe0lEQVRYw+1ZPYvbMBh+XnGBbCFgMgVaMnRPf0X2G+vJx4Gpt/wByX/gNh+GUE++Mfv9ist+Q2ghUwiEbIEM7lDJyLJix7QU29y7KMiRrOfJ837oDWVZhiZGRKW57z++NdvkP9nzw0vpsI3x/g1Bj0/32WA0xH6zi9tI0GQ+9S+nM1bLNf1TgoLEBQD1IATAIy8lNf/88EKPT/fZcXtQxLyinbYAgPHM8VfLNUmlU+SlCBI3U9gUF5GX1hOkkRNaXigA4HI6K3LaSkyJqPHM8QejIek4DOM2ku4akKM2FoPR0Jz/2VJiPqsP2pnFle+GALIgcQsksYYvFJGX4nI6d4GcwtkupzMkcNFkA9ZAPbD8Gp2xG8+sVHSzgrg2ChXc9ptdPJlP/a6QM5lP/f1mFweJm2kq4gZGu4IUW3IhGQtDnVkA+PLpKwC8yrETZjlzZmDjtkAdJC7upCr0RfpCU354//UGAAs5dsIsZ7Zik6MIEjcnilXEHm5L8T0xUYM1j0XMQg63SE+5ICIvpfHM8dtaPdtsv9nF45njR15KOhZLKOEmSczCZGiRHg8StzcKkli4xd1CU03MCMxV6U/IzbPj9tC5LHbcHmJ5vVAuVlfOcACkKmnSXM1UESIv7VP8yQtGi1dwjQPS6yBxJe3pkkSP0jwqyCl4DKuI4oV5Jc8+pHnN1XAlFuXz7MpDW71AfcliCsst2Jml9K6tHXpwF6uLqfnVipkKqSLKkGappdDmdkcNBtPdqNAP0q4bQmPXxjKp1kGHSIJxZrqCTceeF5OsxqUKt3n1YLVc03jm+PjT0ly0mJcFZDdR70tXYSsp4qNpX4P342+favsNYpBG86ssL2UAAAAASUVORK5CYII=","probability":0.5,"id":"GAS_FLOODED","does_not_apply_to_biome":["mountain_hall","excavationsite","snowcave","snowcastle","vault","crypt"],"ui_description":"There's a smell of gas in the air"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACWklEQVRYw+2ZPXLiQBCF3wwEW4sc2c4J5QAjn0IcwgfQLTS6BQfgEOgUSCZAoXPsyPKWA2A2QdrWqPVDlWtrZLsTCg0M3Z/edLcaobXGJSaEqF1z0pfLNvlPls9vas5eGu/YSbK2dbqbyj03oouTzU4LeYtDulraCMhJX7Q+7fH+cFcB5SRZCEDR+94IqAOMAhBxH5hsdvq4jQswaxsBHdJV6asBKSJxhSRe0QWoE4wBx0owxNYAcNzGHCQTFgtqbMBpBVPqUd6al54tBTRt8bkLlAAAWUlq/3KMyj0XueeCS3L6tB8CnIpv+rRnEzSJUxkMQAFpI2mpCxVkvfXw2YxfmwqK+vzQ1dOrPqSr5Xj+GAwFznj+GBzS1fLq6bVvjY+ogkr1kJLfqCD/4zcArM+vg7CePnMMtGxQTyOg+NcfAFicXwdhPX1WnIqk0QjiuxtN2AAgJptdWdIoIK7DLtadJKN90LPlMU8BLEYzP8g9V3TFZqxrabTg315BJgPJ9APse1rFjtt4cFXsuI3ZKtYVt8SPtVoBKHSSDE6SqU8qmUMs88URU+djFhaABMnanYC+cJk3y70CIOgRC2mialLT2/21GM38wNYZUMPYYzma+cHb/bXoUE2NhSSP96pvs/gFn8W42AWXpENK0kmyWvPIjFynlvdAjb7nnls5KTT3cPMgQZ7LirmQIhuBjg4GBIn1mdx4RY6VIizKTprdj3w5opsWd2EII1cACwAYzfygmCZqrcHkmxoYTkEw1GTOiSqw3h/uBBnaW9sgckN7AwoLplz4+dun3f4CkJ8081z5pLwAAAAASUVORK5CYII=","probability":0.1,"id":"SHIELDED","does_not_apply_to_biome":["mountain_hall","excavationsite","snowcave"],"ui_description":"This place feels exceptionally secure"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACUklEQVRYhd1ZoXajQBS9Az1nq3oSlcXlC8KpicY1Jrp+FYr9gIoS0fhFRa1fHZM6NKZn8gVxLIqcqEbArOjMhENhy0wbknAN5DHz5t3L8N7MhEARo9HonY1G90zVTxuwx39I2bZer5V8XH0mgJdnhxn9AbbhcvEZP8cCje5Znia4vQvfCdUU2gK9PDtsRyMhzErXzzGxDZcA3mLVFUlLoII4ZylMASsA2NFIWyQtgYz+oGza6PhpAUNxUxFzIxiqHSilyNOkaNpojdwONuImTxNQSpUdKAsE6L+NU+JLZ1AWe/Vlez9n23C56DlTV2vEE6DnTN1tuFxgP6/lVcfZ+N/Dqs75tx8AsOLXi0BVzE05GwBgWgFhr0+MP3g0+gNksfcIAOz1iZlWILO/sf8NABN+vQhUxdyUs6xi5PqB8AY+N/lZ7IFcP2gvss4dTTjLG9GwmMx4tfJNK5gJG2MM+d+fxXXQ5pgkvgBDAJMbe+wa338RQg7vuwnnK/mtVWR5bvOz2POBt2l5JBKtQoWzYVoBMa2A5Gnil9Y3yNMEeZr4og0AYD9nOxpdXBXb0UhWMRXOsszzKSU7VH1eXUMTzlIgnrlnKCQs0wpmItMLdKXMA804y3WQyNymFczyNBHqikx/6NCRMt+Uc2XSzWKP1SXkLlWxIuo4V241PqpWXdyL1XFW3qzatl02DVV9tIhh8UdF7B9C6zyoXBrLgZwjKmJuBC2Bbu9Cwk8VhelcTxYnAHBjj91Wj1yBg0j80F7XzVHRc6buyQ7tAciBL+lvH1X8A6yqdIf/6vb4AAAAAElFTkSuQmCC","probability":0.2,"id":"PROTECTION_FIELDS","requires_flag":"moon_is_sun","does_not_apply_to_biome":["mountain_hall"],"ui_description":"Everything is glowing in a mysterious light...!"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAACL0lEQVRYhe2ZsXLiMBCGf8mpM3R2yQscTIqkzKSkOLekTxpX3ANQxBTQxxVN0ofWV1BmKAkzGXMvQGl3DD3WFSc5ijGJpASDmfsamx1ptfsbtPJCoEmj0diwPVxHTNdPGdw+NUneNp/PtXycfCWA/tUrc2yKUbgcfsXPrni4jlicpOg+n20IpYqxQP2rVzaZroQwY1M/u2QULgH8i9VUJCOBJHEOUhiJMQBMpitjkYwEcmyaNy1M/JRAXdwUxKyE9qwoihAnqWxaGK1cDgtxEycpoijSdmAkq+nT2CelfYNmA7BRuBy23ZpntOIeaLs1bxQuh7MBtI8jhQKFnfVWRz9uAABjfq0EKjFvyzkT6KXPGB9459gUYWd9J9sFfx4BAC1+rQTbYlbJOati511C+ACfm/yws8Z5lxgfsg4dlZyzGzFQ3sx4tfLdwOoJG2MMv3+l8jloscskvoE6gNblxan3854SIj1vlZwpH8gcm/r5nd6xKRyb+h/tSVVFNWcKAG5gkThJ/dz5BnGSIk5S3w2sTPbZAGwyXVWuik2mq3dVTDVnKk3oAcgmFP28jg2VnN9VMTGBm3w3sHr5KnZMZV4l50wgsXO7gdWLk1Sou1HFjqnMq+SsXcKPqYqp8P9d7BO0ZzWbzbypbrRyOdTlDwWxf4pRPyhfGvOBHCIFMSthJFD3+YzwrqIwHWpnsQUAlxenXqktV+BNJN60N3WzU9puzdtb0x5AtnCV/vbR5S+uj0SAfmTuEgAAAABJRU5ErkJggg==","probability":0.2,"id":"OMINOUS","requires_flag":"darkmoon_is_darksun","does_not_apply_to_biome":["mountain_hall","coalmine","excavationsite"],"ui_description":"There's an ominous atmosphere here...!"},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAABtklEQVRYhe2YMU/CQBTH/0c6oQMhjg6dnAwmspHYmUQ/SBfb0c/AiCz9IJgwYyIbiYaJqYOjIQzARPIc4MhZMb1X7qBFftNBj+v/PXrv/3oCBrh9fCMT65hm2GmIXdfYaYFa0CWHqpgu+tGuQmxQKXv+Ukzw8fyQOU4BAGFrTADw+vmFYach5BMhx3eXFwCA9tPV5ka1oEuz+UgmppdVgGWaAHB+du2rSeLEu0mQGvw21DlKcvKamCRNNUmceB1glck01DkOVZOXY6bgfeHKgaqZE28J0Ctmcg4RYSkm6qVYS+phiOVgKSYgWnkJJ94SoOdC6pwtT1DuUTVz4i1xb1QPBjRd9KNK2fO5vz0UlbLnTxf9qB4M2O0Ie4utK3xPVvoikNRsdYuti1dTp9DlhaRmq1vsv8HuMIkIN+GL2gfFxlWZxcW6D3pv3wsheCFncrGiY93FZvNR4VxsNh/tx8WOAasudgw2b3WLHYPNczi5WAqnd7E/+LHFdOqJnLPlH3B1RR4AV/0gtXPideQXYWtMaSdsksRxxy8heSSpWTfezNZd5CNXDqdD+xSMNH95fQ0x0dx+A+7wOQ3F2i1iAAAAAElFTkSuQmCC","probability":0.1,"id":"INVISIBILITY","does_not_apply_to_biome":["mountain_hall","coalmine"],"ui_description":"You suddenly have trouble focusing your eyes..."},{"ui_decoration_file":"iVBORw0KGgoAAAANSUhEUgAAAEgAAAAYCAYAAABZY7uwAAAChUlEQVRYheVZMZKiQBR9bG1EIqnMVmmVuSYcwgkMZoMx8QBcYCcYYw3cC3CASTRYAwI9BAnmVknVimmTmPYG0mwD3dCMK4XuS8Cm/Pz/eP1/928NFdHv93Nj46lPq9qpA8vZQMuO7Xa7Sja+XuNAb7KhVseEGxHnGju3wnjqUy8Isf94zhGlilKCZktKAWA61lIv6U029GTojhsRANh+1oFbwo0IYOjoTTY0S5Isriy+FD2cLSn1gjBlEPhLDi7ENJKcGFsA25OhO73JJvGfxeIFYSouEUoVZHVMeEEIq2OmxmLlMBwqOl4XuuzG6pjYcw+yMckgVdDL4pgwa3XMRIq+74OpKsZB3d/acWA3XhDC930Al2nFk8PHmoVQQS+LI2XKWb895eaoQEGNR1Yt7IOzWLE4UlGsOQWVkTNfgboRcUYtw/6H/t8Uo5ZhuxFx5ivklLJ+e9LYdBMpKUVQGTkA8G0AANjG17tAmc9FJCUEqZADAL8v03gYX+8CKj7LSEoIYnO0iJxHB4udz1cJQawyFWV0AHh/hdYmZ7upq2cR3Ig4bXK2319R+PFZ7HyVTggqS1aPDlmKSSVpFZLmK9CTod9dFTsZurCKAcX5N1fm/zcllRUn4Uq6iKRHKvMqlVu61ZCR9ChlXnVZU1rSs4Yopfj+M+R384drA7gxugCGbXK2f/0wNU3TlMkBStodMkMqu+Cmgfe5Sp4t3M2LyBkMcsmn+zmXa0GX/8H7rkqStB9UJEEvCAFDlzrSRGRaNADilXMshLXkf8L5N1tSKmtFsqY911UEmttVHAJAm5xt1nKVNe1lMVfed/GnGk1v2o9ahp1t2td6qrH/eNb2uK9jn6r4A2DOnHejE8pCAAAAAElFTkSuQmCC","probability":0.05,"id":"WORMY","does_not_apply_to_biome":["mountain_hall","coalmine"],"ui_description":"You hear creepy crawling everywhere!"}] -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | Noita Seed Tool 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 |

Noita Seed Tool

26 |
27 |
28 | 29 | 30 | 31 | 34 |
35 |
36 |
37 |
38 | 51 |
52 |
53 | 56 | 57 | 60 |
61 |
62 | 63 |
64 | Search for Seed 65 |
66 |
67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 89 | 209 | 223 | 224 | 225 | 226 | 227 | 265 | 266 | 267 |
TypeRequirement
76 | 80 | 84 | 88 | 90 |
91 | 98 |
99 |
100 | 107 |
108 |
109 | 116 |
117 |
118 | 126 |
127 |
128 | 136 |
137 | 145 |
146 | 153 |
154 |
155 | 164 |
165 | 176 |
177 | 186 |
187 |
188 | 197 |
198 | 207 |
208 |
210 |
211 | 216 | 221 |
222 |
228 |
229 | 230 |
231 |
232 |
Seed Criteria: {{ seedCriteriaText }}
233 |
234 |
235 |
236 |
237 |
238 | 239 |
240 |
241 |
242 |
243 | 244 | 245 | 246 | Seed limit reached 247 |
248 |
249 | 253 |
254 | 255 |
256 |
257 |
258 | Searched seeds:
{{ seedSearchCountStr }}
259 |
260 | 261 |
262 |
263 |
264 |
268 |
269 |
270 |
271 |
272 |

Perks (show deck)

273 |
274 |
275 | 276 | {{ perkWorldOffsetText }} 277 | 278 |
279 |
280 |
290 | 291 |
292 |
293 |
294 | {{ perkRerolls[perkWorldOffset]?.[level] }} 295 |
296 |
297 |
298 |
299 |
300 |
301 |

Fungal Shifts

302 |
303 |
{{ level + 1 }}
304 |
305 |
{{ translateMaterial(mat) }}
306 |
307 |
308 |
309 |
310 |
311 |
{{ translateMaterial(shift.to) }}
312 |
313 |
314 |
315 |
316 |

Biome Modifiers

317 |
318 |
319 | {{ translateBiome(biome) }} 320 |
321 |
322 |
323 |
324 |
325 |
326 | {{ translateModifier(modifier.id) }} 327 |
328 |
329 |
330 | 331 |

Dark Cave

332 |
333 |
334 |
335 |
336 |
337 |
Rain Type
338 |
{{ seedInfo.rainType[0] ? seedInfo.rainType[1] : 'no rain' }}
339 |
{{ (seedInfo.rainType[2] * 100).toFixed(10) + '% likely' }}
340 |
341 |
342 |
Starting Flask
343 |
{{ translateMaterial(seedInfo.startingFlask) }}
344 | 345 |
346 |
347 |
Starting Spell
348 |
{{ seedInfo.startingSpell }}
349 | 350 |
351 |
352 |
Starting Bomb Spell
353 |
{{ seedInfo.startingBombSpell }}
354 | 355 |
356 |
357 |
358 |
359 |
360 | 363 |
364 |
365 | 366 | 367 | 368 | -------------------------------------------------------------------------------- /requirements.js: -------------------------------------------------------------------------------- 1 | let _G = {}; 2 | function GlobalsGetValue(varname, defaultvalue) { 3 | return _G[varname] || defaultvalue; 4 | } 5 | function GlobalsSetValue(varname, varvalue) { 6 | _G[varname] = varvalue 7 | } 8 | function GameAddFlagRun(flag) { 9 | _G["FLAG_" + flag] = true 10 | } 11 | function GameHasFlagRun(flag) { 12 | return _G["FLAG_" + flag] != null 13 | } 14 | 15 | let noitaRandomPromise = new Promise(resolve => { 16 | emscripten_ready = function() { 17 | SetWorldSeed = Module.cwrap('SetWorldSeed', null, ['number']); 18 | SetRandomSeed = Module.cwrap('SetRandomSeed', null, ['number', 'number']); 19 | Random = Module.cwrap('Random', "number", ['number', 'number']); 20 | ProceduralRandomf = Module.cwrap('ProceduralRandomf', "number", ['number', 'number', 'number', 'number']); 21 | ProceduralRandomi = Module.cwrap('ProceduralRandomi', "number", ['number', 'number', 'number', 'number']); 22 | resolve(); 23 | } 24 | }); 25 | 26 | // not technically a info provider, but required for basically every other info provider. 27 | var loadingInfoProviders = [noitaRandomPromise]; 28 | 29 | class InfoProvider { 30 | constructor() { 31 | loadingInfoProviders.push(this.ready = this.load()); 32 | } 33 | async load() { 34 | 35 | } 36 | provide() { 37 | throw new Error("InfoProvider.provide called()"); 38 | } 39 | async loadAsync(fn) { 40 | let r = await fetch(fn); 41 | let j = await r.json(); 42 | return j; 43 | } 44 | } 45 | 46 | class RainInfoProvider extends InfoProvider { 47 | async load() { 48 | this.rainTypes = await this.loadAsync("data/rain.json"); 49 | } 50 | provide() { 51 | let snows = false; // TODO.......... 52 | let rainfall_chance = 1 / 15; 53 | let rnd = {x: 7893434, y: 3458934} 54 | let rains = !snows && (random_next(rnd, 0.0, 1.0) <= rainfall_chance) //-- rain is based on world seed 55 | if (!rains) return [rains, null, 1 - rainfall_chance]; 56 | let pickedRain = pick_random_from_table_backwards(this.rainTypes, rnd); 57 | return [rains, pickedRain.rain_material, rainfall_chance * pickedRain.chance]; 58 | } 59 | } 60 | 61 | class WaterCaveInfoProvider extends InfoProvider { 62 | provide() { 63 | return Math.floor(ProceduralRandomi(-2048, 515, 1, 6)) 64 | } 65 | } 66 | 67 | class StartingFlaskInfoProvider extends InfoProvider { 68 | async load() { 69 | this.materials = await this.loadAsync("data/starting-flask-materials.json"); 70 | } 71 | provide() { 72 | SetRandomSeed(-4.5, -4); 73 | 74 | let material = "unknown"; 75 | 76 | let res = Random(1, 100); 77 | 78 | if (res <= 65) { 79 | res = Random(1, 100); 80 | if (res <= 10) { 81 | material = "mud"; 82 | } 83 | else if (res <= 20) { 84 | material = "water_swamp"; 85 | } 86 | else if (res <= 30) { 87 | material = "water_salt"; 88 | } 89 | else if (res <= 40) { 90 | material = "swamp"; 91 | } 92 | else if (res <= 50) { 93 | material = "snow"; 94 | } 95 | else { 96 | material = "water"; 97 | } 98 | } 99 | else if (res <= 70) { 100 | material = "blood"; 101 | } 102 | else if (res <= 99) { 103 | res = Random(0, 100); 104 | material = randomFromArray(["acid", "magic_liquid_polymorph", "magic_liquid_random_polymorph", "magic_liquid_berserk", "magic_liquid_charm", "magic_liquid_movement_faster"]); 105 | } 106 | else { 107 | // one in a million shot 108 | res = Random(0, 100000); 109 | if (res == 666) material = "urine"; 110 | else if (res == 79) material = "gold"; 111 | else material = randomFromArray(["slime", "gunpowder_unstable"]); 112 | } 113 | return material; 114 | } 115 | } 116 | 117 | class StartingSpellInfoProvider extends InfoProvider { 118 | async load() { 119 | this.spells = ["LIGHT_BULLET", "SPITTER", "RUBBER_BALL", "BOUNCY_ORB"]; 120 | } 121 | provide() { 122 | SetRandomSeed(0, -11); 123 | function get_random_between_range(target) { 124 | var minval = target[1] 125 | var maxval = target[2] 126 | return Random(minval, maxval) 127 | } 128 | 129 | function get_random_from(target) { 130 | var rnd = Random(0, target.length - 1) 131 | 132 | return String(target[rnd]) 133 | } 134 | 135 | var gun = { }; 136 | gun.name = ["Bolt staff"]; 137 | gun.deck_capacity = [2,3]; 138 | gun.actions_per_round = 1; 139 | gun.reload_time = [20,28]; 140 | gun.shuffle_deck_when_empty = 0; 141 | gun.fire_rate_wait = [9,15]; 142 | gun.spread_degrees = 0; 143 | gun.speed_multiplier = 1; 144 | gun.mana_charge_speed = [25,40]; 145 | gun.mana_max = [80,130]; 146 | // Note(Petri): Removed DYNAMITE 147 | gun.actions = ["SPITTER", "RUBBER_BALL", "BOUNCY_ORB"]; 148 | 149 | var mana_max = get_random_between_range( gun.mana_max ); 150 | var deck_capacity = get_random_between_range( gun.deck_capacity ); 151 | 152 | var ui_name = get_random_from( gun.name ); 153 | 154 | var gun_config_reload_time = get_random_between_range( gun.reload_time ); 155 | var gunaction_config_fire_rate_wait = get_random_between_range( gun.fire_rate_wait ); 156 | var mana_charge_speed = get_random_between_range( gun.mana_charge_speed); 157 | 158 | var gun_config_actions_per_round = gun.actions_per_round; 159 | var gun_config_deck_capacity = deck_capacity; 160 | var gun_config_shuffle_deck_when_empty = gun.shuffle_deck_when_empty; 161 | var gunaction_config_spread_degrees = gun.spread_degrees; 162 | var gunaction_config_speed_multiplier = gun.speed_multiplier; 163 | 164 | var mana_max = mana_max; 165 | var mana = mana_max; 166 | 167 | var action_count = Math.min(Random(1,3), Number(deck_capacity)); 168 | var gun_action = "LIGHT_BULLET"; 169 | 170 | if( Random(1,100) < 50 ) { 171 | gun_action = get_random_from( gun.actions ) 172 | } 173 | return gun_action; 174 | } 175 | } 176 | 177 | class StartingBombSpellInfoProvider extends InfoProvider { 178 | async load() { 179 | this.spells = ["BOMB", "DYNAMITE", "MINE", "ROCKET", "GRENADE"]; 180 | } 181 | provide() { 182 | SetRandomSeed(-1, 0); 183 | 184 | let res = Random(80, 110); 185 | res = Random(1, 1); 186 | res = Random(1, 10); 187 | res = Random(3, 8); 188 | res = Random(5, 20); 189 | 190 | res = Random(1, 100); 191 | 192 | let actions = this.spells; 193 | 194 | if (res < 50) { 195 | return randomFromArray(actions); 196 | } 197 | else { 198 | return "BOMB"; 199 | } 200 | } 201 | } 202 | 203 | class PerkInfoProvider extends InfoProvider { 204 | async load() { 205 | this.perks = await this.loadAsync("data/perks.json"); 206 | this.temples = await this.loadAsync("data/temple-locations.json"); 207 | } 208 | _getReroll(perkDeck, amountOfPerks) { 209 | var perks = perkDeck; 210 | var perk_count = amountOfPerks; 211 | let perk_reroll_perks = () => { 212 | let result = []; 213 | 214 | for (var i = 0; i < perk_count; i++) { 215 | var next_perk_index = Number( GlobalsGetValue( "TEMPLE_REROLL_PERK_INDEX", String(perks.length - 1) ) ); 216 | var perk_id = perks[next_perk_index]; 217 | 218 | while( !perk_id ) { 219 | // if we over flow 220 | perks[next_perk_index] = "LEGGY_FEET" 221 | next_perk_index--; 222 | if (next_perk_index < 0) { 223 | next_perk_index = perks.length - 1; 224 | } 225 | perk_id = perks[next_perk_index]; 226 | } 227 | 228 | next_perk_index--; 229 | if (next_perk_index < 0) { 230 | next_perk_index = perks.length - 1; 231 | } 232 | 233 | GlobalsSetValue( "TEMPLE_REROLL_PERK_INDEX", String(next_perk_index) ) 234 | 235 | GameAddFlagRun( this.get_perk_flag_name(perk_id) ) 236 | result.push(perk_id); 237 | } 238 | return result; 239 | } 240 | return perk_reroll_perks(); 241 | } 242 | get_perk_flag_name( perk_id ) { 243 | return "PERK_" + perk_id 244 | } 245 | get_perk_picked_flag_name( perk_id ) { 246 | return "PERK_PICKED_" + perk_id 247 | } 248 | perk_spawn_many( perks, x, y, perkCount ) { 249 | let result = []; 250 | let perk_count = parseFloat( GlobalsGetValue( "TEMPLE_PERK_COUNT", "3" ) ) 251 | 252 | let count = perkCount ?? perk_count; 253 | 254 | for (let i = 0; i < count; i++) { 255 | let next_perk_index = parseFloat( GlobalsGetValue( "TEMPLE_NEXT_PERK_INDEX", "0" ) ); 256 | let perk_id = perks[next_perk_index]; 257 | 258 | //while( perk_id == undefined || perk_id == "" ) { 259 | // // if we over flow 260 | // perks[next_perk_index] = "LEGGY_FEET" 261 | // next_perk_index = next_perk_index + 1 262 | // if (next_perk_index > perks.length) then 263 | // next_perk_index = 0; 264 | // } 265 | // perk_id = perks[next_perk_index]; 266 | //} 267 | 268 | next_perk_index++; 269 | if (next_perk_index >= perks.length) { 270 | next_perk_index = 0; 271 | } 272 | GlobalsSetValue( "TEMPLE_NEXT_PERK_INDEX", String(next_perk_index) ); 273 | 274 | GameAddFlagRun( this.get_perk_flag_name(perk_id) ); 275 | 276 | const perk = this.perks.find(f => f.id === perk_id); 277 | 278 | result.push({ 279 | perk_id, 280 | perk, 281 | }) 282 | } 283 | return result; 284 | } 285 | getPerkDeck(returnPerkObjects) { 286 | let perk_list = this.perks; 287 | 288 | var shuffle_table = function( t ) { 289 | //assert( t, "shuffle_table() expected a table, got nil" ) 290 | var iterations = t.length - 1; 291 | var j; 292 | 293 | for (var i = iterations; i >= 1; i--) { 294 | //for i = iterations, 2, -1 do 295 | j = Random(0, i); 296 | //console.log("j", j); 297 | var tmp = t[i]; 298 | t[i] = t[j]; 299 | t[j] = tmp; 300 | //[t[i], t[j]] = [t[j], t[i]]; 301 | } 302 | } 303 | 304 | var table_contains = function(table, element) { 305 | return Object.values(table).indexOf(element) !== -1 306 | }; 307 | 308 | // this generates global perk spawn order for current world seed 309 | let perk_get_spawn_order = ( ignore_these_ ) => { 310 | // this function should return the same list in the same order no matter when or where during a run it is called. 311 | // the expection is that some of the elements in the list can be set to "" to indicate that they're used 312 | 313 | // 1) Create a Deck from all the perks, add multiple of stackable 314 | // 2) Shuffle the Deck 315 | // 3) Remove duplicate perks that are too close to each other 316 | 317 | // NON DETERMISTIC THINGS ARE ALLOWED TO HAPPEN 318 | // 4) Go through the perk list and "" the perks we've picked up 319 | 320 | var ignore_these = ignore_these_ || {}; 321 | 322 | var MIN_DISTANCE_BETWEEN_DUPLICATE_PERKS = 4; 323 | var DEFAULT_MAX_STACKABLE_PERK_COUNT = 128; 324 | 325 | SetRandomSeed( 1, 2 ); 326 | 327 | // 1) Create a Deck from all the perks, add multiple of stackable 328 | // create the perk pool 329 | // var perk_pool = {} 330 | var perk_deck = []; 331 | var stackable_distances = {}; 332 | var stackable_count = {}; // -1 = NON_STACKABLE otherwise the result is how many times can be stacked 333 | 334 | // function create_perk_pool 335 | for (var i = 0; i < perk_list.length; i++) { 336 | var perk_data = perk_list[i]; 337 | if ( ( table_contains( ignore_these, perk_data.id ) == false ) && ( !perk_data.not_in_default_perk_pool ) ) { 338 | var perk_name = perk_data.id; 339 | var how_many_times = 1; 340 | stackable_distances[ perk_name ] = -1; 341 | stackable_count[ perk_name ] = -1; 342 | 343 | if ( perk_data.stackable == true ) { 344 | var max_perks = Random( 1, 2 ); 345 | // TODO( Petri ): We need a new variable that indicates how many times they can appear in the pool 346 | if( perk_data.max_in_perk_pool ) { 347 | max_perks = Random( 1, perk_data.max_in_perk_pool ); 348 | } 349 | 350 | if( perk_data.stackable_maximum ) { 351 | stackable_count[ perk_name ] = perk_data.stackable_maximum; 352 | } else { 353 | stackable_count[ perk_name ] = DEFAULT_MAX_STACKABLE_PERK_COUNT; 354 | } 355 | 356 | if( perk_data.stackable_is_rare == true ) { 357 | max_perks = 1; 358 | } 359 | 360 | stackable_distances[ perk_name ] = perk_data.stackable_how_often_reappears || MIN_DISTANCE_BETWEEN_DUPLICATE_PERKS; 361 | 362 | how_many_times = Random( 1, max_perks ); 363 | } 364 | 365 | for (var j = 1; j <= how_many_times; j++) { 366 | perk_deck.push(perk_name); 367 | } 368 | } 369 | } 370 | 371 | //console.log("STEP 1", perk_deck); 372 | 373 | // 2) Shuffle the Deck 374 | shuffle_table( perk_deck ); 375 | 376 | //console.log("STEP 2", perk_deck); 377 | 378 | // 3) Remove duplicate perks that are too close to each other 379 | // we need to do this in reverse, since otherwise table.remove might cause the iterator to bug out 380 | for (var i = perk_deck.length - 1; i >= 0; i--) { 381 | var perk = perk_deck[i]; 382 | if( stackable_distances[ perk ] != -1 ) { 383 | 384 | var min_distance = stackable_distances[ perk ]; 385 | var remove_me = false; 386 | 387 | // ensure stackable perks are not spawned too close to each other 388 | for (var ri = i - min_distance; ri < i; ri++) { 389 | if (ri >= 0 && perk_deck[ri] == perk) { 390 | remove_me = true; 391 | break; 392 | } 393 | } 394 | 395 | if( remove_me ) { perk_deck.splice(i, 1); } 396 | } 397 | } 398 | 399 | //console.log("STEP 3", perk_deck); 400 | 401 | // NON DETERMISTIC THINGS ARE ALLOWED TO HAPPEN 402 | // 4) Go through the perk list and "" the perks we've picked up 403 | // remove non-stackable perks already collected from the list 404 | for (var i = 0; i < perk_deck.length; i++) { 405 | var perk_name = perk_deck[i]; 406 | var flag_name = this.get_perk_picked_flag_name( perk_name ); 407 | var pickup_count = Number( GlobalsGetValue( flag_name + "_PICKUP_COUNT", "0" ) ); 408 | 409 | // GameHasFlagRun( flag_name ) - this is if its ever been spawned 410 | // has been picked up 411 | if( ( pickup_count > 0 ) ) { 412 | var stack_count = stackable_count[ perk_name ] || -1; 413 | // print( perk_name .. ": " .. tostring( stack_count ) ) 414 | if( ( stack_count == -1 ) || ( pickup_count >= stack_count ) ) { 415 | perk_deck[i] = ""; 416 | } 417 | } 418 | } 419 | 420 | //console.log("STEP 4", perk_deck); 421 | 422 | // DEBUG 423 | if ( false ) { 424 | for (var i = 0; i < perk_deck.length; i++) { 425 | var perk = perk_deck[i]; 426 | console.log( String( i ) + ": " + perk ) 427 | } 428 | } 429 | 430 | return perk_deck; 431 | } 432 | let result = perk_get_spawn_order(); 433 | if (returnPerkObjects) { 434 | for (let i = 0; i < result.length; i++) { 435 | result[i] = this.perks.find(f => f.id === result[i]); 436 | } 437 | } 438 | return result; 439 | } 440 | perkLottery(allPerkPicks, perks, worldOffset) { 441 | const worldPerkPicks = allPerkPicks[worldOffset] ?? []; 442 | const gambledPerks = Object.values(perks).flat().filter(p => p.gambled === true).map(p => p.perk_id) 443 | const destroyChance = Object.values(allPerkPicks).flat(2).concat(gambledPerks).reduce((a, v) => { 444 | if (v === "PERKS_LOTTERY") { 445 | a /= 2; 446 | } 447 | return a; 448 | }, 100); 449 | 450 | const offsetX = 35840 * worldOffset; 451 | 452 | let result = []; 453 | for (let templeIndex = 0; templeIndex < perks.length; ++templeIndex) { 454 | const templePerkPicks = worldPerkPicks[templeIndex] ?? []; 455 | const templeLoc = this.temples[templeIndex]; 456 | const x = templeLoc.x + offsetX; 457 | const y = templeLoc.y; 458 | 459 | // Ignore the extra perks which are inserted from gambling 460 | const perkCount = perks[templeIndex].filter(p => p.gambled !== true).length 461 | const width = 60; 462 | const item_width = width / perkCount; 463 | 464 | let templeResult = [] 465 | 466 | for (let perkIndex = 0; perkIndex < perkCount; perkIndex++) { 467 | let posX = x + (perkIndex + 1 - 0.5) * item_width; 468 | posX = Math.floor(posX) % 2 === 0 ? Math.floor(posX) : Math.ceil(posX); // Thanks Banpa 469 | 470 | SetRandomSeed(posX, y); 471 | 472 | let perk_destroy_chance = destroyChance; 473 | 474 | const perk_id = perks[templeIndex][perkIndex].perk_id 475 | 476 | // If this is the lottery perk and it hasn’t been taken yet, 477 | // we need to factor in the extra luck on this pickup. 478 | if (perk_id === 'PERKS_LOTTERY' && !templePerkPicks.some(p => p === "PERKS_LOTTERY")) { 479 | perk_destroy_chance /= 2; 480 | } 481 | 482 | const willBeRemoved = Random(1, 100) <= perk_destroy_chance; 483 | templeResult.push(willBeRemoved) 484 | } 485 | 486 | result.push(templeResult) 487 | } 488 | 489 | return result 490 | } 491 | provide(perkPicks, maxLevels, worldOffset, rerolls) { 492 | let getPerks = (perkPicks, maxLevels, worldOffset) => { 493 | perkPicks = perkPicks || []; 494 | worldOffset = worldOffset || 0; 495 | if (!maxLevels || maxLevels == -1) maxLevels = Infinity; 496 | 497 | _G = {}; 498 | let perkDeck = this.getPerkDeck(); 499 | 500 | let result = []; 501 | let i, world = 0; 502 | GlobalsSetValue("TEMPLE_PERK_COUNT", "3"); 503 | 504 | let temple_locations = this.temples; 505 | while (true) { 506 | i = 0; 507 | for (let loc of temple_locations) { 508 | if (i >= maxLevels) break; 509 | let offsetX = 0, offsetY = 0; 510 | if (worldOffset != 0 && world != 0) { 511 | offsetX += 35840 * worldOffset; 512 | if (i + 1 == temple_locations.length) break; 513 | } 514 | let res = this.perk_spawn_many(perkDeck, loc.x + offsetX, loc.y + offsetY); 515 | 516 | if (rerolls && rerolls[world] && rerolls[world][i] > 0) { 517 | for (let j = 0; j < rerolls[world][i] - 1; j++) { 518 | this._getReroll(perkDeck, res.length); 519 | } 520 | let rerollRes = this._getReroll(perkDeck, res.length); 521 | if (world == worldOffset) { 522 | rerollRes = res.map((info, index) => { 523 | info.perk_id = rerollRes[index]; 524 | info.perk = this.perks.find(f => f.id === info.perk_id); 525 | return info 526 | }); 527 | result.push(rerollRes); 528 | } 529 | res = rerollRes; 530 | } else { 531 | if (world == worldOffset) { 532 | result.push(res); 533 | } 534 | } 535 | let picked_perks = perkPicks[world]?.[i]; 536 | if (picked_perks && picked_perks.length > 0) { 537 | for (let j = 0; j < picked_perks.length; j++) { 538 | let picked_perk = picked_perks[j]; 539 | if (!picked_perk) continue; 540 | var flag_name = this.get_perk_picked_flag_name(picked_perk); 541 | GameAddFlagRun(flag_name); 542 | GlobalsSetValue( flag_name + "_PICKUP_COUNT", Number(GlobalsGetValue( flag_name + "_PICKUP_COUNT", "0" )) + 1 ); 543 | if (picked_perk == "EXTRA_PERK") { 544 | GlobalsSetValue("TEMPLE_PERK_COUNT", parseFloat(GlobalsGetValue("TEMPLE_PERK_COUNT")) + 1); 545 | } else if (picked_perk == "GAMBLE") { 546 | let gambledPerks = this.perk_spawn_many(perkDeck, loc.x + offsetX, loc.y + offsetY, 2); 547 | while (gambledPerks.some(e => e.perk_id === "GAMBLE")) { 548 | let idx = gambledPerks.findIndex(e => e.perk_id === "GAMBLE"); 549 | let moreGambledPerks = this.perk_spawn_many(perkDeck, loc.x + offsetX, loc.y + offsetY, 1); 550 | gambledPerks.splice(idx, 1, moreGambledPerks[0]); 551 | } 552 | for (let k = 0; k < gambledPerks.length; k++) { 553 | let gambledPerk = gambledPerks[k]; 554 | gambledPerk.gambled = true; 555 | // if (gambledPerk == "GAMBLE") continue; 556 | var flag_name = this.get_perk_picked_flag_name(gambledPerk.perk_id); 557 | GameAddFlagRun(flag_name); 558 | GlobalsSetValue( flag_name + "_PICKUP_COUNT", Number(GlobalsGetValue( flag_name + "_PICKUP_COUNT", "0" )) + 1 ); 559 | if (gambledPerk.perk_id == "EXTRA_PERK") { 560 | GlobalsSetValue("TEMPLE_PERK_COUNT", parseFloat(GlobalsGetValue("TEMPLE_PERK_COUNT")) + 1); 561 | } 562 | let index = res.findIndex(e => e.perk_id === picked_perk); 563 | res.splice(index + 1 + k, 0, gambledPerk); 564 | } 565 | } 566 | } 567 | } 568 | i++; 569 | } 570 | if (world == worldOffset) break; 571 | if (worldOffset < 0) world--; 572 | else world++; 573 | } 574 | //console.log(_G) 575 | return result; 576 | } 577 | 578 | return getPerks(perkPicks, maxLevels, worldOffset); 579 | } 580 | } 581 | 582 | class FungalInfoProvider extends InfoProvider { 583 | async load() { 584 | this.data = await this.loadAsync("data/fungal-materials.json"); 585 | } 586 | provide(maxShifts) { 587 | let getFungalShifts = (maxShifts) => { 588 | var materials_from = this.data.materials_from; 589 | 590 | var materials_to = this.data.materials_to; 591 | 592 | var debug_no_limits = false; 593 | 594 | function CellFactory_GetName(x) { 595 | // TODO 596 | return x; 597 | } 598 | 599 | function CellFactory_GetUIName(x) { 600 | // TODO 601 | return x; 602 | } 603 | 604 | function CellFactory_GetType(x) { 605 | // TODO 606 | return x; 607 | } 608 | 609 | function fungal_shift(entity, x, y, debug_no_limits) { 610 | //var parent = EntityGetParent(entity); 611 | //if (parent != 0) { 612 | // entity = parent 613 | //} 614 | 615 | var iter = Number(GlobalsGetValue("fungal_shift_iteration", "0")); 616 | GlobalsSetValue("fungal_shift_iteration", String(iter + 1)); 617 | if (iter > 20 && !debug_no_limits) { 618 | return 619 | } 620 | 621 | SetRandomSeed(89346, 42345 + iter); 622 | 623 | let rnd = random_create(9123, 58925 + iter); // TODO: store for next change 624 | let _from = pick_random_from_table_weighted(rnd, materials_from); 625 | let to = pick_random_from_table_weighted(rnd, materials_to); 626 | 627 | // if a potion is equipped, randomly use main material from potion as one of the materials 628 | const wouldUseHeldMaterial = random_nexti(rnd, 1, 100) <= 75 629 | let heldMaterialWouldBeUsedAs = null 630 | if (wouldUseHeldMaterial) { 631 | if (random_nexti(rnd, 1, 100) <= 50) { 632 | heldMaterialWouldBeUsedAs = 'from' 633 | } else { 634 | heldMaterialWouldBeUsedAs = 'to' 635 | } 636 | } 637 | 638 | var from_materials = []; 639 | // apply effects 640 | var to_material = CellFactory_GetType(to.material); 641 | for (var i = 0; i < _from.materials.length; i++) { 642 | var it = _from.materials[i]; 643 | var from_material = CellFactory_GetType(it); 644 | //from_material_name = string.upper(GameTextGetTranslatedOrNot(CellFactory_GetUIName(from_material))); 645 | //if (_from.name_material) { 646 | // from_material_name = string.upper(GameTextGetTranslatedOrNot(CellFactory_GetUIName(CellFactory_GetType(_from.name_material)))); 647 | //} 648 | 649 | // convert 650 | if (from_material != to_material) { 651 | //console.log(CellFactory_GetUIName(from_material) + " -> " + CellFactory_GetUIName(to_material)); 652 | 653 | from_materials.push(from_material); 654 | //return [from_material, to_material]; 655 | } 656 | } 657 | return { from: from_materials, to: to_material, flask: heldMaterialWouldBeUsedAs }; 658 | } 659 | 660 | 661 | GlobalsSetValue("fungal_shift_iteration", "0") 662 | 663 | var shifts = []; 664 | if (!maxShifts || maxShifts == -1) maxShifts = 20; 665 | for (var i = 0; i < maxShifts; i++) { 666 | shifts.push(fungal_shift(null, null, null, null)); 667 | } 668 | 669 | return shifts; 670 | } 671 | 672 | return getFungalShifts(maxShifts); 673 | } 674 | } 675 | 676 | class BiomeModifierInfoProvider extends InfoProvider { 677 | async load() { 678 | this.modifiers = await this.loadAsync("data/biome_modifiers.json"); 679 | this.biomes = [ 680 | ["coalmine","mountain_hall"], 681 | ["coalmine_alt"], 682 | ["excavationsite"], 683 | ["fungicave"], 684 | ["snowcave"], 685 | ["snowcastle"], 686 | ["rainforest","rainforest_open"], 687 | ["vault"], 688 | ["crypt"], 689 | ]; 690 | // only used by UI 691 | this.biomeNames = await this.loadAsync("data/biome_names.json"); 692 | } 693 | provide() { 694 | // returns a table mapping biome_names to active_modifiers. 695 | // this function should be deterministic, and have no side effects. 696 | 697 | var biome_modifiers = this.modifiers; 698 | 699 | var result = {}; 700 | 701 | var biomes = this.biomes; 702 | var CHANCE_OF_MODIFIER_PER_BIOME = 0.1; 703 | var CHANCE_OF_MODIFIER_COALMINE = 0.2; 704 | var CHANCE_OF_MODIFIER_EXCAVATIONSITE = 0.15; 705 | var CHANCE_OF_MOIST_FUNGICAVE = 0.5; 706 | var CHANCE_OF_MOIST_LAKE = 0.75; 707 | 708 | function HasFlagPersistent(flag) { // assume everything is unlocked 709 | return true; 710 | } 711 | 712 | var biome_modifier_fog_of_war_clear_at_player = biome_modifiers.find(e => e.id == "FOG_OF_WAR_CLEAR_AT_PLAYER"); 713 | var biome_modifier_cosmetic_freeze = biome_modifiers.find(e => e.id == "FREEZING_COSMETIC"); 714 | 715 | function get_modifier( modifier_id ) { 716 | return biome_modifiers.find(e => e.id == modifier_id); 717 | } 718 | 719 | function biome_modifier_applies_to_biome( modifier, biome_name ) { 720 | if (!modifier) { 721 | return false; 722 | } 723 | 724 | var ok = true; 725 | 726 | if (modifier.requires_flag) { 727 | if ( HasFlagPersistent( modifier.requires_flag ) == false ) { 728 | return false; 729 | } 730 | } 731 | 732 | if (modifier.does_not_apply_to_biome) { 733 | for (var i = 0; i < modifier.does_not_apply_to_biome.length; i++) { 734 | var skip_biome = modifier.does_not_apply_to_biome[i]; 735 | if (skip_biome == biome_name) { 736 | ok = false; 737 | break; 738 | } 739 | } 740 | } 741 | 742 | if (modifier.apply_only_to_biome) { 743 | ok = false 744 | for (var i = 0; i < modifier.apply_only_to_biome.length; i++) { 745 | var required_biome = modifier.apply_only_to_biome[i]; 746 | if (required_biome == biome_name) { 747 | ok = true; 748 | break; 749 | } 750 | } 751 | } 752 | 753 | return ok; 754 | } 755 | 756 | function has_modifiers(biome_name, ctx) { 757 | if (biome_name == "coalmine" && ctx.deaths < 8 && ctx.should_be_fully_deterministic == false) { 758 | return false 759 | } 760 | 761 | var chance_of_modifier = CHANCE_OF_MODIFIER_PER_BIOME 762 | if (biome_name == "coalmine") { 763 | chance_of_modifier = CHANCE_OF_MODIFIER_COALMINE 764 | } else if (biome_name == "excavationsite") { 765 | chance_of_modifier = CHANCE_OF_MODIFIER_EXCAVATIONSITE 766 | } 767 | 768 | return random_next(ctx.rnd, 0.0, 1.0) <= chance_of_modifier; 769 | } 770 | 771 | var set_modifier_if_has_none = function( biome_name, modifier_id ) { 772 | if (!result[biome_name]) { 773 | result[biome_name] = get_modifier( modifier_id ); 774 | } 775 | }; 776 | 777 | 778 | var rnd = random_create(347893,90734); 779 | var ctx = { }; 780 | ctx.rnd = rnd; 781 | ctx.deaths = 1000; //Number(StatsGlobalGetValue( "death_count" )); 782 | ctx.should_be_fully_deterministic = false; //GameIsModeFullyDeterministic(); 783 | 784 | for (var i = 0; i < biomes.length; i++) { 785 | var biome_names = biomes[i]; 786 | var modifier = null; 787 | if (has_modifiers( biome_names[0], ctx)) { 788 | modifier = pick_random_from_table_weighted( rnd, biome_modifiers ); 789 | } 790 | 791 | for (var j = 0; j < biome_names.length; j++) { 792 | var biome_name = biome_names[j]; 793 | if (biome_modifier_applies_to_biome( modifier, biome_name )) { 794 | result[biome_name] = modifier; 795 | } 796 | } 797 | } 798 | 799 | // DEBUG - apply modifier to all biomes 800 | /* 801 | for (var i = 0; i < biomes.length; i++) { 802 | var biome_names = biomes[i]; 803 | for (var j = 0; j < biome_names.length; j++) { 804 | //var biome_name = biome_names[j]; 805 | //result[biome_name] = get_modifier( "GAS_FLOODED" ); 806 | } 807 | } 808 | */ 809 | 810 | if( random_next( rnd, 0.0, 1.0 ) < CHANCE_OF_MOIST_FUNGICAVE ) { 811 | set_modifier_if_has_none( "fungicave", "MOIST" ); 812 | } 813 | 814 | // force custom fog of war in these biomes 815 | result["wandcave"] = biome_modifier_fog_of_war_clear_at_player; 816 | result["wizardcave"] = biome_modifier_fog_of_war_clear_at_player; 817 | result["alchemist_secret"] = biome_modifier_fog_of_war_clear_at_player; 818 | //apply_modifier_if_has_none( "snowcave", "FREEZING" ); 819 | 820 | // side biomes 821 | set_modifier_if_has_none( "mountain_top", "FREEZING" ); // NOTE: Freezing tends to occasionally bug out physics bodies, only put it in overworld biomes 822 | set_modifier_if_has_none( "mountain_floating_island", "FREEZING" ); 823 | set_modifier_if_has_none( "winter", "FREEZING" ); 824 | result["winter_caves"] = biome_modifier_cosmetic_freeze; 825 | //apply_modifier_if_has_none( "bridge", "FREEZING" ) 826 | //apply_modifier_if_has_none( "vault_frozen", "FREEZING" ) 827 | 828 | set_modifier_if_has_none( "lavalake", "HOT" ); 829 | set_modifier_if_has_none( "desert", "HOT" ); 830 | set_modifier_if_has_none( "pyramid_entrance", "HOT" ); 831 | set_modifier_if_has_none( "pyramid_left", "HOT" ); 832 | set_modifier_if_has_none( "pyramid_top", "HOT" ); 833 | set_modifier_if_has_none( "pyramid_right", "HOT" ); 834 | 835 | set_modifier_if_has_none( "watercave", "MOIST" ); 836 | 837 | if( random_next( rnd, 0.0, 1.0 ) < CHANCE_OF_MOIST_LAKE ) { 838 | set_modifier_if_has_none( "lake_statue", "MOIST" ); 839 | } 840 | 841 | return result; 842 | } 843 | } 844 | 845 | class MaterialInfoProvider extends InfoProvider { 846 | async load() { 847 | this.materials = await this.loadAsync("data/materials.json"); 848 | let allMaterials = Array.prototype.concat.apply(this, this.materials.map(e => e.normal.concat(e.statics))); 849 | this.allMaterials = allMaterials; 850 | } 851 | provide(materialName) { 852 | if (materialName.charAt(0) == '(') { // specials, like (flask) for fungal shift 853 | return { 854 | translated_name: materialName 855 | } 856 | } 857 | let found = this.allMaterials.find(e => e.name === materialName); 858 | if (found) return found; 859 | console.warn("Could not find material: " + materialName); 860 | return { 861 | translated_name: materialName 862 | }; 863 | } 864 | translate(materialName) { 865 | return this.provide(materialName).translated_name; 866 | } 867 | } 868 | 869 | class BiomeInfoProvider extends InfoProvider { 870 | async load() { 871 | this.biomes = await this.loadAsync("data/biome_names.json"); 872 | } 873 | provide(biomeId) { 874 | let found = this.biomes.find(e => e.id === biomeId); 875 | if (found) return found; 876 | console.warn("Could not find biome: " + biomeId); 877 | return { 878 | translated_name: biomeId 879 | }; 880 | } 881 | isPrimary(biomeId) { 882 | let found = this.biomes.find(e => e.id === biomeId); 883 | return found && found.translated_name !== ""; 884 | } 885 | translate(biomeName) { 886 | return this.provide(biomeName).translated_name; 887 | } 888 | } 889 | 890 | const infoProviders = { 891 | RAIN: new RainInfoProvider, 892 | STARTING_FLASK: new StartingFlaskInfoProvider, 893 | STARTING_SPELL: new StartingSpellInfoProvider, 894 | STARTING_BOMB_SPELL: new StartingBombSpellInfoProvider, 895 | PERK: new PerkInfoProvider, 896 | FUNGAL_SHIFT: new FungalInfoProvider, 897 | BIOME_MODIFIER: new BiomeModifierInfoProvider, 898 | BIOME: new BiomeInfoProvider, 899 | MATERIAL: new MaterialInfoProvider, 900 | WATER_CAVE: new WaterCaveInfoProvider, 901 | }; 902 | 903 | class SeedRequirement { 904 | constructor(type, name, once, provider) { 905 | this.type = type; 906 | this.name = name; 907 | this.once = once; 908 | this.provider = provider; 909 | } 910 | } 911 | 912 | class SeedRequirementStartingFlask extends SeedRequirement { 913 | constructor() { 914 | super("StartingFlask", "Starting Flask", true, infoProviders.STARTING_FLASK); 915 | } 916 | test(mat) { 917 | return mat === this.provider.provide(); 918 | } 919 | } 920 | 921 | class SeedRequirementStartingSpell extends SeedRequirement { 922 | constructor() { 923 | super("StartingSpell", "Starting Spell", true, infoProviders.STARTING_SPELL); 924 | } 925 | test(spell) { 926 | return spell === this.provider.provide(); 927 | } 928 | } 929 | 930 | class SeedRequirementStartingBombSpell extends SeedRequirement { 931 | constructor() { 932 | super("StartingBombSpell", "Starting Bomb Spell", true, infoProviders.STARTING_BOMB_SPELL); 933 | } 934 | test(spell) { 935 | return spell === this.provider.provide(); 936 | } 937 | } 938 | 939 | class SeedRequirementRain extends SeedRequirement { 940 | constructor() { 941 | super("Rain", "Rain", true, infoProviders.RAIN); 942 | } 943 | test(shouldRainMaterial) { 944 | let [rains, rainMaterial] = this.provider.provide(); 945 | if (!rains) return !shouldRainMaterial; 946 | return rainMaterial === shouldRainMaterial; 947 | } 948 | } 949 | 950 | class SeedRequirementPerk extends SeedRequirement { 951 | constructor() { 952 | super("Perk", "Perk", false, infoProviders.PERK); 953 | } 954 | test(level, perk, reroll) { 955 | if (reroll != -1) { 956 | let rerollsArr; 957 | if (level == -1) { 958 | let numTemples = this.provider.temples.length; 959 | for (let i = 0; i < numTemples; i++) { 960 | rerollsArr = [ 961 | // Level 0 - World 962 | [ 963 | // Level 1 - Holy Mountain 964 | ] 965 | ]; 966 | rerollsArr[0][i] = reroll; 967 | 968 | let perks = this.provider.provide(null, level, null, rerollsArr); 969 | 970 | if (perks[i].findIndex(e => e.perk.id === perk) !== -1) return true; 971 | } 972 | return false; 973 | } 974 | rerollsArr = [ 975 | // Level 0 - World 976 | [ 977 | // Level 1 - Holy Mountain 978 | ] 979 | ]; 980 | rerollsArr[0][level - 1] = reroll; 981 | let perks = this.provider.provide(null, level, null, rerollsArr); 982 | return perks[level - 1].findIndex(e => e.perk.id === perk) !== -1; 983 | } 984 | let perks = this.provider.provide(null, level); 985 | if (level == -1) { 986 | for (let i = 0; i < perks.length; i++) { 987 | if (perks[i].findIndex(e => e.perk.id === perk) !== -1) return true; 988 | } 989 | return false; 990 | } 991 | return perks[level - 1].findIndex(e => e.perk.id === perk) !== -1; 992 | } 993 | } 994 | 995 | class SeedRequirementFungalShift extends SeedRequirement { 996 | constructor() { 997 | super("FungalShift", "Fungal Shift", false, infoProviders.FUNGAL_SHIFT); 998 | } 999 | test(iterations, fromMaterial, toMaterial) { 1000 | let shifts = this.provider.provide(iterations); 1001 | function checkShift(shift) { 1002 | let fromMats = shift.from; 1003 | let toMat = shift.to; 1004 | if (fromMaterial) { 1005 | let flask = fromMaterial === "(flask)" && shift.flask === "from"; 1006 | if (!flask && fromMats.indexOf(fromMaterial) === -1) return false; 1007 | } 1008 | if (toMaterial) { 1009 | let flask = toMaterial === "(flask)" && shift.flask === "to"; 1010 | if (!flask && toMat !== toMaterial) return false; 1011 | } 1012 | return true; 1013 | } 1014 | if (iterations == -1) { 1015 | for (let shift of shifts) { 1016 | if (!checkShift(shift)) continue; 1017 | return true; 1018 | } 1019 | return false; 1020 | } 1021 | return checkShift(shifts[iterations - 1]); 1022 | } 1023 | } 1024 | 1025 | class SeedRequirementBiomeModifier extends SeedRequirement { 1026 | constructor() { 1027 | super("BiomeModifier", "Biome Modifier", false, infoProviders.BIOME_MODIFIER); 1028 | } 1029 | test(biome, modifier) { 1030 | let biomeModifiers = this.provider.provide(); 1031 | return biomeModifiers[biome] && biomeModifiers[biome].id === modifier; 1032 | } 1033 | } 1034 | 1035 | const RequirementRain = function() { 1036 | this.type = "Rain"; 1037 | this.material = ""; 1038 | this.requirement = new SeedRequirementRain(); 1039 | }; 1040 | RequirementRain.prototype.test = function() { 1041 | return this.requirement.test(this.material); 1042 | } 1043 | RequirementRain.prototype.textify = function() { 1044 | if (this.material) 1045 | return "Have " + infoProviders.MATERIAL.translate(this.material) + " rain" 1046 | return "Have no rain" 1047 | } 1048 | RequirementRain.prototype.serialize = function() { 1049 | return "r-m" + this.material; 1050 | } 1051 | RequirementRain.deserialize = function(str) { 1052 | if (!str.startsWith("r")) return; 1053 | let req = new RequirementRain(); 1054 | [req.material] = str.match(/^r\-m(.*?)$/).slice(1); 1055 | return req; 1056 | } 1057 | RequirementRain.displayName = "Rain"; 1058 | 1059 | const RequirementStartingFlask = function() { 1060 | this.type = "StartingFlask"; 1061 | this.requirement = new SeedRequirementStartingFlask(); 1062 | 1063 | // This is required so vue knows which properties are reactive 1064 | this.material = null; 1065 | 1066 | this.requirement.provider.ready.then(() => { 1067 | if (this.material) return; 1068 | this.material = this.requirement.provider.materials[0] 1069 | }); 1070 | }; 1071 | RequirementStartingFlask.prototype.test = function() { 1072 | return this.requirement.test(this.material); 1073 | } 1074 | RequirementStartingFlask.prototype.textify = function() { 1075 | return "Start with a " + infoProviders.MATERIAL.translate(this.material) + " flask" 1076 | } 1077 | RequirementStartingFlask.prototype.serialize = function() { 1078 | return "sf-m" + this.material; 1079 | } 1080 | RequirementStartingFlask.deserialize = function(str) { 1081 | if (!str.startsWith("sf")) return; 1082 | let req = new RequirementStartingFlask(); 1083 | [req.material] = str.match(/^sf\-m(.+?)$/).slice(1); 1084 | return req; 1085 | } 1086 | RequirementStartingFlask.displayName = "Starting Flask"; 1087 | 1088 | const RequirementStartingSpell = function() { 1089 | this.type = "StartingSpell"; 1090 | this.requirement = new SeedRequirementStartingSpell(); 1091 | 1092 | // This is required so vue knows which properties are reactive 1093 | this.spell = null; 1094 | 1095 | this.requirement.provider.ready.then(() => { 1096 | this.spells = this.requirement.provider.spells; 1097 | if (this.spell) return; 1098 | this.spell = this.spells[0]; 1099 | }); 1100 | }; 1101 | RequirementStartingSpell.prototype.test = function() { 1102 | return this.requirement.test(this.spell); 1103 | } 1104 | RequirementStartingSpell.prototype.textify = function() { 1105 | return "Have " + this.spell + " as its starting spell" 1106 | } 1107 | RequirementStartingSpell.prototype.serialize = function() { 1108 | return "ss-s" + this.spell; 1109 | } 1110 | RequirementStartingSpell.deserialize = function(str) { 1111 | if (!str.startsWith("ss")) return; 1112 | let req = new RequirementStartingSpell(); 1113 | [req.spell] = str.match(/^ss\-s(.+?)$/).slice(1); 1114 | return req; 1115 | } 1116 | RequirementStartingSpell.displayName = "Starting Spell"; 1117 | 1118 | const RequirementStartingBombSpell = function() { 1119 | this.type = "StartingBombSpell"; 1120 | this.requirement = new SeedRequirementStartingBombSpell(); 1121 | 1122 | // This is required so vue knows which properties are reactive 1123 | this.spell = null; 1124 | 1125 | this.requirement.provider.ready.then(() => { 1126 | this.spells = this.requirement.provider.spells; 1127 | if (this.spell) return; 1128 | this.spell = this.spells[0]; 1129 | }); 1130 | }; 1131 | RequirementStartingBombSpell.prototype.test = function() { 1132 | return this.requirement.test(this.spell); 1133 | } 1134 | RequirementStartingBombSpell.prototype.textify = function() { 1135 | return "Have " + this.spell + " as its starting bomb spell" 1136 | } 1137 | RequirementStartingBombSpell.prototype.serialize = function() { 1138 | return "sbw-s" + this.spell; 1139 | } 1140 | RequirementStartingBombSpell.deserialize = function(str) { 1141 | if (!str.startsWith("sbw")) return; 1142 | let req = new RequirementStartingBombSpell(); 1143 | [req.spell] = str.match(/^sbw\-s(.+?)$/).slice(1); 1144 | return req; 1145 | } 1146 | RequirementStartingBombSpell.displayName = "Starting Bomb Spell"; 1147 | 1148 | const RequirementPerk = function() { 1149 | this.type = "Perk"; 1150 | this.level = 1; 1151 | this.reroll = -1; 1152 | this.requirement = new SeedRequirementPerk(); 1153 | 1154 | // This is required so vue knows which properties are reactive 1155 | this.perk = null; 1156 | 1157 | this.requirement.provider.ready.then(() => { 1158 | if (this.perk) return; 1159 | this.perk = this.requirement.provider.perks[0].id 1160 | }); 1161 | }; 1162 | RequirementPerk.prototype.test = function() { 1163 | return this.requirement.test(this.level, this.perk, this.reroll); 1164 | } 1165 | RequirementPerk.prototype.textify = function() { 1166 | let str = "Have the perk '" + this.requirement.provider.perks.find(e => e.id == this.perk).ui_name + "'"; 1167 | if (this.level != -1) { 1168 | str += " in the " + nthify(this.level) + " level"; 1169 | } 1170 | if (this.reroll != -1) { 1171 | str += " on the " + nthify(this.reroll) + " reroll"; 1172 | } 1173 | return str; 1174 | } 1175 | RequirementPerk.prototype.serialize = function() { 1176 | let s = "p-l" + this.level + "-p" + this.perk; 1177 | if (this.reroll != -1) s += "-r" + this.reroll; 1178 | return s; 1179 | } 1180 | RequirementPerk.deserialize = function(str) { 1181 | if (!str.startsWith("p")) return; 1182 | let req = new RequirementPerk(); 1183 | if (str.match(/^p\-l(-?\d+)\-p(.+?)\-r(-?\d+)$/)) 1184 | [req.level, req.perk, req.reroll] = str.match(/^p\-l(-?\d+)\-p(.+?)\-r(-?\d+)$/).slice(1); 1185 | else 1186 | [req.level, req.perk] = str.match(/^p\-l(-?\d+)\-p(.+?)$/).slice(1); 1187 | return req; 1188 | } 1189 | RequirementPerk.displayName = "Perk"; 1190 | 1191 | const RequirementFungalShift = function() { 1192 | this.type = "FungalShift"; 1193 | this.iterations = 1; 1194 | this.fromMaterial = ""; 1195 | this.toMaterial = ""; 1196 | this.requirement = new SeedRequirementFungalShift(); 1197 | }; 1198 | RequirementFungalShift.prototype.test = function() { 1199 | return this.requirement.test(this.iterations, this.fromMaterial, this.toMaterial); 1200 | } 1201 | RequirementFungalShift.prototype.textify = function() { 1202 | if (this.iterations == -1) { 1203 | return "Have any fungal shift turn " + infoProviders.MATERIAL.translate(this.fromMaterial || "(anything)") + " to " + infoProviders.MATERIAL.translate(this.toMaterial || "(anything)") 1204 | } else { 1205 | return "Have the " + nthify(this.iterations) + " fungal shift turn " + infoProviders.MATERIAL.translate(this.fromMaterial || "(anything)") + " to " + infoProviders.MATERIAL.translate(this.toMaterial || "(anything)") 1206 | } 1207 | } 1208 | RequirementFungalShift.prototype.serialize = function() { 1209 | return "fs-i" + this.iterations + "-f" + this.fromMaterial + "-t" + this.toMaterial; 1210 | } 1211 | RequirementFungalShift.deserialize = function(str) { 1212 | if (!str.startsWith("fs")) return; 1213 | let req = new RequirementFungalShift(); 1214 | [req.iterations, req.fromMaterial, req.toMaterial] = str.match(/^fs\-i(-?\d+)\-f(.*?)\-t(.*?)$/).slice(1); 1215 | return req; 1216 | } 1217 | RequirementFungalShift.displayName = "Fungal Shift"; 1218 | const RequirementBiomeModifier = function() { 1219 | this.type = "BiomeModifier"; 1220 | this.biome = "coalmine"; 1221 | this.requirement = new SeedRequirementBiomeModifier(); 1222 | 1223 | // This is required so vue knows which properties are reactive 1224 | this.modifier = null; 1225 | 1226 | this.requirement.provider.ready.then(() => { 1227 | if (this.modifier) return; 1228 | this.modifier = this.requirement.provider.modifiers[0].id; 1229 | }); 1230 | }; 1231 | RequirementBiomeModifier.prototype.test = function() { 1232 | return this.requirement.test(this.biome, this.modifier); 1233 | }; 1234 | RequirementBiomeModifier.prototype.textify = function() { 1235 | return "Have the biome modifier '" + this.modifier.replace(/_/g, " ").toLowerCase() + "' in " + infoProviders.BIOME.translate(this.biome); 1236 | } 1237 | RequirementBiomeModifier.prototype.serialize = function() { 1238 | return "bm-b" + this.biome + "-m" + this.modifier; 1239 | } 1240 | RequirementBiomeModifier.deserialize = function(str) { 1241 | if (!str.startsWith("bm")) return; 1242 | let req = new RequirementBiomeModifier(); 1243 | [req.biome, req.modifier] = str.match(/^bm\-b(.+?)\-m(.+?)$/).slice(1); 1244 | return req; 1245 | } 1246 | RequirementBiomeModifier.displayName = "Biome Modifier"; 1247 | 1248 | const AVAILABLE_REQUIREMENTS = [ 1249 | RequirementStartingFlask, 1250 | RequirementStartingSpell, 1251 | RequirementStartingBombSpell, 1252 | RequirementRain, 1253 | RequirementPerk, 1254 | RequirementFungalShift, 1255 | RequirementBiomeModifier 1256 | ]; 1257 | 1258 | function parseSeedCriteria(str) { 1259 | let result = []; 1260 | let parts = str.split(","); 1261 | let criteria; 1262 | let or; 1263 | let not; 1264 | for (let part of parts) { 1265 | or = false; 1266 | not = false; 1267 | if (part.startsWith("!")) { 1268 | part = part.slice(1); 1269 | not = true; 1270 | } 1271 | if (part.endsWith(";")) { 1272 | part = part.slice(0, -1); 1273 | or = true; 1274 | } 1275 | for (let critertaType of AVAILABLE_REQUIREMENTS) { 1276 | if (criteria = critertaType.deserialize(part)) { 1277 | criteria.or = or; 1278 | criteria.not = not; 1279 | result.push(criteria); 1280 | break; 1281 | } 1282 | } 1283 | } 1284 | return result; 1285 | } -------------------------------------------------------------------------------- /data/perks.json: -------------------------------------------------------------------------------- 1 | [{"stackable":true,"id":"CRITICAL_HIT","ui_name":"Critical Hit +","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA50lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/bBqxGQQzhAmXosDEFIacmno439rRAas6FAOQbV8/fw7D06dPGQITUxgkVFQZ7j5+CpdbsGULnM2Cz6nr58+B29xtbMrAYGzKcFJVlYFz7VrsLkAH1o4ODHcfP2UIiYhiYGBgYDipqspgfvs2Q+zKZYQNgGl+cec2iuanly+jqMNqALpmBgYGBj09PQzNDAxYwgDmZ5jmnJp6iM3r1jFUXIMYAEsPDAwkpIMOLV0UA2DpgOKUOAwAAAdZX0ggf3SaAAAAAElFTkSuQmCC"},{"stackable":true,"id":"BREATH_UNDERWATER","stackable_is_rare":true,"ui_name":"Breathless","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxUlEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/mEbltOkMDAwMDHdnZWI1CGYIEzZnKadNZ7i9dztDeZwp3CBcAMUAmO3vHr9iYGBgYEiJb8KqCdl7WF3wfns9g7CaMYOqsyfD3VmZDMpp03G6BKsBMENgmvF5B6cBMEDIO3gNUE6bjuEddMCCzPn17Sc8gAQ9Gxlu793OoJo2neHurEyG92jqCLoAZjM2W5EBxSlxGAAAa85OlCNUH1MAAAAASUVORK5CYII="},{"stackable":true,"id":"EXTRA_MONEY","ui_name":"Greed","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABF0lEQVQ4jd2SLU/DUBSGHxBdMlNX09ZeTR0CT/YHGAb+AKroBdGg6W8gNYOEkGCamdWQDLeZEXIr15pmpiFZspohSC/92Jgk4aiTe8/7vOfevPDXddA8cGx7A6B1O63hYrUGYLpYKN1hU6x1O0o8GvZrgPKuNGkBmq6n5w+qr8KqczVAWW+v1z/9R9yCVWsr4PjkToHy2aDm7ve0/QAlTlJ0yySTUm3ihsV+QOA53+KjW/IkxRCCfDaoPW0nwO9pGEIAED+eoVum2iJPUgLP2Q0oVmvcsCCTkvf7ZwwhiMcR8ThiOZmjWyYXN1OVB2gEybHtTfRyufXHR8M+mZQsJ3Ounj5VmH5Not/TcMOCwHNqztUk/oP6AuT8criHjXPGAAAAAElFTkSuQmCC"},{"stackable":true,"id":"EXTRA_MONEY_TRICK_KILL","ui_name":"Trick Greed","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABAElEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNfHhK6ZjYsdQ3PbhoMM68/fZujZdYKBjYsdbgmGAdjA+vO3GTTlpRgYGBgYNOWlGNafv40ij2IANmdff/iMIdBQFYVGVocSBubqKv8xTIC6AgYCDVUZGBgYGE7evMOI1wsnjhQxnLhxG8MV6ACrCxY3GTKIqakxMDAwMPDLSDNY2PRhaMTpgn5PNgYxNTUGfhlphle3bjF8fPKUYXGTIS6Hohrw69tPhsLtvxhe3brFcHvffoY3x68y8MtIM8TWnWeAuQymDqsXYOmAEPj17Sc8MVGcEocBAAB7mmCdfPgkxwAAAABJRU5ErkJggg=="},{"stackable":false,"id":"GOLD_IS_FOREVER","ui_name":"Gold Is Forever","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABSElEQVQ4jd2Sv0tCcRTFP4WYNGSTi8rXoV4/Jh8I9R9E0FwtQXuLueaLUPca+gMMlxKkiCBaiuCBDoIuNjyX90UdEodegagErynp+SPHoDPee865514u/DWmBgtqMGgDuGdnhsi9dheAUq3W17l+EtaWFuxB0ef8Kp6IhhACKSWdYhIV7G+T6UkRPRGNs314zmncnITwRDRHf6KBEIJQKARAeCuOEMKxnmuMrg8pJaYJ5bsUpmkSTUtH/1eDgh7jtjJHNP2OEDZSQqeYdIjGGhT0GFa9wcprHP08y/ry4kjByBtkEipWvYE3nMKnKFjlOAU9NnKQw6DX7nK66canKABUs9t4A36ahoFVb5BJqH3e2ASH9z2ahsHLxTU+RaH6+EQrX8Eb8LN3XBpKMPSJb6Uje2P3ylF7uNyhaRi08hUOch+OT/wH+AKRPnF10nVbQgAAAABJRU5ErkJggg=="},{"stackable":false,"id":"TRICK_BLOOD_MONEY","ui_name":"Trick Blood Money","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEcSURBVHjaYvz//z8DJQAggJgYKAQAAcSCzGFkZGQwlJUFO4mNix1D8a9vP8H0+cePGWEuBwggFBeANIM0omtu23CQYf352ww9u06A5WCWgABAABH0AkijprwUmA2iQXxkABBAKAZgc/b1h88YAg1VUWhkdQABxIgcCxYaqv9xuQIGQIaAwIkbtxlBNEAA4fTCnjAvhkN9nRiuQAcAAYTVBYs4xBn41SXAYtzq0gwuq7ZhaIS5ACCAMFww5Z4QWDNI48ebLxi+3nwKNhAXAAggJvR4zlF6B9b4bNNZhk/XfoMNivvxkgHmMuT0AAIAAYTiBSM5uf/YYgJbgjr36BHYCwABhGIAOSkRIIAYKc1MAAFEcWYCCCCKDQAIMACzWXcgzJ+19AAAAABJRU5ErkJggg=="},{"stackable":true,"id":"EXPLODING_GOLD","stackable_is_rare":true,"ui_name":"Exploding Gold","stackable_maximum":6,"max_in_perk_pool":1,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCklEQVQ4jWNgGGjAiC5gKCv7f0uyFkPQ8rso4ieOFDEYGbUzMDAwMJx//BiujxFdMxsXO0TD6kAGi9D1cBoZ/Pr2E24ICzZnnVgdyMCgGMZwYjUDhmZ0wITM2ZKsBdEMA4phDCdWBzKsi1SGC/V7sjHAXInhBXN1lf/ILmC4vwqnC07evMOI4QIYeLbuEoOFSSSDReh6uO0nbtxmOHGkCL8XYM5HjoGg5XcZTty4jeIFnAYgOxfZ3wwMDAwMb6YzHG+dguEClFj49e0nPICQXXF7VRicXbj9F8Ovbz/hfKzp4MSN2wwWGqoYtiFbBEsHKF44//gx469vPxmM5OTgYoubDFE0ImseJgAAEfVqY6+oChAAAAAASUVORK5CYII="},{"stackable":true,"id":"HOVER_BOOST","ui_name":"Strong Levitation","max_in_perk_pool":1,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAq0lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNfHhK6ZjYsdRbNE/mw4GyYHswTDAHQgkT+b4cXEVBRD0AGKAeg2v5iYymC1+DaGIcjqsLoAWXO2BgNWQ2AAJRDN1VXgfoNphoGpNxgYjsWqwvknb95hxOkCdM0MDAxwl6ADFmwGHItVZTiGZBC67cgAxQWweCYEkNXhjUZiAMUpcRgAAFRoQumGzx29AAAAAElFTkSuQmCC"},{"stackable":true,"id":"FASTER_LEVITATION","ui_name":"Faster Levitation","max_in_perk_pool":1,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAAA2ElEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUF4A0gzQia/ZcaAFnw+RgloAAQADh9QJI8/b4EyiGoAOAAEIxAN1mmGZ0Q5DVAQQQVhdopKnCNZWZN8ANAYmjA4AAYkSORgsNVTgHphkGuk42gA2BgRM3bjOCaIAAwuoCdM0gAHMJOgAIIJwuQDYI3XZkFwAEEIoLYPFMCCCrAwggilMiQACheIGclAgQQIyUZiaAAKLYCwABRLEBAAEGAP6SWdYN2ONgAAAAAElFTkSuQmCC"},{"stackable":true,"id":"MOVEMENT_FASTER","ui_name":"Faster Movement","max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAuElEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/bBqxGQQzhAmfQs+FFgwMDAwMGmmqONWgGIBue5l5A4PnQguGG7Nuww1DV8eCy1ZkQxgWNjBsjz/B4LnQgmF7/AncTjZXV/n/7f8drLjhRMx/c3UVOI3TBV0nG1Bth4rhcgGGAcgKym7g18zAgBaIsHhGds32+BMMGmmqKJqR1VGcDihOicMAAADQEWxxZEYsYgAAAABJRU5ErkJggg=="},{"stackable":true,"id":"STRONG_KICK","ui_name":"Never Skip Leg Day","max_in_perk_pool":1,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABOUlEQVQ4jd2SMUvDUBSFP1NpoFBEqAiBBgt2EKeCFHEpCIJjQXDQnxDQDl2c3HTpIELBPyHUrYOLi0OWdHJoaVpSKUiDYIsNCrYOIc/ExDoKnu2+e7/zzntc+GvNfT/IpdNTgHhCdusUGLbbq5ckts8cjF5PcJIfPi6eTuMJWcDgwvWSRDEDsewG++syw9uDaaQBwObaYSjm1c0ErZwnqapo5TyX5/eiFzLwm+RS7u0eDJBUVY5OtqINnl4eAyaGDbsXE6oVnZFlATCyLKoVfXYCTxmlAEBsVaGujwWslfNiZt4PdPp3QEHUA6fB3o6CaS8Az3y0Hqh1oKbp0QaeSUYpMHAaZFcUTBuGrw7XRh/TCKcMPOF9/CZMlpcWARduN7vA1z54czP/oN3sBuCf9OsmRiX0b+I/0CdF2nTx0V1RZwAAAABJRU5ErkJggg=="},{"stackable":false,"id":"TELEKINESIS","ui_name":"Telekinetic Kick","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA+klEQVQ4y2P8//8/AyWAiYFCwILMYWRkZDCUlf3PwMDAwMbFjqH417efDAwMDAznHz9mhLkcxQBDWdn/2DTCAEwOagkjhgHYwKy+NBR+WtEsFD4jciDOP8fwn4GBgWFmlCpD+rLbDDOjVBkiQq0Yrt99ypAd446iUc+rlBHDAAsNVZQoyUn3YLh+5ykDAwMDw5v3nxg0laXhcgXNCxkxYiF92W0U+s27Twxv3n9iYGBgYBAR5GMQEUJgrF5Ad4Gzsy5W21esPsZw4sZtRqzpANkVKsnrGN68/4TTdgwXGMnJ4Y1G5PRw7tEjRqqkRBQXkJMSGQc8M1FsAAD5lmjnTgfrpwAAAABJRU5ErkJggg=="},{"stackable":true,"id":"REPELLING_CAPE","stackable_is_rare":true,"ui_name":"Repelling Cape","stackable_maximum":8,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABL0lEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHSzmoRvEICOtAmZPW9cMps8/fswIczlAAKEYANIM04isuWdTN4MA23+4oVBLGEFsgADC6wWQ5lWHl+P1AkAAoRgAs8EiuRxMP3l6h0GcTZhBTlAGRRzZlQABxIgcCxYaqihRAvICDOy4vA7F5hM3boO9ABBAGF6A2YIceNjkYQAggHC6QEnGgMFJx43BTGcxmN+xQpzh3pMLGC4ACCCsgQiyBaTYK/s/w6krsQzrdloyNNUKY1PKABBAKC4wkpODRyPMBSAACkzkMPj17SfDuUePwC4ACCCcYaAmqETQ/yAAEEAoCQmUwhgmN+HMHIcmNyHUQQFAADFSmpkAAojizAQQQBQbABBgAPCWXtYTWaW/AAAAAElFTkSuQmCC"},{"stackable":false,"id":"EXPLODING_CORPSES","ui_name":"Exploding Corpses","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABAUlEQVQ4jd2SLwvCUBTFjwYFQeOCMERmFmxW81hwIDiL+GH8HGrZwtgWZNkkmPQLOIY8MBgVBltQg7zLe3NaBU95f8/vnnd5wK9Vym/0VPWxD8fojwLaM3SF5p7LcGCMfOW8uVKrktm3NABAu9WkO/twjJ6qPgoTcADXzh1K6Tg4S1JKISWo1KpUlY9c9iamp4hFJAAAmE4E39LQNLtSH7jEfhQCDF2B6USS2d7EmAzab7A3gKErWIcXWvuWhv4oQHw6Y77cYtaoYx1epBQSgJv5+00nkqotrrfvCbIkLTSK8K8AUavO/dMRPJcVAw6MlbIkRZakmB5fR/mui3/gT/QEfvpdfJHVA90AAAAASUVORK5CYII="},{"stackable":false,"id":"SAVING_GRACE","ui_name":"Saving Grace","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABGElEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUA0CasWmEAZgc1BJGEBsggPB64eDeLIYTN26DaVwAIIBQDFg0yRzOntdnz2DvPI3BQkMVTIP42NQBBBADyC8w/PZq7v//nw/9N1dXAeOrW5NRaBAGyYPVQfUABBCKAQEeJmAFyIYgY5gcWB1UD0AAoRgAUnR5fQhcIcwgZD5IHiwG1QMQQBiBmFJxAc4ODC1CodHlQQAggFAMAMXznA4DuKbn9z+A2SAaZghIHpYeQAAggBiRk7KRnBzedIBs0blHj8DpACCAUAwgJyUCBBAjpZkJIIAozkwAAUSxAQABBgCKm9CUNqIB6gAAAABJRU5ErkJggg=="},{"stackable":false,"id":"INVISIBILITY","ui_name":"Invisibility","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAtElEQVQ4jWNgGGjAiC5gKCv7n42LHc5XCWmCs68tKmdgYGBgOP/4MVwfCzGar12+wMD89y8DTM5QVvY/zBAmQk68dvkCAwMDAwPznY1Y5VEMQLYdH0BWh9MFf1X8GRgYGBi0dA0YtHQNcBqG0wB1A2OiXIM3DGD+R3YR0QZcu3wBxem4XIRiwK9vP+FsdH/fvHAWqzqsLsDmXKKi8fzjx4y/vv3E6dxf334y/Pr2EyUlDgMAAI7AOxLMoe+gAAAAAElFTkSuQmCC"},{"stackable":true,"id":"GLOBAL_GORE","ui_name":"More Blood","max_in_perk_pool":1,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA8klEQVQ4jd2SoQvCQBSHPw1OBG3CQAZmm5jEoFmbLIh/oBiGplksGtQkNoNJGIOBTWF4KxrkjjungknwB8c9jve+9+O9g18r8/xQd5wbQK5gpZKTWACwCwJVZwDqjnOThZ5dwY1CAHrdBlYpjzhf8WdbklgoSPadNTcK8ewKAFYpb9y6DMCzbQkZLo4AbJb7VF7Kgeyqx6NOFYBmu/bZgezaH7QMkK7+oPUZ0Os2mIxXuFGohqhrMl4ZkBTAn21Vd7mJS3RSB0Ccr68Bcs+ys+5gvjioeOqt3zv4VgZgFwSZJBbKCTyGVrTLABTtsvGJ/kR3ZHpgVyZMpFYAAAAASUVORK5CYII="},{"stackable":false,"id":"REMOVE_FOG_OF_WAR","ui_name":"All-Seeing Eye","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABKElEQVQ4jdWSMUvDUBSFv4i00KlL6ZQtkLkgxFKXQn5C1oD/IGtoOrYh4CD5B0IGl+AvCGRRoiA4i26dSpdMhdbhdQh5JubhKl54y3n3nHvePQ/+urSfwETXBUBv0CdyEon7qctxfwDgbbPRlAITXRe9QR+AyEmIs5W88+wlfuoCcNwfpMiZylZNnmHj2Utm2MTZquVIWZZpiDwoxI11K/KgEF8fQp4mbpmG6DwhDwoBcHV9+fsU4PHuGYD5eqq1dmCZhoichNesariwv8WamJ+6vLx/agDnqgkP5T0AT1kmsW25qwRoO+ws0U9dQieWpPoAhE4sk1AK1Dk3RcbDUYdc9ykdQBXjIvUkeTwcsUg9ZYydn5gHhWiSm7Utd4ROzHw97fD+cZ0Ax42FWVbNu2YAAAAASUVORK5CYII="},{"stackable":true,"id":"LEVITATION_TRAIL","stackable_is_rare":true,"ui_name":"Levitation Trail","max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA50lEQVQ4y2P8//8/AyWAiYFCwILMYWRkZDCUlf3PwMDAwMbFzsDAwMCQ4lHGwMDAwPDk6R0GGWkVhmnrmhnOP37MCHM5igsMZWX/s3GxwzUzMDAwmOksRrGRjYsdbgmGC9BBikcZw6krEEPMdBgY0nrW4Q8DZJuRwbqdlgynrsRiVYc3EOfs6CIYiIzI0WihoQrnKOffYbg7UQWnxhM3bjPidEHyDYTm5Bt3yHMBIYDVBci2KefjtvnXt5/YA3G6nCycfXeiCkHnY3iBgYGBwUhO7r/z0scMh1NVcNqMnBIZBzwzUWwAALs4VEYPMlhjAAAAAElFTkSuQmCC"},{"stackable":false,"id":"VAMPIRISM","ui_name":"Vampirism","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAl0lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNfHhK6ZjYsdRXOLsBicDZODWYJhADkAxQB0ZyPbjksdQRcoq6riNAivAcia8BnCQsh2BgYGBgk9PYjA21fEuwAGJPT0GDgDAxGG4DMAFs8w22Ga0QGyOpxeiDxxlGE5AwODBJTvOGs6VnUUp8RhAABoQSVybrSjiQAAAABJRU5ErkJggg=="},{"stackable":true,"id":"EXTRA_HP","ui_name":"Extra Health (One-off)","max_in_perk_pool":3,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAwUlEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/bBqxGQQzhAWbgqkKygxCEhIMEnp6DI6zpmPwkQGKAWxc7CiKOQMDGfYzMDC8uHQJhY9sCBM+p35fvx4vH6cB7168YHhx6RIDA9R2bDQMoASiubrKfwYGSBgwMDAwCElIYBic/eAuAwMDA8PJm3cYcboApujdixdYNRP0ArohuDRjGABLKOiGoGtGVkdxShwGAABvOl1UgtJwewAAAABJRU5ErkJggg=="},{"stackable":true,"id":"HEARTS_MORE_EXTRA_HP","stackable_is_rare":true,"ui_name":"Stronger Hearts","stackable_maximum":9,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA+klEQVQ4jd2SMWvCUBSFv2clomOnLCGKBTdBMnR2c3DJn8jSf9JdhP6JjH3g4OxQAtkKlja45C2OlrrcLjW810Q7dCj0budyzvcOvAt/Per7YhIEAuD1OjXz8fABQLbbVTkHMAkCaQo2gU6QdpNh0R9y7fv44zHTh2VN2+MAvF7HMXfjmDVQ5rmjbUjrUtX3NL2ozwL2ZUmZ5/D1emYMV4MBj6tVtT8LuHt7cSCZMYQieHFMKEJmzM8NbMg8SYi0BiDSmnmSOF7nG29HN2LrRX9IoRShCJHWPM1mFEpx/7pl87xVtQanQ7GbhCIUSnFM0yps+359if9gPgHPRnKNiu3ICAAAAABJRU5ErkJggg=="},{"stackable":true,"id":"GLASS_CANNON","stackable_is_rare":true,"ui_name":"Glass Cannon","stackable_maximum":2,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/klEQVQ4jd2SrW7DMBSFvw4kUt5gICxRgioVJSPRWIpCi01KxoryCHuL4FKjhlUDa4dGO3WsoNoLTNqIhxxdJx6etEPs+3/useGvMRs7FnFsdtuGRvWTZN3VLFea18tlqLsZFwdRSKN6dFcDcDydh+JG9QRRyCKOjbfBbtsM9zEDacs8B0WWmI/nB1NkiSmyxEhYn417GchJx9OZVlUAtKoaVhkzcxrYvXVXU+Yp+8MVgP3hSpmnTtzCeQVJDeD+7pbH7olWVUMzi5e395l3Bdl9s55T5imb9dwbn0AKaEWUpxTSq8FypX+dJG2Z5/2JAEEUThh+f34BOD/xH+AHSCOCZa0BKygAAAAASUVORK5CYII="},{"stackable":true,"id":"LOW_HP_DAMAGE_BOOST","stackable_is_rare":true,"ui_name":"Living on the Edge","max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABGklEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlf0/8cMHhlIpcQzF3c9eMuQLCDCcf/yYEeZygABCcQFIMxsXO1gzSDEIND1/BdcMEgfJg9TB9AAEEIoBIJthAN0FyHxkdQABxISuCGYzsu0wGtklMAAQQBiBCJMEaVr35w+YDaJhhqC7DCCAUAyA2Q6i6yTFGC7ISoH5IBrER5aHAYAAYkROBxYaqiiJwuDxM4YgFhawC2CGwcCJG7cZQTRAAGF4Adn0aGZmsM0gGps8CAAEEIYXkP34nZERhQYB9IAGCCAUA0CJBJdNyHxkdQABhBIGsJQIYoMSDDr49e0nmEZOiQABxEhpZgIIIIozE0AAUWwAQIABAJCldTFYlMnJAAAAAElFTkSuQmCC"},{"stackable":true,"id":"RESPAWN","stackable_is_rare":true,"ui_name":"Extra Life (One-off)","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAAA6klEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUA0CasWmEAZgc1BJGEBsggEjyQvoEYwwxgABCMQDZdnTFIL65pSGGOoAAwuoCcXVusGIQDQMzC85idRVAAGEYALKpfVEkmA2isTkbGQAEEAu6AMimDeo3wJor45YzvLz5Fa8BAAGE1QsgTSePnyeoGQQAAgjFAFg8w1yCDGBeAdHI6gACiBE5KRvJyeFNB8gWnXv0CJwOAAIIxQByUiJAADFSmpkAAojizAQQQBQbABBgAOzmUc1BDvzZAAAAAElFTkSuQmCC"},{"stackable":true,"id":"WORM_ATTRACTOR","stackable_is_rare":true,"ui_name":"Worm Attractor","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABTUlEQVQ4jdWRvUtCYRSHn1uRoQS5ubyLH2g5CdrXJRwkaBGyKWhxcswmcXGKFvsPGuQStQTZUENgNgSlYFSLccFouVB37IOCWm6DefFeda+zvMM5z+99Dgf+XEWEMCJCGDNBv2FsnVjeTq97XrLDo04HAKf+DAAT81Nmf3Z3A4Dvzy9uNU0CGBpksvSwA8DL1b0FtpcloPO7PcQOd8/1NXDFcgBIImWGvA1PWnoDA+pqC2Vdx5cu4fQE8KVLJuSK5fCEQ5aQEXvA3GKO1yeV7LKCvCK4vNa4CCsAeMIhHqvHgw3qaotapQiAHBVky2vIUWFC+/kFvIkkTk/AZHrO6AiuApCePkSO/hqMK+hN1WJQP9uW+q5QqxTRGpvkqwoH5TakN1Web9qgN5FEb6r9DToW7niBj0YRSaQwtCOgfYWx9zvc8QLne5ke7h/XD0T8dsYrLoIzAAAAAElFTkSuQmCC"},{"stackable":false,"id":"RADAR_ENEMY","ui_name":"Enemy Radar","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAATJJREFUOI3dkjFPwlAUhT+Mgcj4mqZTG2MCUxkYcJOwOPAjuuA/IC5GFweCA2lYHZzYXRvjQnAiDoxG49RODWkTBiHUAQfSlz4kXU08070395x37r0P/hqF3ULdNDcAi5svAFZv3wztDpoQRHHM3e0jsyCQvMNdcrFcYt6N0V0h61pzG2tCUCyXqJvmJhU52HWQJc+7MX475PpqBIAzGeSPcHIvNllyGvf6juxxJgMsz2D6/rnfQR4ZYNS8VPK9Ail6fUfazzrIiu4V0F2B3w7ztCWUK+iukPYtzyCyt0scP01lz+nrMZEdy1xZYnpGvx1ieYasn7eqPBy9cLE643n8QbJcy7+gOEhheYY6wqq6FWz97lV2MAuCQrJckyzXWJ6B5RkM7Q61RgWAWqOivP5P8ANSD3LiD+7LVQAAAABJRU5ErkJggg=="},{"stackable":false,"id":"FOOD_CLOCK","ui_name":"Eat Your Vegetables","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABE0lEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUF4A0gzSiazaPV2OAGQrCMEtAACCAiPLCyYW3GOL67LDKAQQQihfQbXYt0YCznz97hVUdQADhdcHunhsMAlsCGSSlxBg+vf6HYiAMAAQQCxZ9cBCqUcnAmXaU4fssawY5IP+R+VoMNQABhNOAMov5DKLiogzS7wMY1jNsAIuBwgIdAAQQVi/ANeuJMlzYfY1BUUANpysBAgjFBaB4BgXQ/Q8Qm17vfg2mQfzVN9pR1MEAQABhdQFIMcwQdM3oACCAGJHzAjkpESCAGCnNTAABRHFmAgggig0ACDAAxghZvUZjq/kAAAAASUVORK5CYII="},{"stackable":false,"id":"IRON_STOMACH","ui_name":"Iron Stomach","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADUSURBVHjaYvz//z8DJQAggBgpNQAggDAMMJKTAwuwcbFjKP717SeYPvfoESNMDCCAUAwAacamsXPWIjB948Zthvl9zWCDYIYABBALISeCNK9etRrOTyyqZZjZUgPnAwQQigEw22E2ggCyZnR1IAAQQCz4bNTVMyAYiAABxIQuAPInDFy+dAFsCD6DAAKIYBiADIEBkEGgQEQGAAHERGx8Y9MMAgABxIQtnglpRlYHEEBY0wEoqpABus3I6QAggChOiQABRHFeAAggig0ACCCKDQAIMADt8mQEOemyZAAAAABJRU5ErkJggg=="},{"stackable":false,"id":"WAND_RADAR","ui_name":"Wand Radar","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAARhJREFUOI3dkj9Lw0AYxn8xoiB08Eg4VJJBdBWcHJ37BZyzOdu5n+Gcs2X2C/QzOBVcROjUwz8hJVJKUtsicSg5kjbBUfCZ7o73+T3ve7zw17I2Hy49rwCY9jMA5s8rrMjm/umdu4sjZKfDUGvj29k07x3sM+1nuErgKoEV2bx+pgAcP4yIZzMTsgUASHoprhLmXATfnByu7283Z3jSaR/hNBRF1ewqgY4niNvI1KRhgCcdHl9GVmMHTeY0DIy5CmsFlPKkY8zVDn4FuEow7sYGUlV+9dUOcJUg6a1/3B9Ixt3YgEpdJ+ftgGW+qKX7A4k/kDXDRzZnmS/MfbdphDK9lBXZTWXbgKHWVrkk1WTNpNZldRP/gX4ARsZuvtHuP1MAAAAASUVORK5CYII="},{"stackable":false,"id":"ITEM_RADAR","ui_name":"Item Radar","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA80lEQVQ4jd2SMWrDMBSGPzfFBoMXgcgUHcHQY+QQmbNnzhl0hq6dY+gxCl66S1AwCSYZkpBkSIfyhOXYXQv9JyHxvv/9Tw/+Wkn/4mU2uwMc1kcAzp83ktdJeJ8WBR/eh7qnfnGaZxzWR7RVaKsw71MA3jYbANI8CyYPAIDtqkVbFc5u3gBQVdVghAggzl2QdGCMAcDNG9I8GwYMdeGbHYvlMsTozmMUIJJiYwxlWQaIb3a/A7RVIbuorutBkwigrWK7agFCdu89zrlRyPOYuwAEIhKYKFok2QP4mXZ/YF197ffJA0AgQPRVouvpAhBt4j/QN6ASYfrSLTN5AAAAAElFTkSuQmCC"},{"stackable":false,"id":"MOON_RADAR","ui_name":"Moon Radar","not_in_default_perk_pool":true,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABbklEQVQ4jdWSv0sCARTHP2qad8IZR5co2dAUQVJTRXlFDYc0BQ39Hc79De6NRURQ4RQRFBQFurUE7RrGZQleqPdDsyGSLg/X6I2P7/fzHu994d+X73djLpns3pfLvslduQvQenQAmDiPYTctAO7LZZ8nYC6Z7IbEYarZGkpOdoFLGZ2J8xgAdtPqQYZ+b/DTXM3WWLhcQxAFZg0/7YUPHooFl94FqO80XGb1ViOtqSjKKK13E9OyGIlK3F1c9Dx+r8N8T06vqhjVN1KpGQJ0mJ6aQlVXWNa0wQAAQRRQlFE2t7dIJOLMLy7xqldIxBNEJWkwQMnJDAX8hMNhmvUWALHxMdY3NCKRCKFg0Bug5GSq2RoA7c4HpmnSaDTQn156GjEqYDuON8BuWig5mVJG56FY4ObqmlbbpOPvYBgGlcoz+aMT8nsHPU/fG+ErNKWMziH7cApRSSIUDGI7DmfHxy6tZxIBQuJwH9griX9fn6rMiMjiGhc7AAAAAElFTkSuQmCC"},{"stackable":false,"id":"MAP","ui_name":"Spatial Awareness","not_in_default_perk_pool":true,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA80lEQVQ4jd2SsWrCQACGv4goOJVASJfEoRY3IZNToas0g1DfwqEQdOwDFCRPoltL3kEEx+7JUgkEJ0U7xOlC7nLqKPhN4cj/3Q//wa0x1APPcXKARqtJGmQA7H//AXhcPACwThJDK/AcJ2+0mgCkQYYVmpI8HmxwI5vj7lBIarpa5XAaZMSDDQBuZBffAkkgapfDVmhKQTey+Xvf6gXnWpy7/aJAx3K8KmRXBVZoFisAzL9/GPlvfEym0n/SCv1uJ9fVF2GBPxzy+TUzKoLyjGIyNQxIM9bV6m67Tc/zeOo88zJ+rYRVLr5ElePuAMgv8Q44AZwQXxEJYcXvAAAAAElFTkSuQmCC"},{"stackable":false,"id":"PROTECTION_FIRE","ui_name":"Fire Immunity","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABHklEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUA0CalYI7GEQl5HDa+PrFIwaGtRUg3YwgPkAAsaArAGk+e2g9mL1BdTdDwG1XFHlju0CGe0h8gABCCQN0Zwsa6cENwqUOIIAwXIAODoa/BJJAg25jlwcIIIxYAPsRCXC6O4ExzBXo8gABhGHAk+31DHJqxmD2+3OX4OIg74DEQfLIACCA8KYDUAA+a5/A8H3nPgb7leJY1QAEECNySjSSk/sPCyAZz0aGR7fOwuWQbQelh3OPHoGjESCAcAYi2CtAQ0DRCvI3utNhACCAUFxATkoECCBGSjMTQABRnJkAAohiAwACDAD6MGA/ygZcCQAAAABJRU5ErkJggg=="},{"stackable":false,"id":"PROTECTION_RADIOACTIVITY","ui_name":"Toxic Immunity","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABNElEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUA0CalYI7GEQl5HDa+PrFIwaGtRUg3YwgPkAAoRgAshWk+eyh9WB+ZuMXBi0VY4bc6JtwNcZ2gQxPkFwHEEA4wwCk+fjGxwx6AsEMk5eq43QRQADhNODh9bcMj66/Z8ivjWW4ducsTgMAAgjDALAfgWDbCnkGC39pBkt/WYaVHZfALgK5BCYPAwABxIJuwJPt9Qxyno0Mj26dBRsCAVZAFz0E0meB8vtQ1AMEEMF04BXxEO6irUvtMeQBAgjFAFg8g12hZgzXCAIgPkgcWR0IAAQQhhfQvQKKVpC/YZrRAUAAMSLnBXJSIkAAMVKamQACiOLMBBBAFBsAEGAArBtv1c5sfjIAAAAASUVORK5CYII="},{"stackable":false,"id":"PROTECTION_EXPLOSION","ui_name":"Explosion Immunity","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABPUlEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUA0CalYI7GEQl5HDa+PrFIwaGtRUg3YwgPkAAsaArAGmuEk1hCF7rjSK+rf0dw/czxxnaGOYw3EMSBwgglDCAORukGaRhnvhCFM0wQ5G9BxBAOAMRpEGiwJfh1PocuGaYgcgAIIAwDAD7EQg+HnnDIKJRB2ZzmliC6aSX8XB5GAAIIAwDnmyvZ5BTM2bgtxFheHOjCe4akDdA4iB5ZAAQQBiBCAMgW70qhRjWBntC/L6WAWgApjqAAEJxASyeQbZkzHdFUYhsO0wdCAAEECNyUjaSk/uPHMIyno3gaAX5G9npIAPOPXoETgcAAYRiADkpESCAGCnNTAABRHFmAgggig0ACDAA88N7RvGW4tUAAAAASUVORK5CYII="},{"stackable":false,"id":"PROTECTION_MELEE","ui_name":"Melee Immunity","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABDklEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUA0CalYI7GEQl5HDa+PrFIwaGtRUg3YwgPkAAsaArAGk+e2g9hsZTW3sZzLyLGYztAhnuIYkDBBBKGGBzNggUbYZoxqYOIIBwBmKx7Tu45j7fYlzKGAACCMMLYD8CQe9hIYZDDb8Z7NA0w+RhACCAMFzwZHs9g5yaMUOY9isGuwZWuEtAACQOkkcGAAGE0wurrorBXYIPAAQQigGweIa5Ahkg2w5TBwIAAYQRBihe8WwERyvI3+hOhwGAAGJEzgvkpESAAGKkNDMBBBDFmQkggCg2ACDAAOfJXEJzt5mnAAAAAElFTkSuQmCC"},{"stackable":false,"id":"PROTECTION_ELECTRICITY","ui_name":"Electricity Immunity","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABKElEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUA0CalYI7GEQl5HDa+PrFIwaGtRUg3YwgPkAAYXgBpPmtFBvD2UPrwRjGnl5uA6bRDQcIIBQDYM5+sGIlQ9HmXjj71NZeBjPvYgaFiHAUdSAAEEAoXoABkOZ1S0+B6QigFSv+4fQRA0AAYXgB5Mc+32KGoGgzuGYQH2QYyDXgMEACAAGEYcCT7fUMcmrGYE0gzSCXgACIBomD5JEBQADhTQcgQ1bFmoHZINuxAYAAQjEAFs8wV4ACDeZ/ZNth6kAAIICwBiLMEBmGRoaDz35hdToMAAQQI3JeICclAgQQI6WZCSCAKM5MAAFEsQEAAQYAzVR4VxxjiscAAAAASUVORK5CYII="},{"stackable":false,"id":"TELEPORTITIS","ui_name":"Teleportitis","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAAA5klEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUA0CasWmEAZgc1BJGEBsggFC84B7oD2dHJSbB2Sdu3MZpKEAAkRUGyK4ECCAWdElcNsPYFhqqKOoBAgjFBZJSUgzPnz0Ds/PKKxkmdbajKAZpRvcOQAChGADTDALompENQQYAAYRiwP6dO+DsZfPn4dSEDAACCMUAWDwTAsjqAAKIETkpG8nJ4U0HyAace/QInA4AAgjFAHJSIkAAMVKamQACiOLMBBBAFBsAEGAApeJNjGopeqQAAAAASUVORK5CYII="},{"stackable":false,"id":"TELEPORTITIS_DODGE","ui_name":"Teleportitis Dodge","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAAYRpQ0NQUGhvdG9zaG9wIElDQyBQcm9maWxlAAAokXWRz0sCQRTHv/4Ig4Q6dPAQ4cGiwCKUIKKTgV4qRA0qOrS7uhrs6jK74qFr0FXoEHXp1z/Qqa5BXTsFRSD9DUHUJWT7jlZK6Bvezme++96bmTeAd8fQTNs/B5hlR2RSifDG5lY40EDwdyiaba1mkzn0tc8neOT8OCNr9Y/raUP5gq0BnkGyoVnCIdOxWHMsyVfkUa2k5Mn35KjgAclvUldb7PVLLrY5JFnkMsvkKHmk2MVqF2slYZKXyBHTqGo/55E3CRbK61nOafoYilBgcigII4ZZehhTyKIKCwUIRq0hRS3JCJXrXWgkh//yVNNUKtCpGlSm2efee4239tJRZrRoZdqtPAc1VhPMpcm3+d9zW4/H2tWCCWDg1XXfJ4DAIdCsu+7Xues2LwBfA7gtd/IrZ8DCB/V6R4ucAsP7wPVdR1OPgJsDIPRiKUL5ezVvfL7dP5pPfi6fgdwesPIAHJ8AkzprbX8DrZRo7bGNSRwAAAAgY0hSTQAAhwoAAIwOAAD9TAAAgT8AAH12AADpjAAAPOYAABnNq3W/zAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+QKGwwcG+mhVt4AAAAadEVYdEZpbGUgTmFtZQB0ZWxlcG9ydGl0aXMucG5nUak37gAAOj1pVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ4IDc5LjE2NDAzNiwgMjAxOS8wOC8xMy0wMTowNjo1NyAgICAgICAgIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgICAgICAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICAgICAgICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgICAgICAgICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICAgICAgICAgIHhtbG5zOnN0RXZ0PSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VFdmVudCMiCiAgICAgICAgICAgIHhtbG5zOnRpZmY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vdGlmZi8xLjAvIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8vbnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDx4bXA6Q3JlYXRvclRvb2w+QWRvYmUgUGhvdG9zaG9wIDIxLjAgKFdpbmRvd3MpPC94bXA6Q3JlYXRvclRvb2w+CiAgICAgICAgIDx4bXA6Q3JlYXRlRGF0ZT4yMDE5LTEyLTA4VDE2OjM2OjU5KzAyOjAwPC94bXA6Q3JlYXRlRGF0ZT4KICAgICAgICAgPHhtcDpNb2RpZnlEYXRlPjIwMjAtMTAtMjdUMTQ6Mjg6MjYrMDI6MDA8L3htcDpNb2RpZnlEYXRlPgogICAgICAgICA8eG1wOk1ldGFkYXRhRGF0ZT4yMDIwLTEwLTI3VDE0OjI4OjI2KzAyOjAwPC94bXA6TWV0YWRhdGFEYXRlPgogICAgICAgICA8ZGM6Zm9ybWF0PmltYWdlL3BuZzwvZGM6Zm9ybWF0PgogICAgICAgICA8cGhvdG9zaG9wOkNvbG9yTW9kZT4zPC9waG90b3Nob3A6Q29sb3JNb2RlPgogICAgICAgICA8cGhvdG9zaG9wOklDQ1Byb2ZpbGU+Z2FtbWEgMi4yIChTdXBlclBORyBGYWJyaWNhdGVkIFByb2ZpbGUpPC9waG90b3Nob3A6SUNDUHJvZmlsZT4KICAgICAgICAgPHhtcE1NOkluc3RhbmNlSUQ+eG1wLmlpZDo5MGNiMGM4ZS0xZjllLWU1NDUtODAwOS1hMzk5MmMyYTRjNTM8L3htcE1NOkluc3RhbmNlSUQ+CiAgICAgICAgIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkNGUwY2FjNS00YmIyLWY2NDktOGI5MS01OTljMDE4NDBhM2U8L3htcE1NOkRvY3VtZW50SUQ+CiAgICAgICAgIDx4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ+eG1wLmRpZDpmNjRkOWE4ZS1jMDUxLTk5NGItODRmMC01MDc4NWM2NGY5OTk8L3htcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD4KICAgICAgICAgPHhtcE1NOkhpc3Rvcnk+CiAgICAgICAgICAgIDxyZGY6U2VxPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5jcmVhdGVkPC9zdEV2dDphY3Rpb24+CiAgICAgICAgICAgICAgICAgIDxzdEV2dDppbnN0YW5jZUlEPnhtcC5paWQ6ZjY0ZDlhOGUtYzA1MS05OTRiLTg0ZjAtNTA3ODVjNjRmOTk5PC9zdEV2dDppbnN0YW5jZUlEPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6d2hlbj4yMDE5LTEyLTA4VDE2OjM2OjU5KzAyOjAwPC9zdEV2dDp3aGVuPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6c29mdHdhcmVBZ2VudD5BZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cyk8L3N0RXZ0OnNvZnR3YXJlQWdlbnQ+CiAgICAgICAgICAgICAgIDwvcmRmOmxpPgogICAgICAgICAgICAgICA8cmRmOmxpIHJkZjpwYXJzZVR5cGU9IlJlc291cmNlIj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OmFjdGlvbj5zYXZlZDwvc3RFdnQ6YWN0aW9uPgogICAgICAgICAgICAgICAgICA8c3RFdnQ6aW5zdGFuY2VJRD54bXAuaWlkOjkwY2IwYzhlLTFmOWUtZTU0NS04MDA5LWEzOTkyYzJhNGM1Mzwvc3RFdnQ6aW5zdGFuY2VJRD4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OndoZW4+MjAyMC0xMC0yN1QxNDoyODoyNiswMjowMDwvc3RFdnQ6d2hlbj4KICAgICAgICAgICAgICAgICAgPHN0RXZ0OnNvZnR3YXJlQWdlbnQ+QWRvYmUgUGhvdG9zaG9wIDIxLjAgKFdpbmRvd3MpPC9zdEV2dDpzb2Z0d2FyZUFnZW50PgogICAgICAgICAgICAgICAgICA8c3RFdnQ6Y2hhbmdlZD4vPC9zdEV2dDpjaGFuZ2VkPgogICAgICAgICAgICAgICA8L3JkZjpsaT4KICAgICAgICAgICAgPC9yZGY6U2VxPgogICAgICAgICA8L3htcE1NOkhpc3Rvcnk+CiAgICAgICAgIDx0aWZmOk9yaWVudGF0aW9uPjE8L3RpZmY6T3JpZW50YXRpb24+CiAgICAgICAgIDx0aWZmOlhSZXNvbHV0aW9uPjcyMDAwMC8xMDAwMDwvdGlmZjpYUmVzb2x1dGlvbj4KICAgICAgICAgPHRpZmY6WVJlc29sdXRpb24+NzIwMDAwLzEwMDAwPC90aWZmOllSZXNvbHV0aW9uPgogICAgICAgICA8dGlmZjpSZXNvbHV0aW9uVW5pdD4yPC90aWZmOlJlc29sdXRpb25Vbml0PgogICAgICAgICA8ZXhpZjpDb2xvclNwYWNlPjE8L2V4aWY6Q29sb3JTcGFjZT4KICAgICAgICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjE2PC9leGlmOlBpeGVsWERpbWVuc2lvbj4KICAgICAgICAgPGV4aWY6UGl4ZWxZRGltZW5zaW9uPjE2PC9leGlmOlBpeGVsWURpbWVuc2lvbj4KICAgICAgPC9yZGY6RGVzY3JpcHRpb24+CiAgIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz5vztxyAAAAGHRFWHRTb2Z0d2FyZQBBZG9iZSBQaG90b3Nob3A0y45nAAAAD3RFWHRXcml0ZXIAU3VwZXJQTkfFxK/dAAAA4klEQVQ4y8WTMavCMBSFv4i00Llk001w1am/oVP7n8TVn9IO0ufiIghOdRXc4hY6C7rESUna+J6PDp4lJBzOPfeeG2GMoQ8G9MTQvgghmI1GBqBWimQ6ccj36w2A4+Uins6F3cJ8PDa1Up0qPqFaKeFtoU1OphMOp/NnMwiikDTLHUKa5R3RIAr9AmmWU5UFAKvlAoCqLDqibx1UZUEspUOIpXyJ/plCLCWN1qyWC7abHwAarYmlZL3be2fkOGi09lZpv9tD7cQYROHLyfNs49cY/wvHgb2JdlQfb+JXPlNvgQeMt2Pi1sb4ywAAAABJRU5ErkJggg=="},{"stackable":true,"id":"STAINLESS_ARMOUR","stackable_is_rare":true,"ui_name":"Stainless Armour","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABIElEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUA0CasWmEAZgc1BJGEBsggFjQFZnFlTBIykoy2JvoMhQFusHF+9bvYjh45jLD88fPGY7MaIWLAwQQigEgG558/A1kPWc4iKQJZBhMM0ge2ZUAAcSIHAsWGqpgjpR/PoMMPytYDOQakEYQAGl+tnEimH3ixm2wFwACCGssgBSBFIPwmm1H4WyYZmQAEEAYYQADDx89BtPycrJwNisWdQABhNMAkEaQN0BeANFgF5zHVAcQQChegMUzsjdgAYfsfGR1AAGE0wUwQ57hUwAEAAGEEgvkpESAAGKkNDMBBBDFmQkggCg2ACDAAKGif/qUbuDzAAAAAElFTkSuQmCC"},{"stackable":false,"id":"EDIT_WANDS_EVERYWHERE","ui_name":"Tinker With Wands Everywhere","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAqUlEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/bBqxGQQzhIlEF2MAFAOw2a7acghDDFkdQRfcrrGDG3Lixi38LoDZyJK8iOGJkgecvn//AUPR7ssMqx7/ZCjafRlFPUogmqur/MfmCtWWQwy3a+wY/k4+wcCca8HAwMDAcPLmHeICEaaZgYEBrhmnCwYkGilOicMAAACHPUAKxlMj/gAAAABJRU5ErkJggg=="},{"stackable":false,"id":"NO_WAND_EDITING","ui_name":"No Wand Tinkering","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAEBSURBVHjaYvz//z8DJQAggJgYKAQAAcSCLmAkJwd2EhsXO4biX99+gulzjx4xwsQAAogR2Qsgzcga+0XE4ezCNy9RDIIZAhBALLicBtKMrAmdDwMAAYQSBjDbkW1WbTkEdwFMHNmVAAHEhM9mEPt2jR3cEMsjh1EMBwGAAMIaCyzJixhC+Qzhhty//4ChaPdlhlWPf2KoBQggrAb8mRvHIHNvB9wl3RtLGfpcdRm6b3zFUAsQQBgGIPsVBKYVrIa7ZFKjP0ZAAgQQE7Z4RjYEFAYwTTAapg4EAAIIZzSiuwRbFIIAQAAxoucFUlMiQAAxUpqZAAKI4swEEEAUGwAQYADd62pd5AzHJwAAAABJRU5ErkJggg=="},{"stackable":true,"id":"WAND_EXPERIMENTER","stackable_is_rare":true,"ui_name":"Wand Experimenter","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA90lEQVQ4jdWRoW4CQRRFz7YNkyCa9c2uglILoiAqmlaBxGD4hGIQfAFqJfwECQkS07RpUGRFK2oaJAKNakITchFk6BLYpcGQXjMzufed92YGTi0nepBEwfcFkEobANzAMG8tAPj5Xq/v06njOOvSsyig4PtKpc2mGODh/nGzt55tsgOIFrqBoRpWqF3eUQ0ruIHZm7uIu9u8tWDAEEIY3A7jYtsT7NPr20uifxBgH/BoQPP583jA+GsCwLI7/hvA/rNV6eYagPNGibhc4gTZ9ohO74Nse5QU+5Uk8p6nvOep3p+pmMvoqvykYi6jen8m60k6DPs/WgHw/Emrxgaa5QAAAABJRU5ErkJggg=="},{"stackable":false,"id":"ADVENTURER","ui_name":"Healthy Exploration","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABHElEQVQ4jd2Sv0rDUBSHvxSxKJQsRRxabkLbpdslU7bSRXwDJ1/DZ/ANjHRycuhscDAUF8c72TUJzSC2CFKobZc6SC5JvKCb4G+75893zrnnwF/Lqhpku70D2D+sfwverjYAqNlM5+1Vk4uJcjCk0+0xHgXIwRDhuKRJDHfhLodYJoAvbda2h5pEGvQ4fWO5yDg9v6CWhFwF1xZArQjIq69tj+z5QduF47JcZACEN5cIx9W+EiCvpiYR87mFL20A0iSm0WwB0Gi2vsYw/QFAp9vTrT+pd3xpI+pT6PcRjkeaxNpvBIxHQektjg5IXz9QKkJVg00jFHV2cszt/YvuxKQSIN9zMTlXEVKMM67xJ21XG4x3kEPg95f4D/QJ0W9sy2+BuvUAAAAASUVORK5CYII="},{"stackable":false,"id":"ABILITY_ACTIONS_MATERIALIZED","ui_name":"Bombs Materialized","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABE0lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYkC5p8P/p5+/MUjzcjEwMDAw4GMvOXKKkYGBgYEF2YBdV+4yuOkoMxy494SBgYGBwUFJBicbBlAMQHb2iiwXBgYGBobYZ2wMyRvu4QgBNAMclGQYnn7+hrBBk4Fh8Y5fDGZSoijiTz9/w24AzIm3X7+D2DztHsOKyS4MEbl74OLogAmX02DOjsjdw7BisguKXKCnGZyNEgvm6ir/cRkoZOHJwMDAwKDP9IBh/7HrDCdv3mHEcAEsnrFpllJQZmBgYGA48p4fRR1OL6CDZw/uMnz++B5DnOiUyKppwcDAwMDw9exBlJQ4DAAAc79lTnCI1q8AAAAASUVORK5CYII="},{"stackable":false,"id":"PROJECTILE_HOMING","ui_name":"Homing Shots","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAx0lEQVQ4jWNgGGjAiMyREhD4L87Ly8DAwMDAxsWOVcOvbz8ZXn7+zPDswwdGBgYGBiZkSXFeXgY2Lna45gWePhgGsHGxM8AswTAAGUyRVUChcQEUA5Bt5tHRYUjYvoWBR0cHwyXI3kMJA3N1lf/ILuDR0WH4cuUKQ87jBxg2n7x5BzMMkAFME4w+ceMWYS+gg4TtW+BsCw01rIbgNQAdWGio4Tfg17efOJ2KLP7r20/cLoDZAtMAo7HZzsBAYkqE2YycEocBAAAsYUOswLrIcQAAAABJRU5ErkJggg=="},{"stackable":false,"id":"PROJECTILE_HOMING_SHOOTER","ui_name":"Boomerang Spells","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA6ElEQVQ4jd2SMQrCQBBFnyIJRHsrBYOiFhaaQs9gId7AHMM2nTewzREEzyBoEexEURLQKgeIoI0WkiXZTcBO8MMWMzv//9nZgV+jICf6tdoLQDP0VH7cc1jt5gDsbzfBK8pkzdAzyZvzmulwgWbowkQRyEMYBRyuHmEUKHcpAdk5RrXcYDKaUS03lLrcDrbHMwCWaQuRZJwrEBNHnRaWaeP5LpXuUhzPd1MiqV8YtpsvMjDuOcBnFp7vArA7XQpKB7G7jDAKMgeoCAzqdaVAfrNl2jzvDxGXMmUTiFvOw9ebCAjn5Cb+Ad64/EeuGCmQvwAAAABJRU5ErkJggg=="},{"stackable":false,"id":"UNLIMITED_SPELLS","ui_name":"Unlimited Spells","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAADoSURBVHjaYvz//z8DJQAggJgYKAQAAcSCLmAkJwd2EhsXO4biX99+gulzjx4xwsQAAogJXTNII1ABw4kbt1E0g/ggcZA8zBIQAAggBlAYwLC5usp/EADRyGwcYmA9AAGENwwsNFTBNoNoXAAggPAaANOM7h1kABBALLhshbFxicEAQACx4HI6DBTtvszQ56qL0xsAAUTAC7fA9N/JJ3CqAQggJmzxjHCJGphmzrVgwKUOIIDwukC15RDDpBXnwTQuABBAjOh5AT0lPlHyYJC5twNnSgQIIEZKMxNAAFGcmQACiGIDAAIMACA9dWg2dMP4AAAAAElFTkSuQmCC"},{"stackable":false,"id":"FREEZE_FIELD","ui_name":"Freeze Field","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAyUlEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOz/3M1XGVZe+8LAwMDAEK7Fg2LAymtfGMK1eBgm+2rDDWFCt2VmuBGGRgMlNrhmAyU2FDkUA2DONlBiQ1F84d4vhnYfIQYGBgaGTFMdFO9huACmwUCJDU7DNCbq82KoxWrAzHAjuEtwacRrAAwY8kKcOv/iZwYGBgYGCw1V0gyAaUAPOJwGwOIZ2XZ0Nro6vC5Adwk2QHFKHAYAAMXzO2gY/beZAAAAAElFTkSuQmCC"},{"stackable":false,"id":"FIRE_GAS","ui_name":"Gas fire","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAxElEQVQ4jd2SMQ6CQBBFn2I02EG/VyDBhp4GaTkOp/AktGIjN8CEK9BLJ9HEYLVkd2ENnYm/2Z3NzJ8/8xd+jZX5EAoxAGz3u0ny6/EE4Na2Y51GEAoxqIWp74z38/2tEUmSjU1a6jtakRlLrNVAdpfJxdHVFEhFqkqNwOzkHQIAqjyakFgJJKo8wk3i8bTBStDVDQBuEtNfrto4KiZLlDKzsqfghHcI6OqGrOxnF2m18ZsLi2w0FzZn4USBVAHLf+If4AMuu1ibB4ae5AAAAABJRU5ErkJggg=="},{"stackable":false,"id":"DISSOLVE_POWDERS","ui_name":"Dissolve Powders","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABB0lEQVQ4jd2SP0vDQBjGf3Wo4NatGA+DyFW4KZNWUIglIK5Ozukk+BUq+jnsJxHrVnCKSwaLBEu90DGTYJc45ci/1sGh4DMdd8/7u5fnfWHdapQvHCFSgObWZsW8+PoGIJjNTF0B4AiR1hXWgTLIxm/mvt+rPWcqAMq/9/0e02gOgOcqHoZPFd/SDga3PsdnDrt7bQAen0MAlLSWd5AvtmWL8Shge8fGc5V5Cyd6NUBJi/jzg/EoYBrNsWWL7umRgeRhUJrCYWc/zUyxTuieHJgMYp1weXXO/d0QgJe390YF4AiRXt9cmLDyygKNdULwGpkxrtwDJS3CicZzlQkRinvw5038B/oB1mhh6VRNIJcAAAAASUVORK5CYII="},{"stackable":true,"id":"BLEED_SLIME","stackable_is_rare":true,"ui_name":"Slime Blood","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABHUlEQVQ4jd2SMUvDQBiGnzo0pSUVh0KX4FDRyaE4ZCidxTgInTr3H/gLSjY3ZxfnClJxsCIuSqmSQboJBkEkS6GC0ELIZYmD9siZRNwEXzju4/jeh++9O/hr5b4f1A0jAsgXtURz6AsAxp4nfQqgbhhRmnHX2kIrFxCzgIvBA6EvJGTpp/GO1wcAaOWCsselAPJFTZoAOq5F1xyy+XIAwP3to+xLjWBurEWLumsOWX2fA3BSO6K0/Cb7+r0RztNzdoTtnT1sp8nrig6QMLfajfQIAK12g6vLcwAFkqUEoN8bKfdgO03mk6lcAGIWpAMW79xxLQD2Y6DrG1fWZ6d32RPEdfgF+nWEseflQl/ISeDzTvRqBQC9WlE+0T/RB8x9Ys55an//AAAAAElFTkSuQmCC"},{"stackable":false,"id":"BLEED_OIL","ui_name":"Oil Blood","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABRUlEQVQ4jd2SsWvCQBSHP0tJIFsjWiwIRSKFpg4dQnFzVzJ0cPM/KB0EB3FyEIVCh9K1k506OIgdSpdsQRycHCzikkqhxYyBZLFTj0TTudBvubt37/3e+x0Hf01iO3CezW4AJEUWsXqjTFJVWbsuvfaAqeOIur3tYkmRkRQZs2qKeFJVxSopsmiyIxBm+DTErJoUSwYPjzYA93ejnbyIQLizWTXRjo9IpTOc6acMRjOurisiL/YNLk60DUBO1zhMHZBKZwBYrd65rOgir9XsM54vErEWOt0ay9lC2Pj6/Ijct5p9Ot1avIV6o0yr2QfAtiYUSwa2NYl032Y/fLi9eebHwnK2wLYmAFgv40jR2nXjJwg8H0BYCPNqvYl9rz2IFwiT07Vfxw4TEZg6TiLwfALPF1PUG2UKRh6AgpEn8PzIT/wHfAOVeGQuiiSRQAAAAABJRU5ErkJggg=="},{"stackable":false,"id":"BLEED_GAS","ui_name":"Gas Blood","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABJklEQVQ4jd2SMUvDUBSFv4q00DEZmqV0sUMJHQRLEVe3+gsEF2dRCGQQpw5qQegg/gFx72QH0UIRHDJ1KgWHQsjyMrw3BhKHOkhDniaLi+Cd7n2c+91z4MFfV+n7g3t/tgKQQiH8ELvbYucDTMNAKsWgP2IWBOneRnZ5u15fZWerUWPuLTANA/iClKsVTacBAObeAikUpmUg/BCAi/MHAO5uH39E0ADlagU5WSL8ECkUdrcFgH28y/DljZPTg1RX6ABIIesY6x7g8vqo2EFWICdLXq/GCD/EatRw9vfSOFmIBnDcXpo36+awuZVnFIDN7DC8GeeKpk+eDlUq30ESxYWXnqfvaT/oj/IBvykNMAuCUhLFmhPH7dHuNAFod5okUaz9xH9QnzRvaej80eyXAAAAAElFTkSuQmCC"},{"stackable":true,"id":"SHIELD","stackable_how_often_reappears":10,"ui_name":"Permanent Shield","stackable_maximum":5,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAwUlEQVQ4jd2SQQqDMBBFf0tVkG67zAHEgLjwHDlKzpKjeIWuCi6KMOIBvEIR1IUuSkJG66K0UOhfJTP5bybDAL/WYR3IhZgBIIyjzeOxHwAA965zPgbIhZh9Y2HInSstGchCTnutFYbQUMvuPsSKAWz1whDodsVUly5HD+UgfpfHvcq+GQCmukRDLfvWS8C7+j6g0hKpTBBkisWDTCGVyWaQbIhjPyCMI1RaPmdxvricb7b7sAGsO9nbA18fb+IfaAEgo00bGCHREwAAAABJRU5ErkJggg=="},{"stackable":true,"id":"REVENGE_EXPLOSION","stackable_is_rare":true,"ui_name":"Revenge Explosion","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA20lEQVQ4jd2SoQ7CQAyGPxBbco4HQPIAM+SCRjJJUAtyb4HgLU6SKYLc5PSyzPAAk7zDEjCgrrDdBgJBQk0vTfv1b3vwaxt1A8F0egfwlO8k35orAOfLRerG3eLjZtIqLs1S3p7y8ZQvTRwAQJQ2g3JfYb0A27k8nwBIQgXzWGI6zlt5vQoAdLAWiIAGzAGIzMow2x8AxL+qGwToOHeSAOrdliRU6GD9HmBHqLMCKvNUs1p8HsHeuTTL5zUqQ50V1FkhEJvXqyAJlWzb+ihtiNIGHefOQr/+iX9gDwuMUt5jFPm8AAAAAElFTkSuQmCC"},{"stackable":true,"id":"REVENGE_TENTACLE","stackable_is_rare":true,"ui_name":"Revenge Tentacle","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA3ElEQVQ4jd2SMQ6CMBiFP4zRxCMQegidTFh0deAGTm7egYlzMHEDLoCDrDKzmEiNmysJLDq1oQWMm4n/1Lz0ff97TeHX49jCUogXwHp31NrlFAPQ1g0AhZTa59jm2WLOanPobVIQBVKQiX1RmT3hcqtKPOEauj0GQMW+VSUAz+uZNIl6kG69qU30hIsnXNIk0po6B/sQNgejTg+Q5xm+vx2M24UOVgC0OdiHg5CPgMsp5i4fOsk3EAPQ1o2G+P52FKL+w2CFbpKxtxhNUEjptHVjbMjzzNjc/UR/Mm/zQWHO9UzqqAAAAABJRU5ErkJggg=="},{"stackable":false,"id":"REVENGE_RATS","ui_name":"Revenge Rats","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA6ElEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix2rhl/ffjKcf/wYro8JXTMbFzuKZk0FaRSajYsdbgmGAejAWkOeYf6OA3AaG0AxAN3ZfFKSDMUBLgwMDAwMNprKDNYa8hjq8Lrg5q0fDHxSkgwMDAwM5uqKDC/evcdQw4LPADVBJYabt+4xMDAIMtx7cgGrGpwGpHiUofBxGYDXCwwMDAwy0ip45VEM+PXtJ4YCM53FDEHuxxmUZAywqsPpgjk7uhi8sv8znLoSy7BupyVDU60wVnU4U2JWUC1c7MnTOww7Lq+D24ycEocBAACe40BcVu0n3AAAAABJRU5ErkJggg=="},{"stackable":true,"id":"REVENGE_BULLET","stackable_is_rare":true,"ui_name":"Revenge bullets","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA1UlEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNfHgq4Zm0YYOHv1EQMDAwODsbbcf5ghTOiKvPUSGbICGohyPgMDmhfM1VX+w9gLs3YyxE9zh8sdP32bwdJUFc4/efMOdhfAQPw0d4YNE49h1YwMcBqAy2aSDPh48zODpakqw/HTtxmOn76NVQ0LVlEGBoasgAZ4GMBcADME2UUogWgoK/tfV8qFQUVOhWHrpfk4Xfbr2094WsBwwc0PhxlufjiMUzM6oDglDgMAAD3RSNGwTA1nAAAAAElFTkSuQmCC"},{"stackable":true,"id":"ATTACK_FOOT","stackable_is_rare":true,"ui_name":"Lukki Mutation","stackable_maximum":3,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/ElEQVQ4jd2SoW7DMBBA36ap1UL2BZEKy6IGtcClJlbBwH7AaNv/jOUXBqaQ0ARsqFNZYKRG4Q1ItZEMOYprRxqbtEMn+967s3Xw13F1ebAKw/5ePpEWiVP83X0B8Hk8Dtz1FLxcbIdzJTRxJFlvdsyCOasw7M3dzViw3uxo2hqAsspRQtO0tXca7wQA+0MGQBxJ0iKhO50daBbM/QJTrIQe8rLKJ7s7AlPctLUFxpFECf27J4y7G3h/yEiLxCtxBOPucSQtmU9iCcaAyS//IC0S4khOT1BWuQUvF1sLAPh4fxtyZxMfH5777nQmuLu1JlBC85q9APYm/oP4AQ5IdsPWu6WHAAAAAElFTkSuQmCC"},{"stackable":true,"id":"LEGGY_FEET","stackable_is_rare":true,"ui_name":"Leggy Mutation","not_in_default_perk_pool":true,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4jd2SPWrDQBCFPxmjQDA4hQKWQZ1AtSufyE0SYlSbuBF2UjgoqnWDvUaqVAF1gXQGu/AWCQiD3Uya7Nr6OUDIg2Xh7cybebMDfw6jIJBREMg4CqVQSgqlZByFMo5CGQWBFErJebxTT3YvL8iTJQCl3gHQ865tTKl33D4+8b5eOwDdegermzsbGGcpAOk0tu+Ga+2gUEq+txv6vm+5ycOs1erbx6cD0KmwIr/VtT15siBPFgD2PkfVguPQH/hM5qeqebKwQqXWDYFOg+E0B2MhzlJKrRv+oTaD1+xF+gOfr+2Gq+HQCrShdQb3q2cm8xld17XtGt/nP3HcH9o7MHtgkE5jep5nxYyF4/5g96AiYEQA6kJxltrKJvmf4AcziYE05oCcvQAAAABJRU5ErkJggg=="},{"stackable":true,"id":"PLAGUE_RATS","stackable_is_rare":true,"ui_name":"Plague Rats","stackable_maximum":5,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA4ElEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/bBqxGQQzhIlEF2MAFAOIsR1dHcUuYMEl4elkwcDAwMDw6dlzhqM3HuI0AKsLrDXkMfiaCtIMDAwMcBqnAdYa8gx8UpIoYnxSkgzzdxxgsNaQZ5i/4wBuA+qtbOHsT8+eM3x69hxuWHGACwMDAwODjaYyigtRDKjesweuGRkg883VFVHkMFJigrv9/4vX7jGwcbEzaCpIM1x/8JSBgQHi94vX7jHoaykxLNh5EEPfEAYAn505er7ae2YAAAAASUVORK5CYII="},{"stackable":false,"id":"VOMIT_RATS","ui_name":"Spontaneous Generation","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABI0lEQVQ4jdWSMUvDUBSFPyVo2iFDtlSCtRpq6VQQClaQguCqm4Obu4t/yz/g4FCKUMHiJEYCogQb2tIMHWJaC3GIPpq+h47iWd6795573nmXC3+NpcVEzbYTgJX8qkSeRhMA7n1f9GUEaradqBpVQt8iy7+RK8W1zLmIjIDq9WLJpuN6mLpGx/UkntJBlNMBaGyvA3BxdADAXmVT5JQCzuEOAPn3WBDHvUDU6+WNn7/gXd1lhACMgkUYz0Q8fwfQVPZNx2AAbH2kZVPXCOMZjy9v0jAzDqbRhObZrojbQx+AG/dV5Lr9kdgHSaB63EhtemOCXkB1v0x76BPldLr9EZDOZx7SJp6enySmYwDw0HrCKlgEX4MMb58x6yWuL1tS3z/GJ9ilWx1C0RQgAAAAAElFTkSuQmCC"},{"stackable":false,"id":"CORDYCEPS","ui_name":"Cordyceps","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAzElEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix2rhl/ffjKcf/wYrg/FAENZ2f/IGqUSvBh0bHgZrhz5zPBswTashjDhcppUgheDoKQYAwMDA4OgpBiDVIIXVnUsyBxk258t2MbwjIGBIaFjEUNpexwDLnU4XQADchoyeOXxGtC24SADm7wsQ9uGg6QbsP78bQZNeSkGBgYGBk15KYb152+TZsD1h88YAg1VUWhsAG80ItsaaKgKZxMVjeiuwAVISom/vv1kYGBgQEmJwwAAAKCrRmRm03TjAAAAAElFTkSuQmCC"},{"stackable":false,"id":"MOLD","ui_name":"Fungal Colony","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA9UlEQVQ4jd2SqwoCQRhGz4rs4jaL4GUXwSKYTHaDwaRpm2ww2QV9A2FfwSDbbCaDwVewWiyuF7DYFC0aZIZZGTQK/jAM/DPn8M0Ffl3Ge6PqOA8A07ZojD3ZX3Sn3C83AFZRZGgFVcd5mLYFEIMB9psU+dJVioQkoYtVGfpaeL9JURn6FHttuRYTmLZFzm9KSDefjycOk7lkkqpAwOfjiXQ2E4OAGKgVqBsOQDAKAXBrBbxWXXda/R2IcsuFT8vfBapkOlt+F4h3hld8r1Vnu97JISTqPm2CYBTSH3TkHQD0Bx22693nBKsoMlS7AN9Tqj/xD+oJSFBk3poIkzUAAAAASUVORK5CYII="},{"stackable":false,"id":"WORM_SMALLER_HOLES","ui_name":"Feared by Worms","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABj0lEQVQ4jd2QPUgjURSFv1E0oKhgpZFHgv8QG8MIkWmW3dFGBA1YiCJimWKtFHFKCaKdjaCgBJdli4AWksrYCIMBh9gkEH8xDBorwWIFLXwW4uCYWAve7h3OOe+7F756lI9CtxASIB0dIBRNOnrK0AkaCQCObVspWdAthKys8jjvuNrEiHVNytBdZU8Pj05J2Wdob+G42kQomiRl6CV9roL3v7+FSxG89xURVPfMvu4mhl0Ekfgo/Wth2ic6PidI5c6I/b6lZXKTqoY2WiY3GbGu6V8Lc373yLyvgnymQCQ+6mRcRwzpM/L+Jsf0UBZNFZiWzUFNjPLnJfxagCszi18L0FrvIbmb5o+RUFwEh3vLAGiqYHp7DE0VXO7vsuo958rMog8GAdhYTJReobdvljpvJ6ZlsxL+i2nZNP8aJHLTil8LADDvq8DX1Ug+UyheQUop7aMF5vZ/cJvN0RDo5CI2hSdYi6+r0fHlMwUO/qWVogKAn+Pr8v/RMooYRto7jt4+0cHp1gmeYK0T/ibzAvSPj0+5mobaAAAAAElFTkSuQmCC"},{"stackable":true,"id":"PROJECTILE_REPULSION","stackable_is_rare":true,"ui_name":"Projectile Repulsion Field","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAuklEQVQ4jd2SMQqDQBBFf0RXsA2pZNvY6gX2Qt5hC+/ghbxA0morW4llhKyCqXbJxDGNRSAPlmGG+Z8ZZoFfc/osFFKuACCSeNNspycA4Nb3XkcMCilXJyyV3hjUTeWNnEnIjVUqjW4Y2bozcQTviUhiIm7N3T8A6IYRpdJkPXYCJx4fBnaZIcIIrQGyNN/0BYzWY5eZRI6vBiKMSOTYXSFLc7SG5hzsGfeucL2cUTcVOePhf3D4J/4BL7VUWKIn/xI+AAAAAElFTkSuQmCC"},{"stackable":true,"id":"RISKY_CRITICAL","stackable_is_rare":true,"ui_name":"Close Call","stackable_maximum":3,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABN0lEQVQ4jd2SP0vDYBCHHxuN6CwuoXQKmA4Bp0aCCKUfQP0oEeIX6NIO+Q4FJyHB0VVETAkoZOkQOoTgIm6BQAOlDk1e8gddBW863rvnd/feHfy17TQfTrvdzdtwCIDhv9RiebYC4D1JBFcTKOHQsgDQHUfEqmJ5thIinWYHXhCgOw5hGHI3GjHRNC6/PvENU+SUHQLsVmH5cF9U84GJpvH6/ASF7xd5oWXBbNYWAJiuc+GX8Nn5BddZRtTrMVdVBq4rclpfAPANk5uDPQErigKwhaMINY5/FijhZfLBw9Ext4tFC64OtPMbXE3UdR01jvGCoFawJjC/OhGwFwT4hkloWQyiCN1xhGB5D9AY4v3jUlS2JXm7jeIUmpVLa12i2+9vxmkqVmpLMtN1ji3JjNMUqF/iP7BvOYCEumOrgioAAAAASUVORK5CYII="},{"stackable":true,"id":"FUNGAL_DISEASE","stackable_is_rare":true,"ui_name":"Fungal Disease","stackable_maximum":3,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABJUlEQVQ4jd2Sv0rDUBTGf21EURzFCJrMDi6BOnVxC7hLEKyLdPQJMvcJHMXFCCG4F7J1cRALWXyBJhaSkkEQIile4pRLYlJXwQMXvnvP/b7zF/7aOj8fDE0rANa3NloJyywniCLJqwkYmlZUibrtShyOzltFuqtS020X87CLiFNEnNbEqlYTKKPrtouIU3xvipKEnJ7sAjB0vNq/1gyGjoeIU5QkBMC0evjeFBGnjCeLRia1HpzdvxQl2eirBE8JgMRC1QFQ9nZ4vDxu9qAauSRXcbWc1hLmd9eYVk/ejb6KUHV5AMaTBcb2vL2Ecoz7VzcAzPxn6Vt7e2DoeNwOrNoYf92DzSNb4s/XkcQr9yCIos4yy1lmOQAf7zPp+zq4aJD/iX0DTyaFdEwohA0AAAAASUVORK5CYII="},{"stackable":true,"id":"PROJECTILE_SLOW_FIELD","stackable_is_rare":true,"ui_name":"Projectile Slower","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA1ElEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/mMZsuzoMA6YeaoIbBDOEBZuzsu3qGG69fotVHGYIDDAhc9i42OGabz69wNCs8ZLh2K2tcPat128Zsu3qULyHYgAyWOUsycCQnMzw4+87OBsbwGpAs8ZLuIajgW44NeM0oPaGOAPD3LkMDAwMDNbrd8HZ2ADWQGRgYGAI2/ucYRXDXAYOZiE4m4FBHEMd1mjEFQtqosIMUw81oUQjxemA4pQ4DAAA0xpfmR0w1UwAAAAASUVORK5CYII="},{"stackable":true,"id":"PROJECTILE_REPULSION_SECTOR","stackable_is_rare":true,"ui_name":"Projectile Repulsion Sector","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABE0lEQVQ4jd2SPUvDUBiFH420pXdrcAqFdqgiUtBJp2z5Gy6OxSVzcZBuQraOLo7+hWyZdLJQxH4MLYRMkm63NMWgU0JukwhOgmc8vOc5Z3jhr7W3a5w3m18AlXo19ayTHu77EIDtOuLV99Pc/m64Uq8qYYBpMKJn3pKAk5IcoEyhDJh9hCkkKwVw6rzkDoTWQhcG02CUetmFCmDeN+kMPACeJzNlAVC4QgEcXD+yWCyx3TFPfoTtjtnEKzbxilAGyopCwOfDFe12C8fqcj+ROFaXmtagpjXQhcGxcfYzoDPwmPdNALSby9TXhQHA0aHO0LsrB7zZF7kGGS8JZaC0b9dRMaBMujAK2+GXn5g0Zz/xH+gbIjNh4+V3miYAAAAASUVORK5CYII="},{"stackable":false,"id":"PROJECTILE_EATER_SECTOR","ui_name":"Projectile Eater","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABL0lEQVQ4jdWSIUgDURjHf+eN2zEQEVEEtyLeLAZFGFiubhgEDWJZmxb7khYxuRWDzWZfWBizrggrC1q8s52CaBARxtvhcaY9d3tPq/ilx//7/r//9+CDvy5jXFjL5WIAK5Omk7dxPSF7lVIVgP3zA+mbGDdbmbQ0H92/KuZ6syZDFMCwOnmb7vMbvohw4w+p15s1ZTYBsDJp+W6+f+LYJr6IAHh8etDOpX5KB/BFxIthyd7e+jYA7duGfoPR9FFzpVQlu7CkG9UDtqZSOLbJXBwmdB1EAbieoDA/DaBACitX7BRvWMyu6gFhf5DYwhcRjm1y2T5j8zCme1emcb3ByfHM718YbuHYJh1jkkqpSuvi++Z2yy09oBcERtgfEPYHuJ7gdHlWx6cXBMoF/+P6AsT2Y14/1DbwAAAAAElFTkSuQmCC"},{"stackable":true,"id":"ORBIT","stackable_how_often_reappears":10,"ui_name":"Phasing","max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAw0lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/ZI3xocFw9sLVa1EMghnCgs1ZuxbcZuCzYGCw0FBFMQzZEBhgQubAbOezYGD4dAJV4cLVa+EuQnYlEwMaiA8NZrDQUGVwS1DF6g1kPlYD0A1buHotw4kbtxl2LbiNVQ1eA2A2fjoB8RY2gBIL5uoq/5FtxuUiBgYGhpM37zBiuAAWz9j8iqwZpg7DBeSkA4pT4jAAADrQViLf8KEtAAAAAElFTkSuQmCC"},{"stackable":true,"id":"ANGRY_GHOST","ui_name":"Angry ghost","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAv0lEQVQ4jd2SoQ7CQAyGvxGyJXjExD3AyYk9BQluDrUHQ+HmluwpEMg9wMQEfslmhiBdrscNcCT86tprv/Z6hV8r8h2ZMTNAvEs4H46Lv2xqpmEE4NZ1URCQGTPHuwRAJbsSkEA2oaC15NCdAkh1gGvbLud73yu/GxfsoGxqcmsVRJJza9c78CXB+zRVtqvtO4BU9YFfA/yWPz5hGkY1ZQmWIYot+/ACEPlfJTOA54BdBTfxUhQhLqeqAvQm/oEe/oxFlCyqMnUAAAAASUVORK5CYII="},{"stackable":true,"id":"HUNGRY_GHOST","ui_name":"Hungry Ghost","stackable_maximum":5,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2ElEQVQ4jd2SMQrCQBBFnyIRvELYIjmAYC8WksobCGKRwsoLWFp5AQ9gEQjewYgEe8EDJEXIFQRttFpxsru2gr+b2flv/zILv1ar2Rgo9QTwel1W26FhWMcZl6p6+wRgoNTT63UBWG2HBKFvAMqiFpC2LZbLDBh9AdC366E0yQE4Hq6i1nPOBIvJHoDpbCT6zdoJaGoc9Z1nXwE6+jcZgM/VjaM+aZKTJvkbVha1mO98Fo/bnSD0BcT27sft7k4A5qq0yqJmszyLnvUn7k5zq3kdZwDiJ/6BXt0HQnQC82xUAAAAAElFTkSuQmCC"},{"stackable":true,"id":"DEATH_GHOST","stackable_is_rare":true,"ui_name":"Mournful Spirit","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAArUlEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNi50hJLsWQ8Oy7hqG848fw/WhGGAoK/ufjYudgYGBgSEku5ZBUUkNw4D7926hGMKEzVm4NDMwMGCIoxgAsx2maNGCmSiKYXyYOpwu6C6OZWBgYGCIS0hHEUfn4zSAFIDXAJiT0Wm8BmCLOmRw/94tFD5GNFZPX8Vw/94tnLFAVDTi07xmajNuFyC7ApfNDAwMKClxGAAA4+ZAOxBe0mAAAAAASUVORK5CYII="},{"stackable":true,"id":"HOMUNCULUS","ui_name":"Homunculus","stackable_maximum":10,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA20lEQVQ4jWNgGGjAiC5gKCv7n42LnYGBgYFByjCQ4dn59XC5X99+MjAwMDCcf/wYro8Rl2YGBgYGm4A4BlUVZTh/fk893CCYIUyEnDh//kIUGh2gGIBsOwwkJsaj0OjqCLoAn+0MDAwMLLgkEksaGRgYGBhu37nLYGNrw3D7zl2s6rC6ILGkEa4BFoiPHz0kzoDEkkZ4aCODh+cOMsgb2WOIo3jh17efcM2qKsoYzn547iBcHV4vMDBA/H5kwyKGIxsW4VKCacD5x48ZYaYj+xnG/vXtJ0oiGiYAAJiJVzRddsq+AAAAAElFTkSuQmCC"},{"stackable":false,"id":"LUKKI_MINION","ui_name":"Lukki Minion","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABDklEQVQ4jd2SvUoDQRSFv4joYre9W20Vhi1SWOwTLBZ2slho4wv4CmrhY4hgJ2JjIT7BFgYshuU2SZMMpAxpQpImVjPMZAdbwVvNz7nnfHMZ+Ovq7R4MsmwLcHB02BFvlmsAvqdT1xcYDLJsG2uMGVmT/d+Etxd3pEnOfDUmTXJuni47mj1/46dXqqbRLQBpkjvDXV1AUKnarctC0eiW+WpMo1vKQkUpAwMxQv+4jxgJTOzehny2L/EnTBbaXYqRILksFGIEMRKQRofoUzS65fr0nMePVyYL3dEGBJvlmkrViBEntiT+jEazYZzg7OTKNftGfnKlat6/nuME928PPUviD87WaDZEjAQ/8R/UD9WEfpsxIQoNAAAAAElFTkSuQmCC"},{"stackable":false,"id":"ELECTRICITY","ui_name":"Electricity","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA90lEQVQ4jdWSMWoCQRSGP4O4YTAEISgINnZWwZBttkqRG3iVLfQEa+FVvMaCWIiVBGwWQViVNSFhoiGghTjszlNTBv9mhjfv+94MDPx3cnahWavtAArKEc0/egvAaDYz3I0NF5STgeduy+yPZ8chQmBn7rbo9LoZiZ2MwJ7c6XUJ/LaA0n35S5MBswY+VId90XtSUB32CXyMIPDbJ2HxBDt/wWdvkL7FJVgI6k/uoaiK3D5UeGk8wuszAMlkzGYV86u/ABi8TaUgiiKUUsASJ0lYL2JK5QrrRcz3e8L28wMArbVhxE/0PG93kIBzd2/qaTgMQ8Fdcfa/ZlY5Vr2X0QAAAABJRU5ErkJggg=="},{"stackable":true,"id":"ATTRACT_ITEMS","ui_name":"Attract Gold","stackable_maximum":6,"max_in_perk_pool":1,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAq0lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/bBrd1TMZGBgYGHbenA43CGYIEzHO3HlzOsPOm9PhBiEDFAOw2Y5ukLt6Joo6gi44ceM2Xnm8Bpy4cZuB4c10hn5PNhRX4DUA2Z+3V4Ux3N63n6Fw+y+clrDgc0Fs3Xl80pgu+PXtJ87QRleH1QAYgBlCyCAGBiqkxGEAAJdjP2wZNGBLAAAAAElFTkSuQmCC"},{"stackable":true,"id":"EXTRA_KNOCKBACK","stackable_is_rare":true,"ui_name":"Extra Knockback on Spells","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/UlEQVQ4jd2SsWoCQRCGP4O4IOQRtBAltqbR8uAOrotXWPgAkkbBl0newcLi2pM72CqYKq3BYOMjCMKJoNUu683FNpC/2d1Zvn9mdhb+WpVioNdoXABqdXUTX2+2PDebAHzt95Z7KMK1uhKwkbkzSYRBUfPJkPlkyHqztVXcbaH/1L7oOMSLElG+q0G3w+f3T6W0giK8WLwL2DW8MdBxKODxeMqg27GwuwoDL0rQcUg6O4gWXMhV1T2kswOq5aNefXSQ4UXTUuhXg2owsnvV8tExnNOlyH465uUtnNMl+S4DIN9leFFC8PYIyPcxKv2JH6sXOw13rCaz+xP/ga6wqVa/vT5hFwAAAABJRU5ErkJggg=="},{"stackable":true,"id":"LOWER_SPREAD","stackable_is_rare":true,"ui_name":"Concentrated Spells","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAo0lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/bBoZGBgYTty4xcDAwMBgoaHG8OvbT7ghTIScCNOIC6C4wFxd5T+yRgsNNZwaT968g90FB+Zow51KjCswDHBIuYqhyEJDDachOF2ADPB5B6cLkG3EFxYoBsDiGVkTzKATN27B2cjqCEYjPtsZGKiQEocBAAAoD0HZEMuxUgAAAABJRU5ErkJggg=="},{"stackable":false,"id":"LOW_RECOIL","ui_name":"Low Recoil","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA2klEQVQ4jd2SMQrCMBSGP0Va6B06Cl3t0i4KdfEMgqA4unS1J9DVC5RuXT2Co1nqKjj2DoIuOr1gNLWj4JuSl/xf/iQ//Lo6742B7z8A4mREpZSxdr/eADjVtdZ138WO5+J4LgDpOjMAsiaHfACkwijS4+P58vUKBsDxXC0eJuNGkTj8AIRRRKWUFk8Xy1YXvddJpRTpOmO33bRatzoAtHg1nxn9JqD1EeOgT6UUZZEbPRvEAMg/v7oBKIuc4/lCHPQ57CfGPiNIkgNbCUAOkjA1JtEGsiXxD+oJBAFRE/yVkvwAAAAASUVORK5CYII="},{"stackable":false,"id":"BOUNCE","ui_name":"Bouncing Spells","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAApklEQVQ4jdWROw7CMBBEnxECiTs4FRFpQ0N6H8pn8KHcQ0ULgi53QILGVI78iUOoEFOuZt7O2vBriXTQSukAVpt1Zn49ngCc+37IRYBWSjcWHAN5yPKbusfLDYB9VQ2zRWjw272xpLBldMJht3Xhtq6pi5DT9S6yBlMqtSoCuqYeQlNtZjcoKQL4f05bpNtDX/SIWmvHTBljRAYAUEp9hFhrs9wf6w27zzR4DbHn7AAAAABJRU5ErkJggg=="},{"stackable":false,"id":"FAST_PROJECTILES","ui_name":"Faster Projectiles","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAfUlEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNfHhKwgesX//2xc7BiaVRpvM8AMZeNih1uCYUCDJ0IxMeIYBhBrCLILMQwg1SVYDSDFENwGbGdguFOvSlAcuxeI1IzVAGI0w9IDhgGk2AwDFKfEYQAAGrw/XNQ5nKsAAAAASUVORK5CYII="},{"stackable":true,"id":"ALWAYS_CAST","ui_name":"Always Cast (One-off)","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABBUlEQVQ4jd2Sv2oCQRCHvwtB4aqkuC7XKab1Gq+0MiDcCyR93sAmXCmB4BOYKnmGBSFWKa/SVkh56SysPNBmLPRWZ40QbAL5VTv755vfzA78tTx3oxmGAlDxq2SjDnF3jEkDkv6cdbECYJrn3o+AZhjKx/MNSX9+MmM26hC1jYIoQKtRk1ajJrIcioiILIfydn8lImLPSpcAl4eAil/dZpn0iKMBMNjFL7w/XJPNFsS3dXsP4MK1B4D/qK05sUkDu1YO4u54D5t9nWqD6pFycEj+rRTAkotXfcuJbamuzvmFozmYfCaqF65MGnD39G3nQJUwzXMvahs7cWVPSsvrYqUe/xNtAJSBgP4mjK7+AAAAAElFTkSuQmCC"},{"stackable":true,"id":"EXTRA_MANA","ui_name":"High Mana, Low Capacity (One-off)","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA20lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNfHhK6ZjYudgY2LneFX10kMA2ByMEswDIDb1HWSgc9KCKsh6ADFAJiz+ayEGOawQ2hsANl7WF3w6dg7hpSfEJokF8BtKDNn+HTsHQNbmTmDassh0g2AGcLAwMBwu8YObsiJG7cIG6DacoiBJXkRwxMlDzh9//4DhqLdlxlWPf7JULT7Mop6lHQAi0Zsht6usWP4O/kEA3OuBcOvbz/haQGnF9A1MzAwMDDnWmDIU5wShwEAADBJTDyHiQZmAAAAAElFTkSuQmCC"},{"stackable":false,"id":"NO_MORE_SHUFFLE","ui_name":"No More Shuffle","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAAAxklEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlQU7iY2LHUPxr28/wfT5x48ZYS4HCCAUA0CasWmEAZgc1BJGEBsggPB64cSN23AM46MDgABCcQGy7SDFFhqqKHxs6gACCKsL8GlGBwABxIJNEFkzNj4yAAggnC5A9js+lwAEEN5ARPcGNpcABBCKF0DxDAsgkGJkG5E1w9IDCAAEEN4wwOd3GAAIIEbkvEBOSgQIIEZKMxNAAFGcmQACiGIDAAIMALbpTicFaWinAAAAAElFTkSuQmCC"},{"stackable":false,"id":"NO_MORE_KNOCKBACK","ui_name":"No More Knockback","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA/klEQVQ4jWNgGGjAiC5gKCv7n4GBgaHAnYFh2mF2FLlf334yMDAwMJx//BiujwldMxsXO0NtkTaGZgYGBgY2LnYGNi52uCUYBqCDEzduE/QCigFsXOwM5RkqDJ0z7uDVxMaFcB2KAciaYbb39lTiNYwZmfPgAUMDsmYGBgYGOTVDBu4fjxhOnruJovHp23eNGC6AabbQUGV4cGEXXGzinI0M+Sn+WF2AYYCFhioDAwMDQ0RENsRVUIMmztnIwMDAwJBl+xO3AbB4RgYREdkMK1ZMhfN5tbVR1OGNRphrYABbDKEYcP7xY8Zf335iuATmnX9//jH8+vYTJSUOAwAA9GxWzC07jWUAAAAASUVORK5CYII="},{"stackable":true,"id":"DUPLICATE_PROJECTILE","ui_name":"Projectile duplication","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAk0lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNfHgq4Zm0YY2HXuHAMDAwODm5HRf5ghTOQ4GxmgGIDL9i1HjjMwMDAw+NhYMvjYWKKoI+iCLUeOM/jYWOKUZ0EXgNkGA/g0YzUAXQMhFxD0go+NJYarkAFKOiAUjTCD3IyMGKgWjRSnxGEAAMh+LeAB1tZsAAAAAElFTkSuQmCC"},{"stackable":true,"id":"FASTER_WANDS","ui_name":"Faster Wands (One-off)","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA3ElEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/bBqxGQQzhAmXov+67xgad6QynLhxm+HEjdtwPl4vmKur/IdpPrn6LYN5qDAD42UhOJ+BgYHBQkOVgYGBgeHkzTu4XdDUXQ7XDOPjAlhdcOLGbbhNuABeF+x80MXwX/cditgnkfcMITM1MdRiNaCutJPh5Oq3cEM+ibxnSGq1YphXfQxDLQsy59e3nwxsXOwMjJeFGMxDhVH8XhjTxcD3RhCuDgYoTgcUp8RhAADVSWCOGkJJEQAAAABJRU5ErkJggg=="},{"stackable":true,"id":"EXTRA_SLOTS","ui_name":"Extra Wand Capacity (One-off)","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA8klEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/Ni52BgluTgZpPm6Gp5++YtAvvn5n+PXtJ9wQJmzOkubjZjj7/A1WGh2gGABz9tNPXxmMJUWw0ujeY8HmghdfvzO8+PqdgYGBgYG3cifDixo7OB+vC7CB2zV2DKothxgYGBgYTty4RdgA1ZZDDCzJixieKHnA6fv3HzAU7b7MsOrxT4ai3ZdR1KPEgrm6yn9srlBtOcRwu8aO4e/kEwzMuRYMDAwMDCdv3sGMBVg8Y9PMwMAA14ysjqgwwAcoTonDAAAA1vdrZrczY3MAAAAASUVORK5CYII="},{"stackable":false,"id":"CONTACT_DAMAGE","ui_name":"Contact Damage","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABDklEQVQ4jWNgGGjAiC6Q51P2vy6oE85vWlfOcPL2OgYGBgaGX99+MjAwMDCcf/wYrg/FgDfz/v8/fvcQw5Sd3XCxHPdSBktlOwbvTlW42K9vP+GGoBjgbuLzH8YOsfaDi685uolhadZmuCHIBjBh8xdMc8+mboY1RzcxMDAwMERP82XYWn6bgYGBgYGNix2uFqsBMM3oANkQvAY8eXoHl7kMx+8eYjBXDYLzsYaBhYIWXOzEg2sYhizN2swgksSIPQxCrP0YZKRVcLoAHeAMA2INwWmAmc5ihiD34wxC/Bwo4jnupQxN68pxG7Dm6CYGr+z/DKeuxDKs22nJ0FQrjCJvqWzHMGlLF0YKHsIAAA/cVd4ZQxsRAAAAAElFTkSuQmCC"},{"stackable":true,"id":"EXTRA_PERK","ui_name":"Extra Perk","stackable_maximum":5,"max_in_perk_pool":3,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA0UlEQVQ4jd2SMQuCUBSFvyKUFJ0EEXFqcVSc+hf+1P5FQ0iNLbWIiOCkKOhi0xOeGhQFQWe6nHfeuee+d+HXWE2J0PMGAEVTZ+K+7QA4p+l4TzIIPW9QNJXIsciqZmbgmjpJXtK33WiymYoix+JwuWIbBiKJ6Hy6p8SBz/GWjXrJQNFUsqrBNgxpBFHbQFY10tl6lvNNLBosPeAzftFAzPwK/90R+rbDNXWKuqZvu7GjqIu6xjV1KcnsG5O8JA58eQ/0LQD7nUuSl5L+4038AzwAn3BTt6LtDvgAAAAASUVORK5CYII="},{"stackable":true,"id":"PERKS_LOTTERY","stackable_is_rare":true,"ui_name":"Perk Lottery","stackable_maximum":6,"max_in_perk_pool":3,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA6klEQVQ4jd2SPwtBURTAfyTKbnyD6SWTwVuUL2BQFtPzPaQkJZ9DmZVexlcSBgaTdFdvfKsUC4Pu7d13PYtBOdP9c36/c04d+HWk4g8Vy3oAZPM5I/l+vQFwCALFaYKKZT3ege9EUpKJfkRh1/M1SOx37IZdIy8dt7uer8GzQQ8Au+rgej5Of6zlax3IShIAaA1G6m89nRjjGAKA42KuznbVUaDYrLBrdS3XGAGg3GhqHYnN6iWLwYmCeEhQihIF9+uNUJzUXY4S7SAMzmof4MMeFNsdCnYJgFCcCIMzl+1SFZJ78PUm/kE8AUaWW+WmEsALAAAAAElFTkSuQmCC"},{"stackable":true,"id":"GAMBLE","ui_name":"Gamble (One-off)","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABHklEQVQ4y2P8//8/AyWAiYFCwILMYWRkZDCUlf3PwMDAwMbFjqH417efDAwMDAznHz9mhLkcxQBDWdn/C9ZORdEkLi7C4O8Wx4BsKNQSRgwvpAuxMehVlDNw37vHoFdRzqBXUc7w8uUbho27FuH0AiNKIDproYborpMMDL9fYGiyMPBiOHHjNiP+QETSbOMUQVws3E1LR3DQNOMyBMWAr0pKKIaga8JmCIYXviopMVzq6EQR09JQIc4LG1ZuRZGc3F/HEBzgznDtxh2G4AB3hsn9dSjpAdOAVdtQDDDU12JYu2EnAwMDA8PaDTsZDPW1MJ3w//9/OL54ahODoazs/8bi9P//v9/CwDO6qv8bysr+///9FlwP44BnJooNAABeKH/gGEG1cwAAAABJRU5ErkJggg=="},{"stackable":true,"id":"EXTRA_SHOP_ITEM","ui_name":"Extra Item In Holy Mountain","stackable_maximum":5,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA0klEQVQ4jd1SKw7CQBQcCNkNe4e6TdYWzDY47lCJ6xU4AAeoxuG4QQ9AMKSq9A5NcNgSQDwMLLx2W4MgYcxkP2/evMkDfo1B82ISBAQAQsnW51t9BQAcq8rVDZvFQkkUZQIAyA8LJiCUhFDSNWkJAECehsB4zrkHTKAoE0TLEgAY+5x0OvAhmm25wy5Yo4mymKjeM7ZGk3u7n4iymDoFrNFE9Z7zecVErNH+EJuzesd5ZvPC6PMwDTco1pbNmqchywB470PLgetw2XHuwdeb+Ad4APwqcXS5KwlqAAAAAElFTkSuQmCC"},{"stackable":true,"id":"GENOME_MORE_HATRED","ui_name":"More Hatred","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA8ElEQVQ4jd2SsYrCQBRFz4Ks4BeIMFZCGkFEZAN+yoI/kCbodyjb2KYI+BdpAy4SFmGbgJUDsl8gaBMLM+HNTlJZLOyt3rw378693IG/xsvvxlipAuC103Yu3y5XAL60rvYsgrFShVxc9Sb01YiTPrA8ZxaRIWkk2M03jgI/DhyClrwgl9MkAqgUmL4fB5Y9i8DIBvj4+QYgFPWsnEs7loU3b1Ds5ht0nqG8B1Fd7ccBn/nRtQCQJhF9NSJNoupl9lvC7rDqSzgEy3PGqpS9nr6jvAk6z1jst4TlXKIxBROhgYyyMYU6JfJch6d/4j/AHbC0c0/ft3rQAAAAAElFTkSuQmCC"},{"stackable":true,"id":"GENOME_MORE_LOVE","ui_name":"More Love","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABCklEQVQ4jd2SLW/CUBSGHxBtUktAwQQjmUGAWkOyjQTHX0AswSFnNoVCgUHOkSD6F3BLBktIUSAwJNvEqGqDJdkMU+emt00JjoRj7r3nnPe57/2Ac0cqmqgWCgcAwzJjzX/7XwCW263SpaNiwzKVuNMYMO7O6DQGCNSwTLVJzIEAAMbdWczBY+9eOREXmoOoeOj08b2AodPX8uHjaQCxDeB7gZaXtdQTAXbNVs1PrRdt9L0Au2YfB7hzFwBnOlJzd+7iTEdaXUK7xNub0kFsFq+ulQig9dDm++eL17dnABabz1QMEH4FgeTyWXwv0MThV0gEANxVmpQzdda7dz5WE5VPBAgETv+JFxD/uVVyOKUGjgYAAAAASUVORK5CYII="},{"stackable":false,"id":"PEACE_WITH_GODS","ui_name":"Peace with Gods","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6JQAAgIMAAPn/AACA6AAAdTAAAOpgAAA6lwAAF2+XqZnUAAABI0lEQVR4nGL8//8/AyUAIICYKNINBAABxILMYWRkZDCUlf3PxsWOU8Ovbz8Zzj9+zAhzOUAAobiAkGYQAMmD1MH4AAGE4YVyHmEUmhAACCBG5EBcb2KBEaKdX95i1Xjixm1GEA0QQEz4FIP4BQ3NDMdv3AJjEBsdAAQQA8gFMGyurvIfhNcZm4Pp5cuX/0cG/4AQJAaSg+kBCCAUL1hoqKJ44cWP3wz3H9xnUFRQBPNBbBCw1FCDewEggPCmA5hmmEYQWLliJYoagABCcYGRnBxKNIL8jQ5AtoMAzAUAAYTXBSDbUvTN4bSRoRFDiYEdODHBAEAAobgAlhJBbJhLQBq2X78MZrtWFjF0lZWhpESAAGKkNDMBBBDFmQkggCg2ACDAACeNk0X3s8cKAAAAAElFTkSuQmCC"},{"stackable":false,"id":"MANA_FROM_KILLS","ui_name":"Kills to mana","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAo0lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNfHhK6ZjYsdRfOvrpNwNkwOZgmGARg2dp1k4LMSQjEEHaAYgG4zn5UQwxx2BgxDkNXhdAFMMwMDA9wQgi5ABnPY8fMJGpDyEz+foAGfjr2Da0r5CeETNAAWzwwMDAxsZeZwQz4de8fAVmaOVR3eaIQZgqwZHVCcEocBAAC5MD6SdDm9HwAAAABJRU5ErkJggg=="},{"stackable":false,"id":"ANGRY_LEVITATION","ui_name":"Rage-fueled Levitation","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA9ElEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNi52BgYGBIcWjjIGBgYHhydM7DDLSKgzT1jUznH/8GK6PCV0zGxc7XDMDAwODmc5iFAvYuNjhljAwMDCw4HNeikcZw6krEEPMdBgY0nrWYahBcQGyzchg3U5LhlNXYrGqY8KmAQbm7OjCJ83AwIDDC20bDjJoyksxXH/4jKEqwB4ieBm7ARguWH/+NoOmvBQDAwMDg6a8FMP687fxugDDgOsPnzEEGqqi0PgASjowV1f5D3MFDAQaqmJo+vXtJzwtMKFLoLuCEEBxwf///xmM5OT+44pOmAXIKXEYAAAB+Uvh5gzTIQAAAABJRU5ErkJggg=="},{"stackable":true,"id":"LASER_AIM","stackable_is_rare":true,"ui_name":"Pinpointer","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAyUlEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/bBqxGQQzhIlEF2MAFAOIsR1dHYoBqi2HGBgYGBhO3LjFwMDAwNAmIk7QMJQwsO469j8vwhDO73PVZWgTEWe4+f07gzonJ8O+798ZpBkYGJ4yMDC0njvHyMDAwMCCbICiogJDn6suw9/JJxiYcy0Y2kTEGarevIRIfv1E2AXm6ir/CboZCk7evIMZC7B4JgSQ1VEcjRSnxGEAALHaOvOrh1XEAAAAAElFTkSuQmCC"},{"stackable":true,"id":"PERSONAL_LASER","stackable_is_rare":true,"ui_name":"Personal Plasma Beam","stackable_maximum":5,"max_in_perk_pool":2,"ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABA0lEQVQ4jWNgGGjAiC5gKCv7n4GBgYGNix1D8a9vPxkYGBgYzj9+DNeHYoChrOx/mMYUjzK4+JwdXRgGwQxhweYsmOaeTd0MQu58DEyO/CjyHAwMDAwzHjNgGIDs7J5N3Qzx9wIZGBgYGLIYKlEMmMbQznB8xllMA2DgydM7DAwMDAwLldYzCLnzMWxm2ItNGW4DGBgYGCKMIbbv2L8Op2YGBgYGJnSBFI8yBhlpFbya8BoAA8QaguIFWDwzMDAwmOksZjDTYWC49d6A4d6TCwy41GG4YM6OLgav7P8Mp67EMqzbacnQVCuM1wUYKXF2/qz/MPaTp3cYdlxGBCK2lDgMAAAIp0mpgC1gSgAAAABJRU5ErkJggg=="},{"stackable":true,"id":"MEGA_BEAM_STONE","ui_name":"Summon Sädekivi (One-off)","ui_icon":"iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA6klEQVQ4jWNgGHTAUFb2v6Gs7H+bOOP/B/5v/P//////B/5v/G8TZ/wfJoesngldMxsXOwMbFztWw2FyyIYwYVWJB3yw/orCZ0G3gRAQOMrNwMCF4KO4QHmeLAMDAwND541ehtcsz7AaAFOD1QV3kx4zMDAwMJRrFDPMvDEDq2aYGqwuQLbh6IN9KOKejTYMd5MeY7gAw4C7SY8Z/HZ4MyxqXoUibq3gRNgFfju84TbMnIvphTSrAhQ1DAxoYXD12SUUmoGBgYFBAeEdFHEoYEQXUE+SRElpxskaDGfn3kBRc3Pecwx9QxgAADj2UM3yIinTAAAAAElFTkSuQmCC"}] --------------------------------------------------------------------------------