├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── README.md ├── about.html ├── config.html ├── eng.traineddata ├── gear.html ├── index.html ├── main.js ├── map.html ├── messages.html ├── modules ├── ClientTxtWatcher.js ├── Constants.js ├── DB.js ├── FilterParser.js ├── GearChecker.js ├── InventoryGetter.js ├── ItemCategoryParser.js ├── ItemData.js ├── ItemFilter.js ├── ItemParser.js ├── ItemPricer.js ├── KillTracker.js ├── Log.js ├── MapRun.js ├── MapSearcher.js ├── OCRWatcher.js ├── RateGetterV2.js ├── RunParser.js ├── ScreenshotWatcher.js ├── SkillTreeWatcher.js ├── StashGetter.js ├── StatsGetter.js ├── StringMatcher.js ├── Utils.js ├── XPTracker.js ├── electron-capture │ └── src │ │ ├── main.js │ │ └── preload.js └── settings.js ├── overlay.html ├── package-lock.json ├── package.json ├── res ├── Fontin-Regular.ttf ├── Fontin-SmallCaps.ttf ├── Fontin.fnt ├── Fontin_0.tga ├── data │ ├── atlasRegions.json │ ├── constants.json │ ├── dialogue.json │ ├── itemCategories.json │ ├── mapMods.json │ └── uniqueIcons.json ├── debug.js ├── img │ ├── !.png │ ├── AwakenerOrb.png │ ├── CrusaderOrb.png │ ├── HunterOrb.png │ ├── RedeemerOrb.png │ ├── WarlordOrb.png │ ├── atlasicons │ │ ├── GlennachCairns.png │ │ ├── HaewarkHamlet.png │ │ ├── LexEjoris.png │ │ ├── LexProxima.png │ │ ├── LiraArthain.png │ │ ├── NewVastir.png │ │ ├── TirnsEnd.png │ │ └── ValdosRest.png │ ├── blank.png │ ├── c.png │ ├── crusader.png │ ├── delete.png │ ├── discord.png │ ├── elder.png │ ├── encountericons │ │ ├── abnormaldisconnect.png │ │ ├── abyss.png │ │ ├── alluringabyss.png │ │ ├── altered.png │ │ ├── alva.png │ │ ├── argus.png │ │ ├── augmented.png │ │ ├── blight.png │ │ ├── blightedmap.png │ │ ├── brain.png │ │ ├── cassia.png │ │ ├── chimera.png │ │ ├── constrictor.png │ │ ├── cortex.png │ │ ├── crusader.png │ │ ├── darkshrine.png │ │ ├── deaths.png │ │ ├── delirium.png │ │ ├── einhar.png │ │ ├── enslaver.png │ │ ├── envoy.png │ │ ├── eradicator.png │ │ ├── eternal.png │ │ ├── eye.png │ │ ├── grandheist.png │ │ ├── heart.png │ │ ├── heist.png │ │ ├── hunter.png │ │ ├── hydra.png │ │ ├── jun.png │ │ ├── karui.png │ │ ├── kd.png │ │ ├── kills.png │ │ ├── labtrial.png │ │ ├── labyrinth.png │ │ ├── legion.png │ │ ├── liver.png │ │ ├── lung.png │ │ ├── maraketh.png │ │ ├── mastermind.png │ │ ├── maven.png │ │ ├── mavenarena.png │ │ ├── mavencrucible.png │ │ ├── metamorph.png │ │ ├── minotaur.png │ │ ├── niko.png │ │ ├── oshabi.png │ │ ├── phoenix.png │ │ ├── purifier.png │ │ ├── redeemer.png │ │ ├── rewritten.png │ │ ├── shaper.png │ │ ├── shrine.png │ │ ├── simulacrum.png │ │ ├── sirus.png │ │ ├── templar.png │ │ ├── twisted.png │ │ ├── ultimatum.png │ │ ├── vaal.png │ │ ├── vaalsidearea.png │ │ ├── warlord.png │ │ ├── words.png │ │ └── zana.png │ ├── ex.png │ ├── fractured.png │ ├── geartypeicons │ │ ├── amulet.png │ │ ├── belt.png │ │ ├── bodyarmour.png │ │ ├── boots.png │ │ ├── flask.png │ │ ├── gloves.png │ │ ├── helm.png │ │ ├── jewel.png │ │ ├── ring.png │ │ └── weapon.png │ ├── github.png │ ├── howa.png │ ├── hunter.png │ ├── icons │ │ ├── mac │ │ │ └── ExileDiary.icns │ │ ├── png │ │ │ ├── 128x128.png │ │ │ ├── 16x16.png │ │ │ ├── 24x24.png │ │ │ ├── 256x256.png │ │ │ ├── 32x32.png │ │ │ ├── 48x48.png │ │ │ ├── 512x512.png │ │ │ ├── 64x64.png │ │ │ └── 96x96.png │ │ └── win │ │ │ └── ExileDiary.ico │ ├── incursion.png │ ├── itemicons │ │ ├── ElderBackground1x1.png │ │ ├── ElderBackground1x3.png │ │ ├── ElderBackground1x4.png │ │ ├── ElderBackground2x1.png │ │ ├── ElderBackground2x2.png │ │ ├── ElderBackground2x3.png │ │ ├── ElderBackground2x4.png │ │ ├── ShaperBackground1x1.png │ │ ├── ShaperBackground1x2.png │ │ ├── ShaperBackground1x3.png │ │ ├── ShaperBackground1x4.png │ │ ├── ShaperBackground2x1.png │ │ ├── ShaperBackground2x2.png │ │ ├── ShaperBackground2x3.png │ │ ├── ShaperBackground2x4.png │ │ ├── crusader-symbol.png │ │ ├── divination-card-divider.png │ │ ├── divination-card.png │ │ ├── elder-symbol.png │ │ ├── experience-bar-fill.png │ │ ├── experience-bar.png │ │ ├── fractured-symbol.png │ │ ├── glyph.png │ │ ├── header-currency-left.png │ │ ├── header-currency-middle.png │ │ ├── header-currency-right.png │ │ ├── header-double-rare-left.png │ │ ├── header-double-rare-middle.png │ │ ├── header-double-rare-right.png │ │ ├── header-double-relic-left.png │ │ ├── header-double-relic-middle.png │ │ ├── header-double-relic-right.png │ │ ├── header-double-unique-left.png │ │ ├── header-double-unique-middle.png │ │ ├── header-double-unique-right.png │ │ ├── header-gem-left.png │ │ ├── header-gem-middle.png │ │ ├── header-gem-right.png │ │ ├── header-magic-left.png │ │ ├── header-magic-middle.png │ │ ├── header-magic-right.png │ │ ├── header-normal-left.png │ │ ├── header-normal-middle.png │ │ ├── header-normal-right.png │ │ ├── header-prophecy-left.png │ │ ├── header-prophecy-middle.png │ │ ├── header-prophecy-right.png │ │ ├── header-quest-left.png │ │ ├── header-quest-middle.png │ │ ├── header-quest-right.png │ │ ├── header-rare-left.png │ │ ├── header-rare-middle.png │ │ ├── header-rare-right.png │ │ ├── header-relic-left.png │ │ ├── header-relic-middle.png │ │ ├── header-relic-right.png │ │ ├── header-unique-left.png │ │ ├── header-unique-middle.png │ │ ├── header-unique-right.png │ │ ├── hunter-symbol.png │ │ ├── hybrid-title.png │ │ ├── redeemer-symbol.png │ │ ├── seperator-currency.png │ │ ├── seperator-gem.png │ │ ├── seperator-magic.png │ │ ├── seperator-normal.png │ │ ├── seperator-prophecy.png │ │ ├── seperator-quest.png │ │ ├── seperator-rare.png │ │ ├── seperator-relic.png │ │ ├── seperator-unique.png │ │ ├── shaper-symbol.png │ │ ├── socket.png │ │ ├── synthetic-symbol.png │ │ ├── vaal-title.png │ │ ├── veiled-symbol.png │ │ ├── veiled │ │ │ ├── prefix_01.png │ │ │ ├── prefix_02.png │ │ │ ├── prefix_03.png │ │ │ ├── prefix_04.png │ │ │ ├── prefix_05.png │ │ │ ├── prefix_06.png │ │ │ ├── suffix_01.png │ │ │ ├── suffix_02.png │ │ │ ├── suffix_03.png │ │ │ ├── suffix_04.png │ │ │ ├── suffix_05.png │ │ │ └── suffix_06.png │ │ └── warlord-symbol.png │ ├── itemtypeicons │ │ ├── catalyst.png │ │ ├── currency.png │ │ ├── delve.png │ │ ├── divcard.png │ │ ├── essence.png │ │ ├── fragment.png │ │ ├── gem.png │ │ ├── incubator.png │ │ ├── map.png │ │ ├── nonunique.png │ │ ├── oil.png │ │ ├── prophecy.png │ │ └── unique.png │ ├── kofi.png │ ├── loading.gif │ ├── loadingcomplete.png │ ├── mirror.png │ ├── novalue.png │ ├── patreon.png │ ├── q.png │ ├── redBeast.png │ ├── redeemer.png │ ├── shaper.png │ ├── shrineicons │ │ ├── Acceleration.png │ │ ├── Brutal.png │ │ ├── Diamond.png │ │ ├── Divine.png │ │ ├── Echoing.png │ │ ├── Freezing.png │ │ ├── Gloom.png │ │ ├── Impenetrable.png │ │ ├── Lightning.png │ │ ├── Massive.png │ │ ├── Replenishing.png │ │ ├── Resistance.png │ │ ├── Resonating.png │ │ ├── Shrouded.png │ │ └── Skeletal.png │ ├── stash.png │ ├── sulphite.png │ ├── synthesised.png │ ├── tabicons │ │ ├── BlightStash.png │ │ ├── CurrencyStash.png │ │ ├── DeliriumStash.png │ │ ├── DelveStash.png │ │ ├── DivinationCardStash.png │ │ ├── EssenceStash.png │ │ ├── FragmentStash.png │ │ ├── MapStash.png │ │ ├── MetamorphStash.png │ │ ├── NormalStash.png │ │ ├── PremiumStash.png │ │ ├── QuadStash.png │ │ └── UniqueStash.png │ ├── ultimatumicons │ │ ├── AilmentandCurseReflection.png │ │ ├── AilmentandCurseReflectionHinderingFlasks.png │ │ ├── BlisteringCold.png │ │ ├── BonusChaosDamage.png │ │ ├── BuffsExpireFaster.png │ │ ├── ChokingMiasma.png │ │ ├── EscalatingDamageTaken.png │ │ ├── EscalatingMonsterSpeed.png │ │ ├── HinderingFlasks.png │ │ ├── LessCooldownrecovery.png │ │ ├── LessenedReach.png │ │ ├── LightningDamageFromManaCosts.png │ │ ├── LimitedArena.png │ │ ├── LimitedFlasks.png │ │ ├── OccasionalImpotence.png │ │ ├── RagingDead.png │ │ ├── RandomProjectiles.png │ │ ├── RazorDance.png │ │ ├── ReducedRecovery.png │ │ ├── RestlessGround.png │ │ ├── Ruin.png │ │ ├── SiphonedCharges.png │ │ ├── StalkingRuin.png │ │ ├── StormcallerRunes.png │ │ ├── TotemofCostlyMight.png │ │ ├── TotemofCostlyPotency.png │ │ ├── TreacherousAuras.png │ │ └── UnluckyCriticals.png │ ├── unknownBeast.png │ ├── veiled.png │ ├── warlord.png │ ├── weaponswap.png │ └── yellowBeast.png ├── itempopup.css ├── itempopup.js ├── jquery-3.3.1.min.js ├── jquery-ui.js ├── jquery-ui.min.css ├── jquery-ui.min.js ├── jquery-ui.structure.min.css ├── jquery-ui.theme.min.css ├── jquery.lazy.js ├── jquery.tablesorter.js ├── jquery.tablesorter.widgets.js ├── maptable.js ├── page-utils.js ├── poedit.css ├── style.css └── utils.js ├── search.html ├── sidenav.html ├── stash.html └── stats.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: briansd9 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | nbproject/ 3 | *.bak 4 | test.png 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | 11 | # Runtime data 12 | pids 13 | *.pid 14 | *.seed 15 | *.pid.lock 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # Bower dependency directory (https://bower.io/) 30 | bower_components 31 | 32 | # node-waf configuration 33 | .lock-wscript 34 | 35 | # Compiled binary addons (http://nodejs.org/api/addons.html) 36 | build/Release 37 | 38 | # Dependency directories 39 | node_modules/ 40 | jspm_packages/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | 61 | # ========================= 62 | # Operating System Files 63 | # ========================= 64 | 65 | # Windows 66 | # ========================= 67 | 68 | # Windows thumbnail cache files 69 | Thumbs.db 70 | ehthumbs.db 71 | ehthumbs_vista.db 72 | 73 | # Folder config file 74 | Desktop.ini 75 | 76 | # Recycle Bin used on file shares 77 | $RECYCLE.BIN/ 78 | 79 | # Windows Installer files 80 | *.cab 81 | *.msi 82 | *.msm 83 | *.msp 84 | 85 | # Windows shortcuts 86 | *.lnk 87 | dev-app-update.yml 88 | modules/Dev.js 89 | NEW-LEAGUE-TODO.txt 90 | TODO.txt 91 | /temp/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # exile-diary 2 | A game tracker for Path of Exile. Track literally everything that drops in your maps, and more! 3 | 4 | Download here: https://github.com/briansd9/exile-diary/releases 5 | 6 | Get started at https://github.com/briansd9/exile-diary/wiki/Configuration 7 | -------------------------------------------------------------------------------- /eng.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/eng.traineddata -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 34 | 35 | 203 | 204 | 205 |
206 | 207 | 213 | 214 |
215 | Customize the columns displayed in this table. Drag to change order, or drag to lower section to disable 216 |
217 |
218 |
219 |
Apply
220 |
Cancel
221 |
222 | 223 |
224 |
225 |
226 | Most Recent Maps 227 | 233 | 234 |
235 | 236 | 237 | 238 | 239 | 240 |
241 |
242 |
243 | 244 |
245 |
246 | 247 | -------------------------------------------------------------------------------- /messages.html: -------------------------------------------------------------------------------- 1 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /modules/Constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('../res/data/constants.json'), 3 | ...require('../res/data/dialogue.json'), 4 | ...require('../res/data/mapMods.json'), 5 | ...require('../res/data/uniqueIcons.json'), 6 | ...require('../res/data/atlasRegions.json'), 7 | }; 8 | -------------------------------------------------------------------------------- /modules/DB.js: -------------------------------------------------------------------------------- 1 | const sqlite3 = require('sqlite3'); 2 | const path = require('path'); 3 | const logger = require("./Log").getLogger(__filename); 4 | const Utils = require("./Utils"); 5 | 6 | class DB { 7 | 8 | static getDB(char) { 9 | if(!char) { 10 | var settings = require('./settings').get(); 11 | if(!settings) { 12 | logger.info("No settings file found, can't get DB"); 13 | return null; 14 | } 15 | if (!settings.activeProfile || !settings.activeProfile.characterName) { 16 | logger.info("No active profile selected, can't get DB"); 17 | return null; 18 | } 19 | char = settings.activeProfile.characterName; 20 | } 21 | var app = require('electron').app || require('electron').remote.app; 22 | var db = new sqlite3.cached.Database(path.join(app.getPath("userData"), `${char}.db`)); 23 | return db; 24 | } 25 | 26 | static getLeagueDB(league) { 27 | if(!league) { 28 | var settings = require('./settings').get(); 29 | if(!settings || !settings.activeProfile || !settings.activeProfile.league) { 30 | logger.info("Unable to get league DB"); 31 | return null; 32 | } else { 33 | league = settings.activeProfile.league; 34 | } 35 | } 36 | var app = require('electron').app || require('electron').remote.app; 37 | var db = new sqlite3.cached.Database(path.join(app.getPath("userData"), `${league}.leaguedb`)); 38 | return db; 39 | } 40 | 41 | static async initDB(char) { 42 | if(!char) { 43 | var settings = require('./settings').get(); 44 | if(!settings) { 45 | logger.info("No settings file found, can't initialize DB"); 46 | return null; 47 | } 48 | if (!settings.activeProfile || !settings.activeProfile.characterName) { 49 | logger.info("No active profile selected, can't initialize DB"); 50 | return null; 51 | } 52 | char = settings.activeProfile.characterName; 53 | } 54 | var app = require('electron').app || require('electron').remote.app; 55 | var db = new sqlite3.cached.Database(path.join(app.getPath("userData"), `${char}.db`)); 56 | await this.init(db, initSQL, maintSQL); 57 | logger.info(`Completed initializing db for ${char}`); 58 | // allow time for DB file changes to be written 59 | await Utils.sleep(500); 60 | return db; 61 | } 62 | 63 | static async initLeagueDB(league, char) { 64 | var settings = require('./settings').get(); 65 | if(!league) { 66 | if(!settings || !settings.activeProfile || !settings.activeProfile.league) { 67 | logger.info("Unable to get league DB"); 68 | return null; 69 | } else { 70 | league = settings.activeProfile.league; 71 | } 72 | } 73 | var app = require('electron').app || require('electron').remote.app; 74 | var db = new sqlite3.cached.Database(path.join(app.getPath("userData"), `${league}.leaguedb`)); 75 | await this.init(db, leagueInitSQL); 76 | await Utils.sleep(250); 77 | 78 | if(!char) { 79 | await this.addCharacter(db, settings.activeProfile.characterName); 80 | await Utils.sleep(250); 81 | } 82 | 83 | return db; 84 | } 85 | 86 | static async addCharacter(db, char) { 87 | return new Promise( (resolve, reject) => { 88 | db.run(" insert into characters values (?) ", [char], (err) => { 89 | if(err) { 90 | if(!err.message.includes("UNIQUE constraint failed")) { 91 | logger.info(`Error adding character ${char} to league db: ${err.message}`); 92 | } 93 | } else { 94 | logger.info(`Character ${char} added to league db`); 95 | } 96 | resolve(1); 97 | }); 98 | }); 99 | } 100 | 101 | 102 | static async init(db, sqlList, maintSqlList) { 103 | logger.info("Initializing " + path.basename(db.filename)); 104 | return new Promise((resolve, reject) => { 105 | db.get("pragma user_version", (err, row) => { 106 | if(err) { 107 | logger.info("Error reading database version: " + err); 108 | reject(err); 109 | } else { 110 | let ver = row.user_version; 111 | logger.info(`Database version is ${ver}`); 112 | db.serialize(() => { 113 | for(let i = 0; i < sqlList.length; i++) { 114 | if(ver === 0 || i > ver) { 115 | logger.info(`Running initialization SQL for version ${i}`); 116 | for(let j = 0; j < sqlList[i].length; j++) { 117 | let sql = sqlList[i][j]; 118 | logger.info(sql); 119 | db.run(sql, (err) => { 120 | if(err) { 121 | if(!err.toString().includes("duplicate column name")) { 122 | logger.info(`Error initializing DB: ${err}`); 123 | reject(err); 124 | } 125 | } 126 | }); 127 | } 128 | } 129 | } 130 | if(maintSqlList) { 131 | for(let i = 0; i < maintSqlList.length; i++) { 132 | db.run(maintSqlList[i], (err) => { 133 | if(err) { 134 | logger.info(`Error running DB maintenance: ${err}`); 135 | reject(err); 136 | } 137 | }); 138 | } 139 | logger.info("DB maintenance complete"); 140 | } 141 | resolve(); 142 | }); 143 | } 144 | }); 145 | }); 146 | } 147 | 148 | } 149 | 150 | const initSQL = [ 151 | 152 | // version 0 - db initialize 153 | [ 154 | ` 155 | create table if not exists areainfo ( 156 | id text primary key not null, 157 | name text not null, 158 | level number, 159 | depth number 160 | ) 161 | `, 162 | ` 163 | create table if not exists mapmods ( 164 | area_id text not null, 165 | id text not null, 166 | mod text not null, 167 | primary key (area_id, id) 168 | ) 169 | `, 170 | ` 171 | create table if not exists events ( 172 | id text not null, 173 | event_type text not null, 174 | event_text text, 175 | server text, 176 | primary key (id, event_type, event_text) 177 | ) 178 | `, 179 | ` 180 | create table if not exists items ( 181 | event_id text not null, 182 | id text not null, 183 | icon text not null, 184 | name text, 185 | rarity text not null, 186 | category text not null, 187 | identified number not null, 188 | typeline text not null, 189 | sockets text, 190 | stacksize number, 191 | rawdata text, 192 | primary key (event_id, id) 193 | ) 194 | `, 195 | ` 196 | create table if not exists lastinv ( 197 | timestamp text not null, 198 | inventory text not null 199 | ) 200 | `, 201 | ` 202 | create table if not exists xp ( 203 | timestamp text primary key not null, 204 | xp number not null 205 | ) 206 | `, 207 | ` 208 | create table if not exists mapruns ( 209 | id text primary key not null, 210 | firstevent text unique not null, 211 | lastevent text unique not null, 212 | iiq number, 213 | iir number, 214 | packsize number, 215 | gained number, 216 | xp number 217 | ) 218 | `, 219 | ` 220 | create table if not exists filters ( 221 | timestamp text primary key not null, 222 | text text 223 | ) 224 | `, 225 | ` 226 | create table if not exists leagues ( 227 | timestamp text not null, 228 | league text primary key not null 229 | ) 230 | `, 231 | ` 232 | create table if not exists incubators ( 233 | timestamp text primary key not null, 234 | data text not null 235 | ) 236 | `, 237 | `alter table items add value number` 238 | ], 239 | 240 | // version 1 - testing db versioning 241 | // every addition to initSQL must increment user_version 242 | [ 243 | `pragma user_version = 1` 244 | ], 245 | 246 | // version 2 - add runinfo 247 | [ 248 | `pragma user_version = 2`, 249 | `alter table mapruns add runinfo text` 250 | ], 251 | 252 | // version 3 - add gear checker 253 | [ 254 | `pragma user_version = 3`, 255 | ` 256 | create table if not exists gear ( 257 | timestamp text not null, 258 | data text not null, 259 | diff text, 260 | primary key (timestamp) 261 | ) 262 | ` 263 | ], 264 | 265 | 266 | // version 4 - fixes critical bug that caused previous versions to fail on first run 267 | [ 268 | `pragma user_version = 4`, 269 | `alter table mapruns add kills number`, 270 | `insert or ignore into mapruns(id, firstevent, lastevent, gained, kills, runinfo) values(-1, -1, -1, -1, -1, '{"ignored":true}')` 271 | ], 272 | 273 | // version 5 - league start and end dates 274 | [ 275 | 'pragma user_version = 5', 276 | ` 277 | create view if not exists leaguedates as 278 | select league, timestamp as start, 279 | (select ifnull(min(timestamp), 99999999999999) from leagues l2 where l2.timestamp > leagues.timestamp) as end 280 | from leagues 281 | order by start 282 | ` 283 | ], 284 | 285 | // version 6 - migration of fullrates and stashes to separate league DB 286 | [ 287 | // not incremented here, requires extra processing (see debug.js) 288 | ], 289 | 290 | // version 7 - properly set ignored tag in runinfo, instead of relying on magic numbers 291 | [ 292 | 'pragma user_version = 7', 293 | `update mapruns set runinfo = json_set(ifnull(runinfo, "{}"), '$.ignored', true) where kills = -1 and gained = -1` 294 | ], 295 | 296 | // version 8 - passive tree history 297 | [ 298 | 'pragma user_version = 8', 299 | `create table if not exists passives ( timestamp text primary key not null, data text not null )` 300 | ], 301 | 302 | // version 9 - Einhar red/yellow beast tracking update 303 | [ 304 | `pragma user_version = 9`, 305 | ` 306 | update mapruns set runinfo = ( 307 | select json_insert( 308 | runinfo, 309 | '$.masters."Einhar, Beastmaster".redBeasts', redbeasts, 310 | '$.masters."Einhar, Beastmaster".yellowBeasts', yellowbeasts 311 | ) as newinfo 312 | from ( 313 | select id, sum(case beast when "red" then 1 else 0 end) as redbeasts, sum(case beast when "yellow" then 1 else 0 end) as yellowbeasts from ( 314 | select case event_text 315 | when "Einhar, Beastmaster: Haha! You are captured, stupid beast." then "yellow" 316 | when "Einhar, Beastmaster: You have been captured, beast. You will be a survivor, or you will be food." then "yellow" 317 | when "Einhar, Beastmaster: This one is captured. Einhar will take it." then "yellow" 318 | when "Einhar, Beastmaster: Ohhh... That was a juicy one, exile." then "yellow" 319 | when "Einhar, Beastmaster: Do not worry little beast! We are friends now!" then "yellow" 320 | when "Einhar, Beastmaster: Off you go, little beast! Away!" then "yellow" 321 | when "Einhar, Beastmaster: We will be best friends beast! Until we slaughter you!" then "yellow" 322 | when "Einhar, Beastmaster: Great job, Exile! Einhar will take the captured beast to the Menagerie." then "red" 323 | when "Einhar, Beastmaster: The First Ones look upon this capture with pride, Exile. You hunt well." then "red" 324 | when "Einhar, Beastmaster: Survivor! You are well prepared for the end. This is a fine capture." then "red" 325 | when "Einhar, Beastmaster: What? Do you not have nets, exile?" then "red" 326 | end 327 | as beast from events 328 | where events.event_text like 'Einhar%' 329 | and events.id between mapruns.firstevent and mapruns.lastevent 330 | and beast is not null 331 | ) 332 | ) 333 | ) 334 | where runinfo like '%"Einhar, Beastmaster"%' and runinfo like '%"beasts"%' 335 | ` 336 | ] 337 | 338 | ]; 339 | 340 | // db maintenance - execute on every app start 341 | const maintSQL = [ 342 | `delete from incubators where timestamp < (select min(timestamp) from (select timestamp from incubators order by timestamp desc limit 25))` 343 | ]; 344 | 345 | const leagueInitSQL = [ 346 | [ 347 | `pragma user_version = 1`, 348 | ` 349 | create table if not exists characters ( 350 | name text primary key not null 351 | ) 352 | `, 353 | ` 354 | create table if not exists fullrates ( 355 | date text primary key not null, 356 | data text not null 357 | ) 358 | `, 359 | ` 360 | create table if not exists stashes ( 361 | timestamp text primary key not null, 362 | items text not null, 363 | value text not null 364 | ) 365 | ` 366 | ] 367 | ]; 368 | 369 | module.exports = DB; -------------------------------------------------------------------------------- /modules/GearChecker.js: -------------------------------------------------------------------------------- 1 | const logger = require("./Log").getLogger(__filename); 2 | const Utils = require("./Utils"); 3 | const https = require('https'); 4 | const zlib = require('zlib'); 5 | const { deepEqual } = require('fast-equals'); 6 | 7 | var DB; 8 | var settings; 9 | 10 | const gearSlots = [ 11 | "Helm", 12 | "Amulet", 13 | "BodyArmour", 14 | "Gloves", 15 | "Ring", 16 | "Ring2", 17 | "Belt", 18 | "Boots" 19 | // "Weapon", 20 | // "Weapon2", 21 | // "Offhand", 22 | // "Offhand2" 23 | ]; 24 | 25 | // special handling for these - can contain more than one item 26 | const multiGearSlots = [ 27 | "Weapons", 28 | "AmuletSockets", 29 | "BeltSockets", 30 | "BodyArmourSockets", 31 | "BootsSockets", 32 | "GlovesSockets", 33 | "HelmSockets", 34 | "RingSockets", 35 | "Ring2Sockets", 36 | "WeaponsSockets", 37 | "Flask", 38 | "TreeJewels" 39 | ]; 40 | 41 | const equipmentSlots = [ 42 | "Helm", 43 | "Amulet", 44 | "BodyArmour", 45 | "Gloves", 46 | "Ring", 47 | "Ring2", 48 | "Belt", 49 | "Boots", 50 | "Weapons", 51 | "Flask", 52 | "TreeJewels" 53 | ]; 54 | 55 | const flaskIgnoreProperties = [ 56 | "Lasts %0 Seconds", 57 | "Consumes %0 of %1 Charges on use", 58 | "Currently has %0 Charge", 59 | "Currently has %0 Charges", 60 | "Lasts {0} Seconds", 61 | "Consumes {0} of {1} Charges on use", 62 | "Currently has {0} Charge", 63 | "Currently has {0} Charges" 64 | ]; 65 | 66 | async function check(timestamp, eqp) { 67 | 68 | DB = require('./DB').getDB(); 69 | settings = require('./settings').get(); 70 | if(settings.activeProfile.noGearCheck) { 71 | logger.info("Gear checking disabled in settings"); 72 | return; 73 | } 74 | 75 | let currGear = {}; 76 | 77 | let eqpKeys = Object.keys(eqp); 78 | for(let i = 0; i < eqpKeys.length; i++) { 79 | 80 | let item = eqp[eqpKeys[i]]; 81 | if(!item.inventoryId) continue; 82 | 83 | let inv = item.inventoryId; 84 | // put all weapons into one set, to avoid unnecessary change logs for weapon swaps :-\ 85 | if(["Weapon", "Weapon2", "Offhand", "Offhand2"].includes(inv)) { 86 | inv = "Weapons"; 87 | } 88 | 89 | if(item.socketedItems) { 90 | // bind socketed items to gear slot instead, to avoid unnecessary change logs when changing gear but keeping the same gems 91 | if(inv !== "Weapons") { 92 | currGear[inv + "Sockets"] = item.socketedItems; 93 | } else { 94 | // put all weapon gem sets into one set :-\ 95 | currGear["WeaponsSockets"] = currGear["WeaponsSockets"] || []; 96 | currGear["WeaponsSockets"] = [ ...currGear["WeaponsSockets"], ...item.socketedItems ]; 97 | } 98 | } 99 | 100 | // avoid unnecessary change logs when migrating to a different league 101 | delete item.league; 102 | // ignore incubators on item 103 | delete item.incubatedItem; 104 | 105 | if(inv === "Flask" || inv === "Weapons") { 106 | currGear[inv] = currGear[inv] || []; 107 | currGear[inv].push(item); 108 | } else { 109 | currGear[inv] = item; 110 | } 111 | 112 | } 113 | 114 | let jewels = await getEquippedJewels(); 115 | if(jewels) { 116 | currGear["TreeJewels"] = []; 117 | for(let i = 0; i < jewels.length; i++) { 118 | currGear["TreeJewels"].push(jewels[i]); 119 | } 120 | } else { 121 | logger.info("Error getting equipped jewels, will not check diffs for now"); 122 | return; 123 | } 124 | 125 | let prevGear = await getPreviousEquipment(timestamp, currGear); 126 | if(!prevGear) { 127 | logger.info("Error getting previous gear, will not check diffs for now"); 128 | return; 129 | } else if(prevGear === "none") { 130 | insertEquipment(timestamp, currGear); 131 | } else { 132 | compareEquipment(timestamp, prevGear, currGear); 133 | } 134 | 135 | } 136 | 137 | function getEquippedJewels() { 138 | 139 | let league = encodeURIComponent(settings.activeProfile.league); 140 | let accountName = encodeURIComponent(settings.accountName); 141 | let characterName = encodeURIComponent(settings.activeProfile.characterName); 142 | let queryPath = `/character-window/get-passive-skills?league=${league}&accountName=${accountName}&character=${characterName}`; 143 | let requestParams = require('./Utils').getRequestParams(queryPath, settings.poesessid); 144 | 145 | return new Promise((resolve, reject) => { 146 | var request = https.request(requestParams, (response) => { 147 | var body = ''; 148 | response.setEncoding('utf8'); 149 | response.on('data', (chunk) => { 150 | body += chunk; 151 | }); 152 | response.on('end', () => { 153 | try { 154 | var data = JSON.parse(body); 155 | if( !data.items || (data.error && data.error.message === "Forbidden") ) { 156 | logger.info(`Failed to get skill tree, returning null`); 157 | resolve(null); 158 | } else { 159 | resolve(data.items); 160 | } 161 | } catch(err) { 162 | logger.info(`Failed to get skill tree: ${err}`); 163 | resolve(null); 164 | } 165 | }); 166 | response.on('error', (err) => { 167 | logger.info(`Failed to get skill tree: ${err}`); 168 | resolve(null); 169 | }); 170 | }); 171 | request.on('error', (err) => { 172 | logger.info(`Failed to get skill tree: ${err}`); 173 | resolve(null); 174 | }); 175 | request.end(); 176 | }); 177 | 178 | } 179 | 180 | function getPreviousEquipment(timestamp) { 181 | 182 | return new Promise((resolve, reject) => { 183 | DB.get("select data from gear where timestamp < ? order by timestamp desc limit 1", [timestamp], (err, row) => { 184 | if(err) { 185 | logger.info(`Unable to retrieve previous equipment: ${err}`); 186 | resolve(null); 187 | } else if(!row) { 188 | logger.info(`No previous equipment found!`); 189 | resolve("none"); 190 | } else { 191 | logger.info("Returning previous equipment"); 192 | zlib.inflate(row.data, (err, buffer) => { 193 | if(err) { 194 | // old data - compression not implemented yet, just parse directly 195 | resolve(JSON.parse(row.data)); 196 | } else { 197 | resolve(JSON.parse(buffer.toString())); 198 | } 199 | }); 200 | } 201 | }); 202 | }); 203 | 204 | } 205 | 206 | function compareEquipment(timestamp, prev, curr) { 207 | 208 | let diffs = {}; 209 | 210 | for(var slot of gearSlots) { 211 | 212 | if(!prev[slot] && curr[slot]) { 213 | addDiff(slot, null, curr[slot]); 214 | } else if(!prev[slot] && curr[slot]) { 215 | addDiff(slot, prev[slot], null); 216 | } else { 217 | if(!itemsEqual(prev[slot], curr[slot])) { 218 | addDiff(slot, prev[slot], curr[slot]); 219 | } 220 | } 221 | } 222 | 223 | for(var slot of multiGearSlots) { 224 | 225 | prev[slot] = prev[slot] || []; 226 | curr[slot] = curr[slot] || []; 227 | 228 | let prevTemp = JSON.parse(JSON.stringify(prev[slot])); 229 | let currTemp = JSON.parse(JSON.stringify(curr[slot])); 230 | 231 | for(let i = prevTemp.length - 1; i >= 0; i--) { 232 | let p = prevTemp[i]; 233 | for(let j = currTemp.length - 1; j >= 0; j--) { 234 | let c = currTemp[j]; 235 | if(itemsEqual(p, c)) { 236 | //logger.info(`Found same ${slot} in prev${i} and curr${j}`); 237 | prevTemp.splice(i, 1); 238 | currTemp.splice(j, 1); 239 | break; 240 | } 241 | } 242 | } 243 | 244 | if(prevTemp.length > 0 || currTemp.length > 0) { 245 | addDiff(slot, prevTemp, currTemp); 246 | } else { 247 | //logger.info(`No diffs found in ${slot}`); 248 | } 249 | 250 | } 251 | 252 | if(Object.keys(diffs).length > 0) { 253 | logger.info("Inserting equipment diff"); 254 | insertEquipment(timestamp, curr, diffs); 255 | } else { 256 | logger.info("No diffs found in equipment, returning"); 257 | } 258 | 259 | function addDiff(s, p, c) { 260 | logger.info(`Diffs found in ${s}`); 261 | diffs[s] = { prev: p, curr: c }; 262 | } 263 | 264 | } 265 | 266 | function itemsEqual(a, b) { 267 | if(!a || !b) return (a == b); 268 | return deepEqual(getTempItem(a), getTempItem(b)); 269 | } 270 | 271 | function getTempItem(item) { 272 | 273 | let tempItem; 274 | try { 275 | tempItem = JSON.parse(JSON.stringify(item)); 276 | } catch(e) { 277 | logger.info(`Error parsing, item follows`); 278 | logger.info(JSON.stringify(item)); 279 | } 280 | 281 | if(tempItem.inventoryId === "Flask") { 282 | // ignore flask charges and enchantment mods 283 | delete tempItem.enchantMods; 284 | for(let i = tempItem.properties.length - 1; i >= 0; i--) { 285 | if(flaskIgnoreProperties.includes(tempItem.properties[i].name)) { 286 | tempItem.properties.splice(i, 1); 287 | } 288 | } 289 | } 290 | 291 | // remove icon (ignore flask icon changes) 292 | delete tempItem.icon; 293 | // remove inventory id (ignore weapon swaps) 294 | delete tempItem.inventoryId; 295 | // remove requirements (may be changed by socketed gems) 296 | delete tempItem.requirements; 297 | // remove additionalProperties (only contains XP on skill gems) 298 | delete tempItem.additionalProperties; 299 | // remove socketed items 300 | delete tempItem.socketedItems; 301 | 302 | // ignore jewel socket position on tree 303 | delete tempItem.x; 304 | delete tempItem.y; 305 | 306 | return tempItem; 307 | 308 | } 309 | 310 | async function insertEquipment(timestamp, currData, diffData = "") { 311 | let data = await Utils.compress(currData); 312 | let diff = JSON.stringify(diffData); 313 | DB.run("insert into gear(timestamp, data, diff) values(?, ?, ?)", [timestamp, data, diff], (err) => { 314 | if(err) { 315 | logger.info(`Unable to insert equipment: ${err}`); 316 | } else { 317 | logger.info(`Updated last equipment at ${timestamp} (data length: ${data.length}, diff length: ${diff.length})`); 318 | } 319 | }); 320 | } 321 | 322 | module.exports.check = check; 323 | module.exports.itemsEqual = itemsEqual; 324 | module.exports.gearSlots = gearSlots; 325 | module.exports.multiGearSlots = multiGearSlots; 326 | module.exports.equipmentSlots = equipmentSlots; -------------------------------------------------------------------------------- /modules/InventoryGetter.js: -------------------------------------------------------------------------------- 1 | const logger = require("./Log").getLogger(__filename); 2 | const https = require('https'); 3 | const moment = require('moment'); 4 | const XPTracker = require('./XPTracker'); 5 | const KillTracker = require('./KillTracker'); 6 | const GearChecker = require('./GearChecker'); 7 | const EventEmitter = require('events'); 8 | 9 | var DB; 10 | var settings; 11 | var emitter = new EventEmitter(); 12 | 13 | class InventoryGetter extends EventEmitter { 14 | 15 | constructor() { 16 | 17 | super(); 18 | 19 | DB = require('./DB').getDB(); 20 | settings = require('./settings').get(); 21 | 22 | var league = encodeURIComponent(settings.activeProfile.league); 23 | var accountName = encodeURIComponent(settings.accountName); 24 | var characterName = encodeURIComponent(settings.activeProfile.characterName); 25 | 26 | this.queryPath = `/character-window/get-items?league=${league}&accountName=${accountName}&character=${characterName}`; 27 | 28 | this.on("xp", XPTracker.logXP); 29 | this.on("equipment", KillTracker.logKillCount); 30 | this.on("equipment", GearChecker.check); 31 | 32 | logger.info(`Inventory getter started with query path ${this.queryPath}`); 33 | 34 | } 35 | 36 | /* 37 | * function name not completely accurate -- does not perform full diff, only gets items added in current inventory 38 | */ 39 | async getInventoryDiffs(timestamp) { 40 | return new Promise(async (resolve, reject) => { 41 | var previnv = await this.getPreviousInventory(); 42 | var currinv = await this.getCurrentInventory(timestamp); 43 | var diff = await this.compareInventories(previnv, currinv); 44 | resolve(diff); 45 | }); 46 | } 47 | 48 | compareInventories(prev, curr) { 49 | 50 | return new Promise((resolve, reject) => { 51 | 52 | //logger.info("Comparing inventories..."); 53 | 54 | var prevKeys = Object.keys(prev); 55 | var currKeys = Object.keys(curr); 56 | 57 | var diff = {}; 58 | 59 | currKeys.forEach(key => { 60 | if (!prevKeys.includes(key)) { 61 | diff[key] = curr[key]; 62 | } else { 63 | var elem = this.compareElements(prev[key], curr[key]); 64 | if (elem) { 65 | diff[key] = elem; 66 | } 67 | } 68 | }); 69 | 70 | this.updateLastInventory(curr); 71 | resolve(diff); 72 | 73 | }); 74 | 75 | } 76 | 77 | compareElements(prev, curr) { 78 | if (prev.stackSize && curr.stackSize && curr.stackSize > prev.stackSize) { 79 | var obj = Object.assign({}, curr); 80 | obj.stackSize -= prev.stackSize; 81 | return obj; 82 | } else if(prev.name !== curr.name || prev.typeLine !== curr.typeLine) { 83 | // for items that transform (fated uniques, upgraded breachstones, etc) 84 | return curr; 85 | } 86 | return null; 87 | } 88 | 89 | getPreviousInventory() { 90 | return new Promise((resolve, reject) => { 91 | DB.all("select timestamp, inventory from lastinv order by timestamp desc", (err, rows) => { 92 | if (err) { 93 | logger.info(`Failed to get previous inventory: ${err}`); 94 | resolve({}); 95 | } 96 | if (rows.length === 0) { 97 | resolve({}); 98 | } else { 99 | resolve(JSON.parse(rows[0].inventory)); 100 | } 101 | }); 102 | }); 103 | } 104 | 105 | getCurrentInventory(timestamp) { 106 | 107 | var ig = this; 108 | var requestParams = require('./Utils').getRequestParams(this.queryPath, settings.poesessid); 109 | 110 | return new Promise((resolve, reject) => { 111 | var request = https.request(requestParams, (response) => { 112 | var body = ''; 113 | response.setEncoding('utf8'); 114 | response.on('data', (chunk) => { 115 | body += chunk; 116 | }); 117 | response.on('end', () => { 118 | try { 119 | var data = JSON.parse(body); 120 | if(data.error && data.error.message === "Forbidden") { 121 | emitter.emit("invalidSessionID"); 122 | resolve({}); 123 | } else { 124 | var inv = this.getInventory(data); 125 | ig.emit("xp", timestamp, data.character.experience); 126 | ig.emit("equipment", timestamp, inv.equippedItems); 127 | resolve(inv.mainInventory); 128 | } 129 | } catch(err) { 130 | logger.info(`Failed to get current inventory: ${err}`); 131 | resolve({}); 132 | } 133 | }); 134 | response.on('error', (err) => { 135 | logger.info(`Failed to get current inventory: ${err}`); 136 | resolve({}); 137 | }); 138 | }); 139 | request.on('error', (err) => { 140 | logger.info(`Failed to get current inventory: ${err}`); 141 | resolve({}); 142 | }); 143 | request.end(); 144 | }); 145 | } 146 | 147 | updateLastInventory(data) { 148 | var dataString = JSON.stringify(data); 149 | DB.serialize(() => { 150 | DB.run("delete from lastinv", (err) => { 151 | if (err) { 152 | logger.info(`Unable to delete last inventory: ${err}`); 153 | } 154 | }); 155 | var timestamp = moment().format('YYYYMMDDHHmmss') 156 | DB.run( 157 | "insert into lastinv(timestamp, inventory) values(?, ?)", [timestamp, dataString], (err) => { 158 | if (err) { 159 | logger.info(`Unable to update last inventory: ${err}`); 160 | } else { 161 | logger.info(`Updated last inventory at ${timestamp} (length: ${dataString.length})`); 162 | } 163 | } 164 | ); 165 | }); 166 | } 167 | 168 | getInventory(inv) { 169 | var mainInventory = {}; 170 | var equippedItems = {}; 171 | inv.items.forEach(item => { 172 | if (item.inventoryId === "MainInventory") { 173 | mainInventory[item.id] = item; 174 | } else { 175 | mainInventory[item.id] = item; 176 | equippedItems[item.id] = item; 177 | if(item.socketedItems) { 178 | for(let i = 0; i < item.socketedItems.length; i++) { 179 | // this prevents gem swaps from being counted as newly picked up 180 | let socketedItem = item.socketedItems[i]; 181 | mainInventory[socketedItem.id] = socketedItem; 182 | equippedItems[socketedItem.id] = socketedItem; 183 | } 184 | } 185 | } 186 | }); 187 | return { 188 | mainInventory: mainInventory, 189 | equippedItems: equippedItems 190 | }; 191 | } 192 | 193 | } 194 | 195 | module.exports = InventoryGetter; 196 | module.exports.emitter = emitter; -------------------------------------------------------------------------------- /modules/ItemCategoryParser.js: -------------------------------------------------------------------------------- 1 | const logger = require("./Log").getLogger(__filename); 2 | const data = require('../res/data/itemCategories.json'); 3 | 4 | const equipmentBaseTypes = data.equipmentBaseTypes; 5 | const gemBaseTypes = data.gemBaseTypes; 6 | const otherBaseTypes = data.otherBaseTypes; 7 | const nonStackableBaseTypes = [].concat(Object.keys(equipmentBaseTypes), Object.keys(gemBaseTypes)); 8 | 9 | const metamorphSamples = [ 10 | "BrainInventory", "LungInventory", "HeartInventory", "LiverInventory", "EyeballInventory" 11 | ]; 12 | 13 | const nonStackableBulkItems = [ 14 | 'Map Fragments', 15 | 'Prophecy', 16 | 'Labyrinth Items', 17 | 'Maps', 18 | 'Incubator', 19 | 'Atlas Region Upgrade Item' 20 | ]; 21 | 22 | function getCategory(item, subcategory = false) { 23 | 24 | // handle hybrid gems 25 | var t = (item.hybrid ? item.hybrid.baseTypeName : item.typeLine); 26 | if(!t) return null; 27 | 28 | if(t === "Expedition Logbook") { 29 | return t; 30 | } 31 | 32 | if(t.includes("Contract")) { 33 | return data.heistQuestItems.includes(t) ? "Quest Items" : "Contract"; 34 | } 35 | 36 | if(t.includes("Blueprint")) { 37 | return "Blueprint"; 38 | } 39 | 40 | if(otherBaseTypes[t]) { 41 | if(!subcategory && Array.isArray(otherBaseTypes[t])) { 42 | return otherBaseTypes[t][0]; 43 | } else { 44 | return otherBaseTypes[t]; 45 | } 46 | } 47 | 48 | switch(item.frameType) { 49 | case 4: 50 | var n = t.replace(/(Superior|Anomalous|Divergent|Phantasmal) /g, ""); 51 | if(gemBaseTypes[n]) { 52 | return gemBaseTypes[n]; 53 | } else { 54 | logger.info(`No base type found for gem [${t}]`); 55 | return ""; 56 | } 57 | case 5: 58 | if(t.startsWith("Captured Soul")) { 59 | return "Pantheon Soul"; 60 | } else if(t.endsWith("Seed") || t.endsWith("Grain") || t.endsWith("Bulb") || t.endsWith("fruit")) { 61 | return "Harvest Seed"; 62 | } 63 | return "Labyrinth Items"; 64 | case 6: 65 | return "Divination Card"; 66 | case 7: 67 | return "Quest Items"; 68 | case 8: 69 | return "Prophecy"; 70 | } 71 | 72 | if(t.endsWith("Scarab")) { 73 | return (subcategory ? ["Map Fragments", "Scarab"] : "Map Fragments"); 74 | } 75 | 76 | if(t.includes("Watchstone")) { 77 | return "Atlas Region Upgrade Item"; 78 | } 79 | 80 | // Maligaro's Map quest item has frameType 7, already detected above as a quest item 81 | if(t.includes(" Map")) { 82 | return "Maps"; 83 | } 84 | 85 | if(t.endsWith("Incubator")) { 86 | return "Incubator"; 87 | } 88 | 89 | if(t.endsWith("Piece")) { 90 | return "Harbinger Item Piece"; 91 | } 92 | 93 | if(item.icon.includes("BestiaryOrbFull")) { 94 | return "Captured Beast"; 95 | } 96 | 97 | // 3.9 metamorph inventory organs 98 | for(var i = 0; i < metamorphSamples.length; i++) { 99 | if(item.icon.includes(metamorphSamples[i])) return "Metamorph Sample"; 100 | } 101 | 102 | // equipment - search by hardcoded basetype 103 | t = t.replace("Superior ", ""); 104 | 105 | // non-magic equipment 106 | if(item.frameType !== 1) { 107 | if(equipmentBaseTypes[t]) { 108 | return equipmentBaseTypes[t]; 109 | } 110 | } 111 | 112 | // magic equipment - typeline is polluted by prefixes $%&*#^@!!! 113 | var keys = Object.keys(equipmentBaseTypes); 114 | for(var i = 0; i < keys.length; i++) { 115 | var key = keys[i]; 116 | if(t.includes(key)) { 117 | return equipmentBaseTypes[key]; 118 | } 119 | } 120 | 121 | logger.info(`No category found for item ${item.id || "(no id)"}! JSON follows:`); 122 | logger.info(JSON.stringify(item)); 123 | return null; 124 | 125 | } 126 | 127 | function getEquipmentBaseType(str) { 128 | var types = Object.keys(equipmentBaseTypes); 129 | for(var i = 0; i < types.length; i++) { 130 | if(str.includes(types[i])) { 131 | return types[i]; 132 | } 133 | } 134 | return null; 135 | } 136 | 137 | function isNonStackable(str) { 138 | return nonStackableBaseTypes.includes(str); 139 | } 140 | 141 | module.exports.getCategory = getCategory; 142 | module.exports.getEquipmentBaseType = getEquipmentBaseType; 143 | module.exports.isNonStackable = isNonStackable; 144 | module.exports.nonStackableBulkItems = nonStackableBulkItems; -------------------------------------------------------------------------------- /modules/ItemFilter.js: -------------------------------------------------------------------------------- 1 | const logger = require('./Log').getLogger(__filename); 2 | const ItemCategoryParser = require("./ItemCategoryParser"); 3 | const itemTypes = ["nonunique", "unique", "gem", "map", "divcard", "prophecy", "oil", "fragment", "delve", "catalyst", "essence", "incubator", "currency"]; 4 | 5 | var settings; 6 | var itemFilters; 7 | 8 | function load() { 9 | 10 | itemFilters = {}; 11 | itemTypes.forEach(type => { 12 | itemFilters[type] = {}; 13 | }); 14 | settings = require("./settings").get(); 15 | 16 | // backward compatibility - preserve previous minItemValue functionality 17 | if(settings.minItemValue) { 18 | itemFilters["nonunique"] = {ignore: true, minValue: settings.minItemValue}; 19 | itemFilters["unique"] = {ignore: true, minValue: settings.minItemValue}; 20 | itemFilters["gem"] = {ignore: true, minValue: settings.minItemValue}; 21 | } 22 | 23 | // if new itemFilter setting is present, overwrite previous minItemValue 24 | if(settings.itemFilter) { 25 | itemFilters = settings.itemFilter; 26 | } 27 | 28 | logger.info(`Loaded: ${JSON.stringify(itemFilters)}`); 29 | 30 | } 31 | 32 | function filter(item) { 33 | 34 | if(!itemFilters) load(); 35 | 36 | // gem, div card, prophecy can be determined by frametype 37 | switch(item.frameType) { 38 | case 4: 39 | return itemFilters.gem; 40 | case 6: 41 | return itemFilters.divcard; 42 | case 8: 43 | return itemFilters.prophecy; 44 | // no default case - if none of the above, fall through 45 | } 46 | 47 | // gear - nonunique, unique 48 | let typeLine = ItemCategoryParser.getEquipmentBaseType(item.typeLine); 49 | if(ItemCategoryParser.isNonStackable(typeLine)) { 50 | switch(item.frameType) { 51 | case 3: 52 | case 9: 53 | // 3 = unique, 9 = relic 54 | return itemFilters.unique; 55 | default: 56 | return itemFilters.nonunique; 57 | } 58 | } 59 | 60 | // stackable items 61 | let cat = ItemCategoryParser.getCategory(item, true); 62 | switch(cat) { 63 | case "Maps": 64 | return itemFilters.map; 65 | case "Map Fragments": 66 | case "Labyrinth Items": // offering to the goddess 67 | case "Misc Map Items": // maven's invitation 68 | return itemFilters.fragment; 69 | case "Currency": 70 | case "Stackable Currency": 71 | return itemFilters.currency; 72 | case "Incubator": 73 | return itemFilters.incubator; 74 | } 75 | 76 | if(Array.isArray(cat)) { 77 | if(cat[0] === "Map Fragments") { 78 | return itemFilters.fragment; 79 | } else if(cat[0] === "Stackable Currency") { 80 | return itemFilters.currency; 81 | } else { // only remaining case is cat[0] === "Currency" 82 | switch(cat[1]) { 83 | case "Oil": 84 | return itemFilters.oil; 85 | case "Catalyst": 86 | return itemFilters.catalyst; 87 | case "Essence": 88 | return itemFilters.essence; 89 | case "Resonator": 90 | case "Fossil": 91 | return itemFilters.delve; 92 | default: 93 | return itemFilters.currency; 94 | } 95 | } 96 | } 97 | 98 | // default case: return empty filter 99 | return {}; 100 | 101 | } 102 | 103 | function getForCategory(cat) { 104 | if(!itemTypes.includes(cat)) return null; 105 | if(!itemFilters) load(); 106 | return itemFilters[cat]; 107 | } 108 | 109 | module.exports.load = load; 110 | module.exports.filter = filter; 111 | module.exports.getForCategory = getForCategory; -------------------------------------------------------------------------------- /modules/ItemParser.js: -------------------------------------------------------------------------------- 1 | const logger = require("./Log").getLogger(__filename); 2 | const Utils = require("./Utils"); 3 | const ItemCategoryParser = require("./ItemCategoryParser"); 4 | const rarities = ['Normal', 'Magic', 'Rare', 'Unique', 'Gem', 'Currency', 'Divination Card', 'Quest Item', 'Prophecy', 'Relic']; 5 | 6 | async function insertItems(items, timestamp) { 7 | return new Promise( async (resolve, reject) => { 8 | 9 | var DB = require('./DB').getDB(); 10 | 11 | var duplicateInventory = await isDuplicateInventory(items); 12 | if(duplicateInventory) { 13 | logger.info(`Duplicate items found for ${timestamp}, returning`); 14 | resolve(); 15 | } else { 16 | logger.info(`Inserting items for ${timestamp}`); 17 | DB.serialize(function() { 18 | DB.run("begin transaction", (err) => { 19 | if(err) { 20 | logger.info(`Error beginning transaction to insert items: ${err}`); 21 | } 22 | }); 23 | var stmt = DB.prepare( 24 | ` 25 | insert into items (id, event_id, icon, name, rarity, category, identified, typeline, sockets, stacksize, rawdata) 26 | values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 27 | ` 28 | ); 29 | Object.keys(items).forEach((key) => { 30 | var itemToInsert = parseItem(items[key], timestamp); 31 | stmt.run(itemToInsert, (err) => { 32 | if(err) { 33 | logger.info(`Error inserting item: ${err}`); 34 | logger.info(JSON.stringify(itemToInsert)); 35 | } 36 | }); 37 | }); 38 | stmt.finalize( (err) => { 39 | if(err) { 40 | logger.warn(`Error inserting items for ${timestamp}: ${err}`); 41 | DB.run("rollback", (err) => { 42 | if (err) { 43 | logger.info(`Error rolling back failed item insert: ${err}`); 44 | } 45 | }); 46 | } else { 47 | DB.run("commit", (err) => { 48 | if (err) { 49 | logger.info(`Error committing item insert: ${err}`); 50 | } 51 | }); 52 | } 53 | }); 54 | logger.info(`Done inserting items for ${timestamp}`); 55 | resolve(); 56 | }); 57 | } 58 | }); 59 | } 60 | 61 | async function isDuplicateInventory(items) { 62 | 63 | return new Promise( (resolve, reject) => { 64 | 65 | var checkDuplicates = false; 66 | var numItemsToCheck = 0; 67 | 68 | if(items.length === 0) resolve(false); 69 | 70 | var keys = Object.keys(items); 71 | var query = "select count(1) as count from items where ("; 72 | for(var i = 0; i < keys.length; i++) { 73 | 74 | if(items[keys[i]].stacksize || items[keys[i]].stackSize) continue; 75 | 76 | if(checkDuplicates) { 77 | query += " or "; 78 | } else { 79 | checkDuplicates = true; 80 | } 81 | 82 | query += `( id = '${keys[i]}' `; 83 | query += `)`; 84 | 85 | numItemsToCheck++; 86 | 87 | } 88 | query += ")"; 89 | 90 | if(!checkDuplicates || numItemsToCheck < 1) { 91 | resolve(false); 92 | } else { 93 | logger.info(query); 94 | var DB = require('./DB').getDB(); 95 | DB.get(query, (err, row) => { 96 | if(err) { 97 | logger.warn(`Error checking inventory keys: ${err}`); 98 | resolve(false); 99 | } else { 100 | logger.info(`${numItemsToCheck} items in inventory, ${row.count} duplicates found in DB`); 101 | resolve(row.count === numItemsToCheck); 102 | } 103 | }); 104 | } 105 | }); 106 | 107 | 108 | } 109 | 110 | function parseItem(item, timestamp) { 111 | 112 | var id = item.id; 113 | var icon = getImageUrl(item.icon); 114 | var name = stripTags(item.name); 115 | var rarity = rarities[item.frameType]; 116 | var category = ItemCategoryParser.getCategory(item); 117 | var identified = item.identified; 118 | 119 | var typeline = stripTags(item.typeLine); 120 | if(rarity === 'Gem' && item.typeLine !== item.baseType) { 121 | // to handle hybrid gems (general's cry, predator support) 122 | typeline = stripTags(item.baseType); 123 | } 124 | 125 | 126 | var stacksize = item.stackSize || null; 127 | var sockets = Utils.getSockets(item); 128 | var rawdata = JSON.stringify(item); 129 | 130 | return [id, timestamp, icon, name, rarity, category, identified, typeline, sockets, stacksize, rawdata]; 131 | } 132 | 133 | function getImageUrl(url) { 134 | // flask image urls are in a very strange form, just return as is 135 | if (url.includes("web.poecdn.com/gen")) { 136 | return url; 137 | } else { 138 | // stripping identifier from end 139 | return url.substring(0, url.indexOf("?")); 140 | } 141 | } 142 | 143 | function stripTags(name) { 144 | if(!name) { 145 | return null; 146 | } 147 | return name.replace("<><><>", ""); 148 | } 149 | 150 | module.exports.insertItems = insertItems; 151 | module.exports.parseItem = parseItem; -------------------------------------------------------------------------------- /modules/KillTracker.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | const logger = require("./Log").getLogger(__filename); 3 | const moment = require('moment'); 4 | 5 | var emitter = new EventEmitter(); 6 | 7 | const incubatorGearSlots = [ 8 | "Helm", 9 | "BodyArmour", 10 | "Gloves", 11 | "Boots", 12 | "Ring", 13 | "Ring2", 14 | "Amulet", 15 | "Boots", 16 | "Weapon", 17 | "Offhand" 18 | // weapon2 and offhand2 not included - alternate weapon set does not accumulate monster kills 19 | // seems inconsistent with gem leveling?? 20 | ]; 21 | 22 | async function logKillCount(timestamp, eqp) { 23 | 24 | var incubators = {}; 25 | Object.keys(eqp).forEach((key) => { 26 | var item = eqp[key]; 27 | if(item.incubatedItem) { 28 | incubators[key] = { 29 | gearSlot: item.inventoryId, 30 | itemType: item.incubatedItem.name, 31 | level: item.incubatedItem.level, 32 | progress: item.incubatedItem.progress, 33 | total: item.incubatedItem.total 34 | }; 35 | } 36 | }); 37 | 38 | var DB = require('./DB').getDB(); 39 | var currIncubators = JSON.stringify(incubators); 40 | var prevIncubators = await getPrevIncubators(DB); 41 | if(prevIncubators === currIncubators) { 42 | return; 43 | } else { 44 | DB.run("insert into incubators(timestamp, data) values(?, ?)", [timestamp, currIncubators], (err) => { 45 | if(err) { 46 | logger.info(`Error inserting incubator data for ${timestamp}): ${err}`); 47 | } else { 48 | emitter.emit("incubatorsUpdated", incubators); 49 | } 50 | }); 51 | } 52 | 53 | } 54 | 55 | function getPrevIncubators(DB) { 56 | return new Promise((resolve, reject) => { 57 | DB.get("select data from incubators order by timestamp desc limit 1", (err, row)=> { 58 | if(err) { 59 | logger.info(`Error getting previous incubators: ${err}`); 60 | } 61 | if(row) { 62 | resolve(row.data); 63 | } else { 64 | resolve(""); 65 | } 66 | }); 67 | }); 68 | } 69 | 70 | module.exports.logKillCount = logKillCount; 71 | module.exports.emitter = emitter; 72 | -------------------------------------------------------------------------------- /modules/Log.js: -------------------------------------------------------------------------------- 1 | const winston = require('winston'); 2 | const path = require('path'); 3 | 4 | class Log { 5 | 6 | static getLogger(module) { 7 | 8 | var app = require('electron').app || require('electron').remote.app; 9 | 10 | module = path.basename(module); 11 | return winston.createLogger({ 12 | level: "verbose", 13 | transports: [ 14 | new winston.transports.File({ 15 | filename: path.join(app.getPath("userData"), "log.txt"), 16 | maxsize: 8388608, 17 | maxFiles: 1, 18 | tailable: true 19 | }), 20 | new winston.transports.Console() 21 | ], 22 | format: winston.format.combine( 23 | winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss.SSS' }), 24 | winston.format.printf(info => `${info.timestamp} [${info.level}] ${module.padEnd(20)} > ${info.message}`) 25 | ) 26 | }); 27 | } 28 | 29 | } 30 | 31 | module.exports = Log; -------------------------------------------------------------------------------- /modules/MapRun.js: -------------------------------------------------------------------------------- 1 | const logger = require('./Log').getLogger(__filename); 2 | const Constants = require('./Constants'); 3 | const EventEmitter = require('events'); 4 | const Parser = require('./FilterParser'); 5 | const ClientTxtWatcher = require('./ClientTxtWatcher'); 6 | const Utils = require('./Utils'); 7 | 8 | class MapRun extends EventEmitter { 9 | 10 | constructor(mapID, char) { 11 | super(); 12 | this.init(mapID, char); 13 | } 14 | 15 | async init(mapID, char) { 16 | this.id = mapID; 17 | this.DB = require('./DB').getDB(char); 18 | this.parser = await Parser.get(mapID, char); 19 | this.nav = { 20 | prev : await this.getPrevMap(mapID), 21 | next : await this.getNextMap(mapID), 22 | } 23 | this.info = await this.getInfo(mapID); 24 | this.mods = await this.getMods(mapID); 25 | this.events = await this.getEvents(mapID); 26 | this.items = await this.getItems(mapID); 27 | this.league = await this.getLeague(mapID); 28 | 29 | if(this.info.level) { 30 | this.parser.setAreaLevel(this.info.level); 31 | } 32 | 33 | this.emit("MapRunReady", mapID); 34 | } 35 | 36 | async getPrevMap(mapID) { 37 | return new Promise((resolve, reject) => { 38 | this.DB.get("select id from mapruns where id < ? and json_extract(runinfo, '$.ignored') is null order by id desc limit 1", [mapID], (err, row) => { 39 | if (err) { 40 | logger.error(`Unable to get previous map: ${err}`); 41 | resolve(null); 42 | } else { 43 | resolve (row && row.id !== -1 ? row.id : null); 44 | } 45 | }); 46 | }); 47 | } 48 | 49 | async getNextMap(mapID) { 50 | return new Promise((resolve, reject) => { 51 | this.DB.get("select id from mapruns where id > ? and json_extract(runinfo, '$.ignored') is null order by id limit 1", [mapID], (err, row) => { 52 | if (err) { 53 | logger.error(`Unable to get next map: ${err}`); 54 | resolve(null); 55 | } else { 56 | resolve (row && row.id !== -1 ? row.id : null); 57 | } 58 | }); 59 | }); 60 | } 61 | 62 | async getInfo(mapID) { 63 | return new Promise((resolve, reject) => { 64 | this.DB.get(` 65 | select name, level, depth, iiq, iir, packsize, xp, kills, runinfo, 66 | (select xp from mapruns m where m.id < mapruns.id and xp is not null order by m.id desc limit 1) prevxp 67 | from areainfo, mapruns where mapruns.id = ? and areainfo.id = ? 68 | `, [mapID, mapID], (err, row) => { 69 | if (err) { 70 | logger.error(`Unable to get map info: ${err}`); 71 | resolve(null); 72 | } else { 73 | let info = { 74 | name: row.name, 75 | level: row.level, 76 | depth: row.depth, 77 | iiq: row.iiq, 78 | iir: row.iir, 79 | packsize: row.packsize, 80 | xp: row.xp, 81 | prevxp: row.prevxp, 82 | kills: row.kills 83 | }; 84 | Object.assign(info, JSON.parse(row.runinfo)); 85 | resolve(info); 86 | } 87 | }); 88 | }); 89 | } 90 | 91 | async getMods(mapID) { 92 | return new Promise((resolve, reject) => { 93 | var arr = []; 94 | this.DB.all("select mod from mapmods where area_id = ? order by cast(id as integer)", [mapID], (err, rows) => { 95 | if (err) { 96 | logger.error(`Unable to get next map: ${err}`); 97 | resolve(null); 98 | } else { 99 | rows.forEach(row => arr.push(row.mod)); 100 | resolve(arr); 101 | } 102 | }); 103 | }); 104 | } 105 | 106 | async getEvents(mapID) { 107 | return new Promise((resolve, reject) => { 108 | var events = []; 109 | this.DB.all(` 110 | select events.* from mapruns, events 111 | where mapruns.id = ? 112 | and events.id between mapruns.firstevent and mapruns.lastevent 113 | order by events.id; 114 | `, [mapID], (err, rows) => { 115 | if (err) { 116 | logger.info(`Failed to get run events: ${err}`); 117 | } else { 118 | rows.forEach(row => { 119 | if(row.event_type !== "chat") { 120 | events.push({ 121 | id: row.id, 122 | event_type: row.event_type, 123 | event_text: row.event_text 124 | }); 125 | } 126 | }); 127 | resolve(events); 128 | } 129 | }); 130 | }); 131 | } 132 | 133 | async getItems(mapID) { 134 | return new Promise((resolve, reject) => { 135 | var items = {}; 136 | this.DB.all(` 137 | select events.id, items.rarity, items.icon, items.value, items.stacksize, items.rawdata from mapruns, events, items 138 | where mapruns.id = ? 139 | and events.id between mapruns.firstevent and mapruns.lastevent 140 | and items.event_id = events.id; 141 | `, [mapID], async (err, rows) => { 142 | if (err) { 143 | logger.info(`Failed to get run events: ${err}`); 144 | resolve(null); 145 | } else { 146 | for(var i = 0; i < rows.length; i++) { 147 | var row = rows[i]; 148 | var data = JSON.parse(row.rawdata); 149 | if(!items[row.id]) { 150 | items[row.id] = []; 151 | } 152 | var secretName = ""; 153 | if(row.rarity === "Unique") { 154 | secretName = Utils.getItemName(row.icon); 155 | if(secretName) { 156 | if(secretName === "Starforge" && data.elder) { 157 | secretName = "Voidforge"; 158 | } 159 | } 160 | } 161 | if(secretName || row.value || row.stacksize) { 162 | if(secretName) data.secretName = secretName; 163 | if(row.value) data.value = row.value; 164 | if(row.stacksize) data.pickupStackSize = row.stacksize; 165 | items[row.id].push(JSON.stringify(data)); 166 | } else { 167 | items[row.id].push(row.rawdata); 168 | } 169 | 170 | } 171 | resolve(items); 172 | } 173 | }); 174 | }); 175 | } 176 | 177 | async getLeague(mapID) { 178 | return new Promise((resolve, reject) => { 179 | this.DB.get(`select league from leagues where timestamp < ? order by timestamp desc limit 1`, [mapID], async (err, row) => { 180 | if (err) { 181 | logger.info(`Failed to get league: ${err}`); 182 | resolve(null); 183 | } else { 184 | resolve(row.league); 185 | } 186 | }); 187 | }); 188 | } 189 | 190 | } 191 | 192 | module.exports = MapRun; -------------------------------------------------------------------------------- /modules/OCRWatcher.js: -------------------------------------------------------------------------------- 1 | const Jimp = require('jimp'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | const chokidar = require('chokidar'); 5 | const StringMatcher = require('./StringMatcher'); 6 | const logger = require("./Log").getLogger(__filename); 7 | const EventEmitter = require('events'); 8 | 9 | var DB; 10 | var watcher; 11 | var emitter = new EventEmitter(); 12 | var app = require('electron').app || require('electron').remote.app; 13 | var areaInfo; 14 | var mapMods; 15 | 16 | const watchPaths = [ 17 | path.join(app.getPath('userData'), '.temp_capture', "*.area.png"), 18 | path.join(app.getPath('userData'), '.temp_capture', "*.mods.png") 19 | ]; 20 | 21 | function test(filename) { 22 | DB = null; 23 | processImage(filename); 24 | } 25 | 26 | function start() { 27 | 28 | areaInfo = null; 29 | mapMods = null; 30 | DB = require('./DB').getDB(); 31 | 32 | if (watcher) { 33 | try { 34 | watcher.close(); 35 | watcher.unwatch(watchPaths); 36 | } catch (err) { 37 | } 38 | } 39 | 40 | watcher = chokidar.watch(watchPaths, {usePolling: true, awaitWriteFinish: true, ignoreInitial: true}); 41 | watcher.on("add", (path) => { 42 | processImage(path); 43 | }); 44 | 45 | } 46 | 47 | function processImage(file) { 48 | 49 | logger.info("Performing OCR on " + file + "..."); 50 | 51 | var TesseractWorker = require('tesseract.js').create({ langPath: process.resourcesPath }); 52 | 53 | TesseractWorker.recognize(file, { 54 | lang: "eng", 55 | tessedit_char_whitelist: "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:-',%+" 56 | }).then(async result => { 57 | 58 | var filename = path.basename(file); 59 | var timestamp = filename.substring(0, filename.indexOf(".")); 60 | var lines = []; 61 | result.lines.forEach(line => { 62 | lines.push(line.text.trim()); 63 | }); 64 | 65 | if (file.indexOf("area") > -1) { 66 | 67 | var area = getAreaInfo(lines); 68 | var areaName = await getAreaNameFromDB(timestamp); 69 | if(areaName) { 70 | logger.info(`Got last entered area from db: ${areaName}`); 71 | area.name = areaName; 72 | } else { 73 | logger.info(`Got last entered area from ocr: ${area.name}`); 74 | } 75 | 76 | DB.run( 77 | "insert into areainfo(id, name, level, depth) values(?, ?, ?, ?)", 78 | [timestamp, area.name, area.level, area.depth], 79 | (err) => { 80 | if(err) { 81 | cleanFailedOCR(err, timestamp); 82 | } else { 83 | areaInfo = area; 84 | checkAreaInfoComplete(); 85 | } 86 | } 87 | ); 88 | 89 | } else if (file.indexOf("mods") > -1) { 90 | 91 | try { 92 | var mods = getModInfo(lines); 93 | var mapModErr = null; 94 | for (var i = 0; i < mods.length; i++) { 95 | DB.run( 96 | "insert into mapmods(area_id, id, mod) values(?, ?, ?)", 97 | [timestamp, i, mods[i]], 98 | (err) => { 99 | if(err && !mapModErr) { 100 | mapModErr = err; 101 | } 102 | } 103 | ); 104 | } 105 | if(mapModErr) { 106 | cleanFailedOCR(mapModErr, timestamp); 107 | } else { 108 | mapMods = mods; 109 | checkAreaInfoComplete(); 110 | } 111 | } 112 | catch(e) { 113 | cleanFailedOCR(e, timestamp); 114 | } 115 | 116 | } 117 | 118 | }).catch(err => { 119 | cleanFailedOCR(err); 120 | }).finally(() => { 121 | fs.unlinkSync(file); 122 | TesseractWorker.terminate(); 123 | logger.info("Completed OCR on " + file + ", deleting"); 124 | }); 125 | } 126 | 127 | function checkAreaInfoComplete() { 128 | if(areaInfo && mapMods) { 129 | emitter.emit("areaInfoComplete", {areaInfo: areaInfo, mapMods: mapMods}); 130 | areaInfo = null; 131 | mapMods = null; 132 | } 133 | } 134 | 135 | function cleanFailedOCR(e, timestamp) { 136 | areaInfo = null; 137 | mapMods = null; 138 | logger.info("Error processing screenshot: " + e); 139 | emitter.emit("OCRError"); 140 | if(timestamp) { 141 | DB.serialize(() => { 142 | DB.run("delete from areainfo where id = ?", [timestamp], (err) => { 143 | if(err) { 144 | logger.info(`Error cleaning areainfo for failed OCR: ${err}`); 145 | } 146 | }); 147 | DB.run("delete from mapmods where area_id = ?", [timestamp], (err) => { 148 | if(err) { 149 | logger.info(`Error cleaning mapmods for failed OCR: ${err}`); 150 | } 151 | }); 152 | }); 153 | } 154 | } 155 | 156 | function getAreaInfo(lines) { 157 | 158 | var areaInfo = {}; 159 | 160 | for (var i = 0; i < lines.length; i++) { 161 | var line = lines[i]; 162 | if (!areaInfo.name) { 163 | var str = StringMatcher.getMap(line); 164 | if (str.length > 0) { 165 | areaInfo.name = str; 166 | continue; 167 | } 168 | } 169 | var levelMatch = line.match(/Level: ([1-9][0-9])?/); 170 | if (levelMatch) { 171 | areaInfo.level = levelMatch.pop(); 172 | continue; 173 | } 174 | depthMatch = line.match(/Depth: ([1-9][0-9]+)?/); 175 | if (depthMatch) { 176 | areaInfo.depth = depthMatch.pop(); 177 | continue; 178 | } 179 | } 180 | 181 | if (!areaInfo.depth) { 182 | areaInfo.depth = null; 183 | } 184 | return areaInfo; 185 | 186 | } 187 | 188 | function getModInfo(lines) { 189 | 190 | var mods = []; 191 | for (var i = 0; i < lines.length; i++) { 192 | var mod = StringMatcher.getMod(lines[i]); 193 | if (mod.length > 0) { 194 | mods.push(mod); 195 | } 196 | } 197 | return mods; 198 | 199 | } 200 | 201 | function getAreaNameFromDB(timestamp) { 202 | return new Promise((resolve, reject) => { 203 | DB.get("select event_text as area from events where event_type='entered' and id < ? order by id desc limit 1", [timestamp], (err, row)=> { 204 | if(err) { 205 | logger.info(`Error getting previous XP: ${err}`); 206 | resolve(null); 207 | } else { 208 | resolve(row ? row.area : null); 209 | } 210 | }); 211 | }); 212 | } 213 | 214 | module.exports.start = start; 215 | module.exports.test = test; 216 | module.exports.emitter = emitter; -------------------------------------------------------------------------------- /modules/ScreenshotWatcher.js: -------------------------------------------------------------------------------- 1 | const Jimp = require('jimp'); 2 | const path = require('path'); 3 | const convert = require('color-convert'); 4 | const moment = require('moment'); 5 | const chokidar = require('chokidar'); 6 | const logger = require("./Log").getLogger(__filename); 7 | const EventEmitter = require('events'); 8 | const fs = require('fs'); 9 | 10 | const SCREENSHOT_DIRECTORY_SIZE_LIMIT = 400; 11 | 12 | var settings; 13 | var watcher; 14 | var currentWatchDirectory; 15 | 16 | var app = require('electron').app || require('electron').remote.app; 17 | var emitter = new EventEmitter(); 18 | 19 | function tryClose() { 20 | if(watcher) { 21 | try { 22 | watcher.close(); 23 | watcher.unwatch(currentWatchDirectory); 24 | watcher = null; 25 | currentWatchDirectory = null; 26 | } 27 | catch(err) { 28 | logger.info("Error closing screenshot watcher: " + err.message); 29 | } 30 | } 31 | } 32 | 33 | function start() { 34 | 35 | tryClose(); 36 | 37 | settings = require('./settings').get(); 38 | 39 | if (settings.screenshotDir !== "disabled") { 40 | logger.info("Watching " + settings.screenshotDir); 41 | watcher = chokidar.watch( 42 | `${settings.screenshotDir}`, 43 | {usePolling: true, awaitWriteFinish: true, ignoreInitial: true, disableGlobbing: true} 44 | ); 45 | watcher.on("add", (path) => { 46 | logger.info("Cropping new screenshot: " + path); 47 | process(path); 48 | }); 49 | currentWatchDirectory = settings.screenshotDir; 50 | } else { 51 | logger.info("Screenshot directory is disabled"); 52 | } 53 | 54 | } 55 | 56 | async function checkScreenshotSpaceUsed() { 57 | var dir = fs.readdirSync(settings.screenshotDir); 58 | if(dir.length > SCREENSHOT_DIRECTORY_SIZE_LIMIT) { 59 | emitter.emit("tooMuchScreenshotClutter", dir.length); 60 | } 61 | } 62 | 63 | 64 | function process(file) { 65 | var filename = moment().format("YMMDDHHmmss"); 66 | if (file.length > 0) { 67 | Jimp.read(file).then(image => { 68 | 69 | try { 70 | // 1920 x 1080 image scaled up 3x; 71 | // scale differently sized images proportionately 72 | var scaleFactor = 3 * (1920 / image.bitmap.width); 73 | 74 | var yBounds = getYBounds(image); 75 | var xBounds = getXBounds(image, yBounds); 76 | 77 | var image2 = image.clone(); 78 | 79 | // take only rightmost 14% of screen for area info (no area name is longer than this) 80 | var areaInfoWidth = Math.floor(image.bitmap.width * 0.14); 81 | // chop off top 24px of area info - blank 82 | image.crop(xBounds, 24, image.bitmap.width - xBounds, yBounds[0] - 24); 83 | if (image.bitmap.width > areaInfoWidth) { 84 | image.crop(image.bitmap.width - areaInfoWidth, 0, areaInfoWidth, image.bitmap.height); 85 | } 86 | image.color([ 87 | {apply: 'red', params: [99]}, 88 | {apply: 'blue', params: [-99]}, 89 | {apply: 'green', params: [-99]}, 90 | {apply: 'saturate', params: [100]}, 91 | ]); 92 | enhanceImage(image, scaleFactor); 93 | image.write(path.join(app.getPath('userData'), '.temp_capture', filename + "." + path.basename(file, ".png") + ".area.png")); 94 | 95 | image2.crop(xBounds, yBounds[0], image2.bitmap.width - xBounds, yBounds[1] - yBounds[0]); 96 | image2.color([ 97 | {apply: 'red', params: [-50]}, 98 | {apply: 'green', params: [-50]}, 99 | ]); 100 | enhanceImage(image2, scaleFactor); 101 | image2.write(path.join(app.getPath('userData'), '.temp_capture', filename + "." + path.basename(file, ".png") + ".mods.png")); 102 | logger.info("Deleting screenshot " + file); 103 | fs.unlinkSync(file); 104 | checkScreenshotSpaceUsed(); 105 | 106 | } catch(e) { 107 | logFailedCapture(e); 108 | } 109 | 110 | }); 111 | } 112 | 113 | } 114 | 115 | function enhanceImage(image, scaleFactor) { 116 | image.scale(scaleFactor, Jimp.RESIZE_BEZIER); 117 | image.invert(); 118 | image.greyscale(); 119 | /* 120 | image.convolute([ 121 | [0, 0, 0, 0, 0], 122 | [0, 0, -1, 0, 0], 123 | [0, -1, 5, -1, 0], 124 | [0, 0, -1, 0, 0], 125 | [0, 0, 0, 0, 0] 126 | ]); 127 | */ 128 | image.convolute([ 129 | [-1 / 8, -1 / 8, -1 / 8], 130 | [-1 / 8, 2, -1 / 8], 131 | [-1 / 8, -1 / 8, -1 / 8] 132 | ]); 133 | image.brightness(-0.43); 134 | image.contrast(0.75); 135 | } 136 | 137 | function getXBounds(image, yBounds) { 138 | 139 | var numCols = 40; 140 | var blueArray = []; 141 | 142 | for (var x = image.bitmap.width - 1; x > 0; x--) { 143 | var pixCount = 0; 144 | for (var y = yBounds[0]; y < yBounds[1]; y++) { 145 | var pixel = image.getPixelColor(x, y); 146 | if (isBlue(pixel)) { 147 | pixCount++; 148 | } 149 | } 150 | blueArray.push(pixCount); 151 | if (blueArray.length === numCols) { 152 | var blueAvg = blueArray.reduce((acc, curr) => { 153 | return acc + curr; 154 | }) / numCols; 155 | if (blueAvg < 1) { 156 | return x; 157 | } 158 | blueArray.shift(); 159 | } 160 | 161 | } 162 | 163 | return 0; 164 | 165 | } 166 | 167 | /* 168 | * Detects the bottom edge of the list of map mods. 169 | * 170 | * The map mod list is a solid block of pixels that are either black or blue, 171 | * in the upper-right corner of the screen 172 | */ 173 | function getYBounds(image) { 174 | 175 | var numRows = Math.floor(image.bitmap.width / 100); 176 | var numCols = Math.floor(image.bitmap.width / 10); 177 | var pixArray = []; 178 | var blueArray = []; 179 | var blueStarted = false; 180 | var lowerBound = 0; 181 | 182 | // start from top of image 183 | for (var y = 0; y < image.bitmap.height; y++) { 184 | var bluePixels = 0; 185 | var blackPixels = 0; 186 | // scan a row of pixels from left to right starting {numCols} px from the right edge, 187 | // counting the number of blue or black pixels in the row 188 | for (var x = image.bitmap.width - numCols; x < image.bitmap.width; x++) { 189 | var pixel = image.getPixelColor(x, y); 190 | if (isBlue(pixel)) { 191 | bluePixels++; 192 | } else if (isBlack(pixel, 20)) { 193 | blackPixels++; 194 | } 195 | } 196 | 197 | // number of blue or black pixels in row 198 | pixArray.push(bluePixels + blackPixels); 199 | // number of blue pixels only 200 | blueArray.push(bluePixels); 201 | 202 | // when {numRows} rows have been scanned, get running average 203 | if (pixArray.length === numRows) { 204 | // average number of blue/black pixels 205 | var totalAvg = pixArray.reduce((acc, curr) => { 206 | return acc + curr; 207 | }) / numRows; 208 | // average number of blue pixels 209 | var blueAvg = blueArray.reduce((acc, curr) => { 210 | return acc + curr; 211 | }) / numRows; 212 | 213 | if (totalAvg > (numRows * 0.95)) { 214 | if (blueAvg > 25 && !blueStarted) { 215 | // if the top of the mod list has not already been found, 216 | // mark it if average blue/black pixels is > 95% and average blue pixels > 25 217 | blueStarted = true; 218 | lowerBound = y - numRows; 219 | } else if (blueStarted && (blueAvg < 25 || (bluePixels + blackPixels < (numRows * 0.7)))) { 220 | // if top of mod list has already been found, check if we've gone past the bottom: 221 | // average blue pixels in past {numRows} rows < 25, or current row has less than 70% blue/black pixels 222 | // if so, return bounds of mod list 223 | return [lowerBound, y]; 224 | } 225 | } else if (blueStarted) { 226 | // also return bounds of mod list if top has already been found 227 | // and average blue/black pixels in past {numRows} rows < 95% 228 | return [lowerBound, y]; 229 | } 230 | 231 | // only keep pixel count for last {numRows} rows 232 | pixArray.shift(); 233 | blueArray.shift(); 234 | 235 | } 236 | 237 | } 238 | } 239 | 240 | function isBlue(pixel) { 241 | var rgba = Jimp.intToRGBA(pixel); 242 | var hsv = convert.rgb.hsl([rgba.r, rgba.g, rgba.b]); 243 | // map mod blue: 244 | // hue 240 245 | // saturation + value > 40 246 | // red and green components equal and both > 70 247 | 248 | return ( 249 | hsv[0] <= 250 250 | && hsv[0] >= 235 251 | && hsv[1] + hsv[2] > 40 252 | && Math.abs(rgba.r - rgba.g) <= 10 253 | && rgba.r > 70 254 | ); 255 | } 256 | 257 | function isBlack(pixel, tolerance) { 258 | var rgba = Jimp.intToRGBA(pixel); 259 | var hsv = convert.rgb.hsl([rgba.r, rgba.g, rgba.b]); 260 | return (hsv[2] <= tolerance); 261 | } 262 | 263 | function logFailedCapture(e) { 264 | logger.info(`Error processing screenshot: ${e}`); 265 | emitter.emit("OCRError"); 266 | } 267 | 268 | function test(file) { 269 | process(file); 270 | } 271 | 272 | module.exports.start = start; 273 | module.exports.emitter = emitter; 274 | module.exports.test = test; -------------------------------------------------------------------------------- /modules/SkillTreeWatcher.js: -------------------------------------------------------------------------------- 1 | const logger = require("./Log").getLogger(__filename); 2 | const https = require('https'); 3 | const moment = require('moment'); 4 | const EventEmitter = require('events'); 5 | 6 | var DB; 7 | var settings; 8 | var emitter = new EventEmitter(); 9 | 10 | class SkillTreeWatcher { 11 | 12 | constructor() { 13 | 14 | DB = require('./DB').getDB(); 15 | settings = require('./settings').get(); 16 | 17 | var league = encodeURIComponent(settings.activeProfile.league); 18 | var accountName = encodeURIComponent(settings.accountName); 19 | var characterName = encodeURIComponent(settings.activeProfile.characterName); 20 | 21 | this.queryPath = `/character-window/get-passive-skills?league=${league}&accountName=${accountName}&character=${characterName}`; 22 | 23 | logger.info(`Skill tree watcher started with query path ${this.queryPath}`); 24 | 25 | } 26 | 27 | async checkPassiveTree(timestamp) { 28 | 29 | var prevTree = await this.getPrevTree(); 30 | var requestParams = require('./Utils').getRequestParams(this.queryPath, settings.poesessid); 31 | 32 | return new Promise((resolve, reject) => { 33 | var request = https.request(requestParams, (response) => { 34 | var body = ''; 35 | response.setEncoding('utf8'); 36 | response.on('data', (chunk) => { 37 | body += chunk; 38 | }); 39 | response.on('end', () => { 40 | try { 41 | var data = JSON.parse(body); 42 | if(data.error && data.error.message === "Forbidden") { 43 | emitter.emit("invalidSessionID"); 44 | resolve({}); 45 | } else { 46 | let currTree = JSON.stringify(data.hashes); 47 | if(currTree !== prevTree) { 48 | logger.info(`prevtree: ${prevTree}`); 49 | logger.info(`currtree: ${currTree}`); 50 | this.insertPassiveTree(timestamp, currTree); 51 | } 52 | } 53 | } catch(err) { 54 | logger.info(`Failed to get current skill tree: ${err}`); 55 | resolve({}); 56 | } 57 | }); 58 | response.on('error', (err) => { 59 | logger.info(`Failed to get current skill tree: ${err}`); 60 | resolve({}); 61 | }); 62 | }); 63 | request.on('error', (err) => { 64 | logger.info(`Failed to get current skill tree: ${err}`); 65 | resolve({}); 66 | }); 67 | request.end(); 68 | }); 69 | } 70 | 71 | getPrevTree() { 72 | return new Promise((resolve, reject) => { 73 | DB.get("select timestamp, data from passives order by timestamp desc limit 1", (err, row) => { 74 | if (err) { 75 | logger.info(`Failed to get previous passive tree: ${err}`); 76 | resolve(null); 77 | } 78 | if(!row) { 79 | resolve(null); 80 | } else { 81 | resolve(row.data); 82 | } 83 | }); 84 | }); 85 | } 86 | 87 | insertPassiveTree(timestamp, data) { 88 | DB.run( 89 | "insert into passives(timestamp, data) values(?, ?)", [timestamp, data], (err) => { 90 | if (err) { 91 | logger.info(`Unable to insert current passive tree: ${err}`); 92 | } else { 93 | logger.info(`Updated current passive tree at ${timestamp} (length: ${data.length})`); 94 | } 95 | } 96 | ); 97 | } 98 | 99 | } 100 | 101 | module.exports = SkillTreeWatcher; 102 | module.exports.emitter = emitter; -------------------------------------------------------------------------------- /modules/StringMatcher.js: -------------------------------------------------------------------------------- 1 | const levenshtein = require('js-levenshtein'); 2 | const logger = require("./Log").getLogger(__filename); 3 | const Constants = require('./Constants'); 4 | 5 | var allAreas; 6 | 7 | class StringMatcher { 8 | 9 | static getMap(str) { 10 | if(!allAreas) { 11 | allAreas = []; 12 | let keys = Object.keys(Constants.areas); 13 | for(let i = 0; i < keys.length; i++) { 14 | allAreas.push( ...Constants.areas[keys[i]] ); 15 | } 16 | } 17 | return this.getClosest(str, allAreas); 18 | } 19 | 20 | static getMod(str) { 21 | if (str.length < 10) { 22 | return ""; 23 | } 24 | var ret = ""; 25 | ret = this.getClosest(str, Constants.mapMods); 26 | if (ret.indexOf("#") > -1) { 27 | var matches = str.match(/[1-9][0-9]*/g); 28 | if (matches) { 29 | ret = ret.replace("#", matches.pop()); 30 | } else { 31 | var tempStr = str.replace("S", "5"); 32 | var tempMatches = tempStr.match(/[1-9][0-9]*/g); 33 | if(tempMatches) { 34 | ret = ret.replace("#", tempMatches.pop()); 35 | } else { 36 | throw new Error(`No number replacement found: [${str}] -> [${ret}]`); 37 | } 38 | } 39 | } 40 | return ret; 41 | } 42 | 43 | static getClosest(str, arr) { 44 | var minLevenshtein = 999; 45 | var ret = ""; 46 | for (var i = 0; i < arr.length; i++) { 47 | 48 | var match = arr[i]; 49 | var score = levenshtein(str.toUpperCase(), match.toUpperCase()); 50 | if (score === 0) { 51 | return match; 52 | } else if (score < minLevenshtein || (score === minLevenshtein && match.indexOf("#") < 0) ) { 53 | minLevenshtein = score; 54 | ret = match; 55 | } 56 | 57 | } 58 | 59 | // don't return match if too different 60 | if (minLevenshtein / str.length > 0.5) { 61 | //logger.info("Correction factor too high (" + str + " -> " + ret + " = " + (minLevenshtein / str.length) + "), returning"); 62 | return ""; 63 | } 64 | 65 | //logger.info(`Returning [${str}] => [${ret}] with score of ${minLevenshtein} (correction factor: ${(minLevenshtein / str.length)}`); 66 | return ret; 67 | } 68 | 69 | } 70 | 71 | 72 | 73 | module.exports = StringMatcher; -------------------------------------------------------------------------------- /modules/XPTracker.js: -------------------------------------------------------------------------------- 1 | const logger = require("./Log").getLogger(__filename); 2 | const Constants = require("./Constants"); 3 | const moment = require('moment'); 4 | 5 | var maxXP = false; 6 | 7 | function isMaxXP() { 8 | return maxXP; 9 | } 10 | 11 | async function logXP(timestamp, currXP) { 12 | if(maxXP) { 13 | return; 14 | } 15 | var DB = require('./DB').getDB(); 16 | var prevXP = await getPrevXP(DB); 17 | if(prevXP !== currXP) { 18 | logger.info(`XP update ${timestamp}: ${prevXP} -> ${currXP}`); 19 | DB.run("insert into xp(timestamp, xp) values(?, ?)", [timestamp, currXP], (err) => { 20 | if(err) { 21 | logger.info(`Error inserting xp (${currXP} for ${timestamp}): ${err}`); 22 | } 23 | }); 24 | } 25 | } 26 | 27 | function getPrevXP(DB) { 28 | return new Promise((resolve, reject) => { 29 | DB.get("select xp from xp order by timestamp desc limit 1", (err, row)=> { 30 | if(err) { 31 | logger.info(`Error getting previous XP: ${err}`); 32 | } 33 | if(row) { 34 | if(row.xp === Constants.MAX_XP) { 35 | logger.info(`Max XP ${row.xp} reached, XP will now be ignored`); 36 | maxXP = true; 37 | } 38 | resolve(row.xp); 39 | } else { 40 | resolve(0); 41 | } 42 | }); 43 | }); 44 | } 45 | 46 | module.exports.logXP = logXP; 47 | module.exports.isMaxXP = isMaxXP; -------------------------------------------------------------------------------- /modules/electron-capture/src/main.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const mergeImages = require('merge-img'); 3 | const { BrowserWindow, ipcMain, app } = require('electron') 4 | 5 | let tempDir = app.getPath('userData') + '/.temp_capture', canvas = null, contentSize = null, captureTimes = 1 6 | let targetWindow = null, callback = null, options = {} 7 | 8 | ipcMain.on('start-capture', function(events){ 9 | targetWindow.webContents.send('get-content-size') 10 | }) 11 | ipcMain.on('return-content-size', function (events, size) { 12 | contentSize = size 13 | captureTimes = Math.ceil(contentSize.height/contentSize.windowHeight) 14 | targetWindow.webContents.send('move-page-to', 1) 15 | }) 16 | ipcMain.on('return-move-page', function (events, page) { 17 | let options = { 18 | x: 0, 19 | y: 0, 20 | width: contentSize.windowWidth, 21 | height: contentSize.windowHeight 22 | } 23 | if (page === captureTimes) { 24 | options.height = contentSize.height - ((captureTimes - 1) * contentSize.windowHeight) 25 | options.y = contentSize.windowHeight - options.height 26 | } 27 | targetWindow.capturePage(options).then( (image) => { 28 | if (!fsExistsSync(tempDir)) { 29 | fs.mkdirSync(tempDir) 30 | } 31 | fs.writeFile(tempDir + '/' + page + '.png', image.toPNG(), function(err){ 32 | if (page !== captureTimes) { 33 | targetWindow.webContents.send('move-page-to', page + 1) 34 | } else { 35 | targetWindow.webContents.send('done-capturing') 36 | flattenPNG() 37 | } 38 | }) 39 | }) 40 | }) 41 | 42 | function flattenPNG () { 43 | let fileNames = [] 44 | for (var i = 1 ; i <= captureTimes; i++) { 45 | fileNames.push(tempDir + '/' + i + '.png') 46 | } 47 | mergeImages(fileNames, {direction: true}).then(img => { 48 | for(var i = 0; i < fileNames.length; i++) { 49 | fs.unlinkSync(fileNames[i]); 50 | } 51 | img.crop(0, 0, img.bitmap.width - contentSize.scrollBarWidth, img.bitmap.height); 52 | callback(img); 53 | }) 54 | 55 | } 56 | 57 | function fsExistsSync(path) { 58 | try { 59 | fs.accessSync(path, fs.F_OK); 60 | } catch (e) { 61 | return false; 62 | } 63 | return true; 64 | } 65 | 66 | BrowserWindow.prototype.captureFullPage = function(_callback, _options){ 67 | targetWindow = this 68 | callback = _callback 69 | options = _options || {} 70 | canvas = null 71 | this.webContents.executeJavaScript(` 72 | var ipcRender = require('electron').ipcRenderer; 73 | ipcRender.send('start-capture'); 74 | `) 75 | } -------------------------------------------------------------------------------- /modules/electron-capture/src/preload.js: -------------------------------------------------------------------------------- 1 | const ipcRender = require('electron').ipcRenderer; 2 | 3 | //console.log("preloading"); 4 | 5 | ipcRender.on('get-content-size', function() { 6 | var height = Math.max( document.body.scrollHeight, document.body.offsetHeight, 7 | document.documentElement.clientHeight, document.documentElement.scrollHeight, document.documentElement.offsetHeight ); 8 | ipcRender.send('return-content-size', { 9 | width: window.innerWidth, 10 | height: height, 11 | windowHeight: window.innerHeight, 12 | windowWidth: window.innerWidth, 13 | scrollBarWidth: window.innerWidth - document.body.clientWidth 14 | }); 15 | }); 16 | 17 | ipcRender.on('move-page-to', function(events, page) { 18 | window.scrollTo(0, window.innerHeight * (page - 1) ) 19 | setTimeout(function() { 20 | ipcRender.send('return-move-page', page); 21 | }, 100) 22 | }); 23 | -------------------------------------------------------------------------------- /modules/settings.js: -------------------------------------------------------------------------------- 1 | const logger = require("./Log").getLogger(__filename); 2 | const path = require('path'); 3 | 4 | function get() { 5 | var app = require('electron').app || require('electron').remote.app; 6 | var settings = null; 7 | try { 8 | settings = require(path.join(app.getPath("userData"), "settings.json")); 9 | } catch (err) { 10 | logger.info(err); 11 | logger.info("Unable to load settings.json"); 12 | // do nothing if file doesn't exist 13 | } 14 | return settings; 15 | } 16 | 17 | function set(key, value) { 18 | var app = require('electron').app || require('electron').remote.app; 19 | var fs = require('fs'); 20 | var settingsPath = path.join(app.getPath("userData"), "settings.json"); 21 | if(fs.existsSync(settingsPath)) { 22 | var settings = require(settingsPath); 23 | settings[key] = value; 24 | var tempFilePath = path.join(app.getPath("userData"), "settings.json.bak"); 25 | fs.writeFile(tempFilePath, JSON.stringify(settings), (err) => { 26 | if(err) { 27 | logger.info("Error writing temp settings file: " + err.message); 28 | } else { 29 | logger.info(`Renaming ${settingsPath}`); 30 | fs.rename(tempFilePath, settingsPath, (err2) => { 31 | if(err2) { 32 | logger.info("Error copying temp settings file: " + err2.message); 33 | } else { 34 | if(key !== "mainWindowBounds") { 35 | logger.info(`Set "${key}" to ${JSON.stringify(value)}`); 36 | } 37 | } 38 | }); 39 | } 40 | }); 41 | } 42 | } 43 | 44 | module.exports.get = get; 45 | module.exports.set = set; -------------------------------------------------------------------------------- /overlay.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 57 | 58 | 80 | 81 | 82 | 83 |
 
84 |
85 | 86 | 87 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exile-diary", 3 | "version": "0.3.4", 4 | "main": "main.js", 5 | "build": { 6 | "appId": "com.pathofexile.exilediary", 7 | "productName": "Exile Diary", 8 | "win": { 9 | "target": "nsis", 10 | "artifactName": "exile-diary-setup-${version}.${ext}", 11 | "icon": "./res/img/icons/win/ExileDiary.ico", 12 | "extraResources": [ 13 | "eng.traineddata" 14 | ] 15 | }, 16 | "nsis": { 17 | "oneClick": "false", 18 | "allowElevation": "false", 19 | "allowToChangeInstallationDirectory": "true", 20 | "installerIcon": "./res/img/icons/win/ExileDiary.ico", 21 | "uninstallerIcon": "./res/img/icons/win/ExileDiary.ico", 22 | "installerHeaderIcon": "./res/img/icons/win/ExileDiary.ico" 23 | }, 24 | "linux": { 25 | "target": "deb", 26 | "executableName": "Exile-Diary", 27 | "icon": "./res/img/icons/png", 28 | "category": "Utility", 29 | "maintainer": "briansd9" 30 | } 31 | }, 32 | "scripts": { 33 | "start": "electron .", 34 | "postinstall": "install-app-deps", 35 | "pack": "electron-builder --dir", 36 | "dist": "electron-builder" 37 | }, 38 | "keywords": [ 39 | "util", 40 | "functional", 41 | "server", 42 | "client", 43 | "browser" 44 | ], 45 | "author": "joshi", 46 | "repository": { 47 | "type": "git", 48 | "url": "https://github.com/briansd9/exile-diary.git" 49 | }, 50 | "contributors": [], 51 | "dependencies": { 52 | "active-win": "^5.1.3", 53 | "chokidar": "^3.5.1", 54 | "color-convert": "^1.9.3", 55 | "electron-is-dev": "^1.2.0", 56 | "electron-updater": "^4.3.8", 57 | "fast-equals": "^2.0.0", 58 | "imgur": "^0.3.2", 59 | "jimp": "^0.4.0", 60 | "js-levenshtein": "^1.1.6", 61 | "merge-img": "^2.1.3", 62 | "moment": "^2.29.1", 63 | "moment-duration-format": "^2.3.2", 64 | "nodejs-tail": "^1.1.1", 65 | "opn": "^5.5.0", 66 | "pastebin-js": "^1.0.6", 67 | "ps-list": "^7.2.0", 68 | "request": "^2.88.2", 69 | "sqlite3": "^4.2.0", 70 | "tesseract.js": "^1.0.19", 71 | "winston": "^3.3.3", 72 | "xlsx": "^0.14.5" 73 | }, 74 | "devDependencies": { 75 | "electron": "^7.3.3", 76 | "electron-builder": "^20.44.4" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /res/Fontin-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/Fontin-Regular.ttf -------------------------------------------------------------------------------- /res/Fontin-SmallCaps.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/Fontin-SmallCaps.ttf -------------------------------------------------------------------------------- /res/Fontin_0.tga: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/Fontin_0.tga -------------------------------------------------------------------------------- /res/data/atlasRegions.json: -------------------------------------------------------------------------------- 1 | { 2 | "atlasRegions" : { 3 | "Academy" : "Valdo's Rest", 4 | "Acid Caverns" : "Lex Proxima", 5 | "Acton's Nightmare" : "Tirn's End", 6 | "Alleyways" : "New Vastir", 7 | "Ancient City" : "Valdo's Rest", 8 | "Arachnid Nest" : "Lex Ejoris", 9 | "Arachnid Tomb" : "Lex Proxima", 10 | "Arcade" : "Haewark Hamlet", 11 | "Arena" : "Valdo's Rest", 12 | "Arid Lake" : "Tirn's End", 13 | "Armoury" : "Lex Proxima", 14 | "Arsenal" : "Valdo's Rest", 15 | "Ashen Wood" : "Lex Proxima", 16 | "Atoll" : "Tirn's End", 17 | "Barrows" : "New Vastir", 18 | "Basilica" : "Valdo's Rest", 19 | "Bazaar" : "Lira Arthain", 20 | "Beach" : "Glennach Cairns", 21 | "Belfry" : "Haewark Hamlet", 22 | "Bog" : "Lex Proxima", 23 | "Bone Crypt" : "New Vastir", 24 | "Bramble Valley" : "Lex Ejoris", 25 | "Burial Chambers" : "New Vastir", 26 | "Caer Blaidd, Wolfpack's Den" : "Lex Proxima", 27 | "Cage" : "Lex Proxima", 28 | "Caldera" : "Glennach Cairns", 29 | "Canyon" : "Glennach Cairns", 30 | "Carcass" : "Glennach Cairns", 31 | "Castle Ruins" : "Glennach Cairns", 32 | "Cells" : "Haewark Hamlet", 33 | "Cemetery" : "Tirn's End", 34 | "Channel" : "Valdo's Rest", 35 | "Chateau" : "Haewark Hamlet", 36 | "City Square" : "Lex Ejoris", 37 | "Cold River" : "Tirn's End", 38 | "Colonnade" : "Lex Proxima", 39 | "Colosseum" : "Glennach Cairns", 40 | "Conservatory" : "Valdo's Rest", 41 | "Coral Ruins" : "Lira Arthain", 42 | "Core" : "Lira Arthain", 43 | "Courthouse" : "Glennach Cairns", 44 | "Courtyard" : "Valdo's Rest", 45 | "Coves" : "Lex Proxima", 46 | "Crater" : "Tirn's End", 47 | "Crimson Temple" : "Lira Arthain", 48 | "Crimson Township" : "Tirn's End", 49 | "Crystal Ore" : "New Vastir", 50 | "Cursed Crypt" : "Tirn's End", 51 | "Dark Forest" : "Tirn's End", 52 | "Death and Taxes" : "Tirn's End", 53 | "Defiled Cathedral" : "Haewark Hamlet", 54 | "Desert Spring" : "Haewark Hamlet", 55 | "Desert" : "Haewark Hamlet", 56 | "Dig" : "Lex Proxima", 57 | "Doryani's Machinarium" : "Lex Proxima", 58 | "Dry Sea" : "Glennach Cairns", 59 | "Dunes" : "Lex Ejoris", 60 | "Dungeon" : "New Vastir", 61 | "Estuary" : "Valdo's Rest", 62 | "Excavation" : "Valdo's Rest", 63 | "Factory" : "Haewark Hamlet", 64 | "Fields" : "Glennach Cairns", 65 | "Flooded Mine" : "Tirn's End", 66 | "Forbidden Woods" : "Glennach Cairns", 67 | "Forking River" : "Valdo's Rest", 68 | "Foundry" : "Lex Ejoris", 69 | "Frozen Cabins" : "Lex Proxima", 70 | "Fungal Hollow" : "Lex Ejoris", 71 | "Gardens" : "Lira Arthain", 72 | "Geode" : "Lex Proxima", 73 | "Ghetto" : "Glennach Cairns", 74 | "Glacier" : "Lex Proxima", 75 | "Grave Trough" : "New Vastir", 76 | "Graveyard" : "Glennach Cairns", 77 | "Grotto" : "Valdo's Rest", 78 | "Hallowed Ground" : "Tirn's End", 79 | "Haunted Mansion" : "Lex Proxima", 80 | "Iceberg" : "Lex Proxima", 81 | "Infested Valley" : "Glennach Cairns", 82 | "Ivory Temple" : "Lex Ejoris", 83 | "Jungle Valley" : "Glennach Cairns", 84 | "Laboratory" : "Lira Arthain", 85 | "Lair" : "Valdo's Rest", 86 | "Lava Chamber" : "Lex Ejoris", 87 | "Lava Lake" : "Lira Arthain", 88 | "Leyline" : "Lex Proxima", 89 | "Lighthouse" : "New Vastir", 90 | "Lookout" : "Tirn's End", 91 | "Maelström of Chaos" : "Tirn's End", 92 | "Malformation" : "Lex Proxima", 93 | "Mao Kun" : "Lex Proxima", 94 | "Marshes" : "Glennach Cairns", 95 | "Mausoleum" : "Glennach Cairns", 96 | "Maze" : "Lex Proxima", 97 | "Mesa" : "Lex Proxima", 98 | "Mineral Pools" : "Haewark Hamlet", 99 | "Moon Temple" : "Haewark Hamlet", 100 | "Mud Geyser" : "New Vastir", 101 | "Museum" : "New Vastir", 102 | "Necropolis" : "Tirn's End", 103 | "Oba's Cursed Trove" : "Valdo's Rest", 104 | "Olmec's Sanctum" : "New Vastir", 105 | "Orchard" : "Glennach Cairns", 106 | "Overgrown Ruin" : "Lex Ejoris", 107 | "Overgrown Shrine" : "Tirn's End", 108 | "Palace" : "New Vastir", 109 | "Park" : "Tirn's End", 110 | "Pen" : "Haewark Hamlet", 111 | "Peninsula" : "Glennach Cairns", 112 | "Perandus Manor" : "Haewark Hamlet", 113 | "Phantasmagoria" : "Lex Ejoris", 114 | "Pier" : "Lex Ejoris", 115 | "Pillars of Arun" : "Lex Ejoris", 116 | "Pit" : "Lex Ejoris", 117 | "Plateau" : "Lira Arthain", 118 | "Plaza" : "Lex Proxima", 119 | "Poorjoy's Asylum" : "Lex Proxima", 120 | "Port" : "Valdo's Rest", 121 | "Precinct" : "Glennach Cairns", 122 | "Primordial Blocks" : "Haewark Hamlet", 123 | "Primordial Pool" : "Haewark Hamlet", 124 | "Promenade" : "Lex Ejoris", 125 | "Racecourse" : "Lex Proxima", 126 | "Ramparts" : "Lira Arthain", 127 | "Reef" : "New Vastir", 128 | "Relic Chambers" : "Haewark Hamlet", 129 | "Residence" : "New Vastir", 130 | "Scriptorium" : "Lira Arthain", 131 | "Sepulchre" : "Lira Arthain", 132 | "Shipyard" : "New Vastir", 133 | "Shore" : "Lex Proxima", 134 | "Shrine" : "Tirn's End", 135 | "Siege" : "Lex Ejoris", 136 | "Silo" : "Lex Proxima", 137 | "Spider Forest" : "Lex Ejoris", 138 | "Spider Lair" : "Lira Arthain", 139 | "Stagnation" : "Lira Arthain", 140 | "Strand" : "Glennach Cairns", 141 | "Sulphur Vents" : "Glennach Cairns", 142 | "Summit" : "Glennach Cairns", 143 | "Sunken City" : "Lira Arthain", 144 | "Temple" : "Lex Proxima", 145 | "Terrace" : "Tirn's End", 146 | "The Coward's Trial" : "Tirn's End", 147 | "The Putrid Cloister" : "New Vastir", 148 | "The Twilight Temple" : "Haewark Hamlet", 149 | "The Vinktar Square" : "Valdo's Rest", 150 | "Thicket" : "Lira Arthain", 151 | "Tower" : "Haewark Hamlet", 152 | "Toxic Sewer" : "Haewark Hamlet", 153 | "Tropical Island" : "Tirn's End", 154 | "Underground River" : "Lex Proxima", 155 | "Underground Sea" : "Valdo's Rest", 156 | "Vaal Pyramid" : "Lex Proxima", 157 | "Vaal Temple" : "Lex Proxima", 158 | "Vault" : "Lex Ejoris", 159 | "Vaults of Atziri" : "Lex Proxima", 160 | "Villa" : "Valdo's Rest", 161 | "Volcano" : "Glennach Cairns", 162 | "Waste Pool" : "Valdo's Rest", 163 | "Wasteland" : "Lira Arthain", 164 | "Waterways" : "Tirn's End", 165 | "Whakawairua Tuahu" : "Glennach Cairns", 166 | "Wharf" : "Valdo's Rest" 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /res/img/!.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/!.png -------------------------------------------------------------------------------- /res/img/AwakenerOrb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/AwakenerOrb.png -------------------------------------------------------------------------------- /res/img/CrusaderOrb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/CrusaderOrb.png -------------------------------------------------------------------------------- /res/img/HunterOrb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/HunterOrb.png -------------------------------------------------------------------------------- /res/img/RedeemerOrb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/RedeemerOrb.png -------------------------------------------------------------------------------- /res/img/WarlordOrb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/WarlordOrb.png -------------------------------------------------------------------------------- /res/img/atlasicons/GlennachCairns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/atlasicons/GlennachCairns.png -------------------------------------------------------------------------------- /res/img/atlasicons/HaewarkHamlet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/atlasicons/HaewarkHamlet.png -------------------------------------------------------------------------------- /res/img/atlasicons/LexEjoris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/atlasicons/LexEjoris.png -------------------------------------------------------------------------------- /res/img/atlasicons/LexProxima.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/atlasicons/LexProxima.png -------------------------------------------------------------------------------- /res/img/atlasicons/LiraArthain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/atlasicons/LiraArthain.png -------------------------------------------------------------------------------- /res/img/atlasicons/NewVastir.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/atlasicons/NewVastir.png -------------------------------------------------------------------------------- /res/img/atlasicons/TirnsEnd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/atlasicons/TirnsEnd.png -------------------------------------------------------------------------------- /res/img/atlasicons/ValdosRest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/atlasicons/ValdosRest.png -------------------------------------------------------------------------------- /res/img/blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/blank.png -------------------------------------------------------------------------------- /res/img/c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/c.png -------------------------------------------------------------------------------- /res/img/crusader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/crusader.png -------------------------------------------------------------------------------- /res/img/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/delete.png -------------------------------------------------------------------------------- /res/img/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/discord.png -------------------------------------------------------------------------------- /res/img/elder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/elder.png -------------------------------------------------------------------------------- /res/img/encountericons/abnormaldisconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/abnormaldisconnect.png -------------------------------------------------------------------------------- /res/img/encountericons/abyss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/abyss.png -------------------------------------------------------------------------------- /res/img/encountericons/alluringabyss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/alluringabyss.png -------------------------------------------------------------------------------- /res/img/encountericons/altered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/altered.png -------------------------------------------------------------------------------- /res/img/encountericons/alva.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/alva.png -------------------------------------------------------------------------------- /res/img/encountericons/argus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/argus.png -------------------------------------------------------------------------------- /res/img/encountericons/augmented.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/augmented.png -------------------------------------------------------------------------------- /res/img/encountericons/blight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/blight.png -------------------------------------------------------------------------------- /res/img/encountericons/blightedmap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/blightedmap.png -------------------------------------------------------------------------------- /res/img/encountericons/brain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/brain.png -------------------------------------------------------------------------------- /res/img/encountericons/cassia.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/cassia.png -------------------------------------------------------------------------------- /res/img/encountericons/chimera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/chimera.png -------------------------------------------------------------------------------- /res/img/encountericons/constrictor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/constrictor.png -------------------------------------------------------------------------------- /res/img/encountericons/cortex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/cortex.png -------------------------------------------------------------------------------- /res/img/encountericons/crusader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/crusader.png -------------------------------------------------------------------------------- /res/img/encountericons/darkshrine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/darkshrine.png -------------------------------------------------------------------------------- /res/img/encountericons/deaths.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/deaths.png -------------------------------------------------------------------------------- /res/img/encountericons/delirium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/delirium.png -------------------------------------------------------------------------------- /res/img/encountericons/einhar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/einhar.png -------------------------------------------------------------------------------- /res/img/encountericons/enslaver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/enslaver.png -------------------------------------------------------------------------------- /res/img/encountericons/envoy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/envoy.png -------------------------------------------------------------------------------- /res/img/encountericons/eradicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/eradicator.png -------------------------------------------------------------------------------- /res/img/encountericons/eternal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/eternal.png -------------------------------------------------------------------------------- /res/img/encountericons/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/eye.png -------------------------------------------------------------------------------- /res/img/encountericons/grandheist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/grandheist.png -------------------------------------------------------------------------------- /res/img/encountericons/heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/heart.png -------------------------------------------------------------------------------- /res/img/encountericons/heist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/heist.png -------------------------------------------------------------------------------- /res/img/encountericons/hunter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/hunter.png -------------------------------------------------------------------------------- /res/img/encountericons/hydra.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/hydra.png -------------------------------------------------------------------------------- /res/img/encountericons/jun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/jun.png -------------------------------------------------------------------------------- /res/img/encountericons/karui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/karui.png -------------------------------------------------------------------------------- /res/img/encountericons/kd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/kd.png -------------------------------------------------------------------------------- /res/img/encountericons/kills.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/kills.png -------------------------------------------------------------------------------- /res/img/encountericons/labtrial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/labtrial.png -------------------------------------------------------------------------------- /res/img/encountericons/labyrinth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/labyrinth.png -------------------------------------------------------------------------------- /res/img/encountericons/legion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/legion.png -------------------------------------------------------------------------------- /res/img/encountericons/liver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/liver.png -------------------------------------------------------------------------------- /res/img/encountericons/lung.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/lung.png -------------------------------------------------------------------------------- /res/img/encountericons/maraketh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/maraketh.png -------------------------------------------------------------------------------- /res/img/encountericons/mastermind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/mastermind.png -------------------------------------------------------------------------------- /res/img/encountericons/maven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/maven.png -------------------------------------------------------------------------------- /res/img/encountericons/mavenarena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/mavenarena.png -------------------------------------------------------------------------------- /res/img/encountericons/mavencrucible.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/mavencrucible.png -------------------------------------------------------------------------------- /res/img/encountericons/metamorph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/metamorph.png -------------------------------------------------------------------------------- /res/img/encountericons/minotaur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/minotaur.png -------------------------------------------------------------------------------- /res/img/encountericons/niko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/niko.png -------------------------------------------------------------------------------- /res/img/encountericons/oshabi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/oshabi.png -------------------------------------------------------------------------------- /res/img/encountericons/phoenix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/phoenix.png -------------------------------------------------------------------------------- /res/img/encountericons/purifier.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/purifier.png -------------------------------------------------------------------------------- /res/img/encountericons/redeemer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/redeemer.png -------------------------------------------------------------------------------- /res/img/encountericons/rewritten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/rewritten.png -------------------------------------------------------------------------------- /res/img/encountericons/shaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/shaper.png -------------------------------------------------------------------------------- /res/img/encountericons/shrine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/shrine.png -------------------------------------------------------------------------------- /res/img/encountericons/simulacrum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/simulacrum.png -------------------------------------------------------------------------------- /res/img/encountericons/sirus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/sirus.png -------------------------------------------------------------------------------- /res/img/encountericons/templar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/templar.png -------------------------------------------------------------------------------- /res/img/encountericons/twisted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/twisted.png -------------------------------------------------------------------------------- /res/img/encountericons/ultimatum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/ultimatum.png -------------------------------------------------------------------------------- /res/img/encountericons/vaal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/vaal.png -------------------------------------------------------------------------------- /res/img/encountericons/vaalsidearea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/vaalsidearea.png -------------------------------------------------------------------------------- /res/img/encountericons/warlord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/warlord.png -------------------------------------------------------------------------------- /res/img/encountericons/words.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/words.png -------------------------------------------------------------------------------- /res/img/encountericons/zana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/encountericons/zana.png -------------------------------------------------------------------------------- /res/img/ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ex.png -------------------------------------------------------------------------------- /res/img/fractured.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/fractured.png -------------------------------------------------------------------------------- /res/img/geartypeicons/amulet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/geartypeicons/amulet.png -------------------------------------------------------------------------------- /res/img/geartypeicons/belt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/geartypeicons/belt.png -------------------------------------------------------------------------------- /res/img/geartypeicons/bodyarmour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/geartypeicons/bodyarmour.png -------------------------------------------------------------------------------- /res/img/geartypeicons/boots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/geartypeicons/boots.png -------------------------------------------------------------------------------- /res/img/geartypeicons/flask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/geartypeicons/flask.png -------------------------------------------------------------------------------- /res/img/geartypeicons/gloves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/geartypeicons/gloves.png -------------------------------------------------------------------------------- /res/img/geartypeicons/helm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/geartypeicons/helm.png -------------------------------------------------------------------------------- /res/img/geartypeicons/jewel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/geartypeicons/jewel.png -------------------------------------------------------------------------------- /res/img/geartypeicons/ring.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/geartypeicons/ring.png -------------------------------------------------------------------------------- /res/img/geartypeicons/weapon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/geartypeicons/weapon.png -------------------------------------------------------------------------------- /res/img/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/github.png -------------------------------------------------------------------------------- /res/img/howa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/howa.png -------------------------------------------------------------------------------- /res/img/hunter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/hunter.png -------------------------------------------------------------------------------- /res/img/icons/mac/ExileDiary.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/mac/ExileDiary.icns -------------------------------------------------------------------------------- /res/img/icons/png/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/png/128x128.png -------------------------------------------------------------------------------- /res/img/icons/png/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/png/16x16.png -------------------------------------------------------------------------------- /res/img/icons/png/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/png/24x24.png -------------------------------------------------------------------------------- /res/img/icons/png/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/png/256x256.png -------------------------------------------------------------------------------- /res/img/icons/png/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/png/32x32.png -------------------------------------------------------------------------------- /res/img/icons/png/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/png/48x48.png -------------------------------------------------------------------------------- /res/img/icons/png/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/png/512x512.png -------------------------------------------------------------------------------- /res/img/icons/png/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/png/64x64.png -------------------------------------------------------------------------------- /res/img/icons/png/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/png/96x96.png -------------------------------------------------------------------------------- /res/img/icons/win/ExileDiary.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/icons/win/ExileDiary.ico -------------------------------------------------------------------------------- /res/img/incursion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/incursion.png -------------------------------------------------------------------------------- /res/img/itemicons/ElderBackground1x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ElderBackground1x1.png -------------------------------------------------------------------------------- /res/img/itemicons/ElderBackground1x3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ElderBackground1x3.png -------------------------------------------------------------------------------- /res/img/itemicons/ElderBackground1x4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ElderBackground1x4.png -------------------------------------------------------------------------------- /res/img/itemicons/ElderBackground2x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ElderBackground2x1.png -------------------------------------------------------------------------------- /res/img/itemicons/ElderBackground2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ElderBackground2x2.png -------------------------------------------------------------------------------- /res/img/itemicons/ElderBackground2x3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ElderBackground2x3.png -------------------------------------------------------------------------------- /res/img/itemicons/ElderBackground2x4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ElderBackground2x4.png -------------------------------------------------------------------------------- /res/img/itemicons/ShaperBackground1x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ShaperBackground1x1.png -------------------------------------------------------------------------------- /res/img/itemicons/ShaperBackground1x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ShaperBackground1x2.png -------------------------------------------------------------------------------- /res/img/itemicons/ShaperBackground1x3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ShaperBackground1x3.png -------------------------------------------------------------------------------- /res/img/itemicons/ShaperBackground1x4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ShaperBackground1x4.png -------------------------------------------------------------------------------- /res/img/itemicons/ShaperBackground2x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ShaperBackground2x1.png -------------------------------------------------------------------------------- /res/img/itemicons/ShaperBackground2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ShaperBackground2x2.png -------------------------------------------------------------------------------- /res/img/itemicons/ShaperBackground2x3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ShaperBackground2x3.png -------------------------------------------------------------------------------- /res/img/itemicons/ShaperBackground2x4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/ShaperBackground2x4.png -------------------------------------------------------------------------------- /res/img/itemicons/crusader-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/crusader-symbol.png -------------------------------------------------------------------------------- /res/img/itemicons/divination-card-divider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/divination-card-divider.png -------------------------------------------------------------------------------- /res/img/itemicons/divination-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/divination-card.png -------------------------------------------------------------------------------- /res/img/itemicons/elder-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/elder-symbol.png -------------------------------------------------------------------------------- /res/img/itemicons/experience-bar-fill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/experience-bar-fill.png -------------------------------------------------------------------------------- /res/img/itemicons/experience-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/experience-bar.png -------------------------------------------------------------------------------- /res/img/itemicons/fractured-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/fractured-symbol.png -------------------------------------------------------------------------------- /res/img/itemicons/glyph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/glyph.png -------------------------------------------------------------------------------- /res/img/itemicons/header-currency-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-currency-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-currency-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-currency-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-currency-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-currency-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-double-rare-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-double-rare-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-double-rare-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-double-rare-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-double-rare-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-double-rare-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-double-relic-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-double-relic-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-double-relic-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-double-relic-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-double-relic-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-double-relic-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-double-unique-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-double-unique-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-double-unique-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-double-unique-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-double-unique-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-double-unique-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-gem-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-gem-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-gem-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-gem-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-gem-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-gem-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-magic-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-magic-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-magic-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-magic-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-magic-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-magic-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-normal-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-normal-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-normal-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-normal-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-normal-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-normal-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-prophecy-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-prophecy-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-prophecy-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-prophecy-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-prophecy-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-prophecy-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-quest-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-quest-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-quest-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-quest-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-quest-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-quest-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-rare-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-rare-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-rare-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-rare-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-rare-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-rare-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-relic-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-relic-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-relic-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-relic-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-relic-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-relic-right.png -------------------------------------------------------------------------------- /res/img/itemicons/header-unique-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-unique-left.png -------------------------------------------------------------------------------- /res/img/itemicons/header-unique-middle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-unique-middle.png -------------------------------------------------------------------------------- /res/img/itemicons/header-unique-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/header-unique-right.png -------------------------------------------------------------------------------- /res/img/itemicons/hunter-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/hunter-symbol.png -------------------------------------------------------------------------------- /res/img/itemicons/hybrid-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/hybrid-title.png -------------------------------------------------------------------------------- /res/img/itemicons/redeemer-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/redeemer-symbol.png -------------------------------------------------------------------------------- /res/img/itemicons/seperator-currency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/seperator-currency.png -------------------------------------------------------------------------------- /res/img/itemicons/seperator-gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/seperator-gem.png -------------------------------------------------------------------------------- /res/img/itemicons/seperator-magic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/seperator-magic.png -------------------------------------------------------------------------------- /res/img/itemicons/seperator-normal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/seperator-normal.png -------------------------------------------------------------------------------- /res/img/itemicons/seperator-prophecy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/seperator-prophecy.png -------------------------------------------------------------------------------- /res/img/itemicons/seperator-quest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/seperator-quest.png -------------------------------------------------------------------------------- /res/img/itemicons/seperator-rare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/seperator-rare.png -------------------------------------------------------------------------------- /res/img/itemicons/seperator-relic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/seperator-relic.png -------------------------------------------------------------------------------- /res/img/itemicons/seperator-unique.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/seperator-unique.png -------------------------------------------------------------------------------- /res/img/itemicons/shaper-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/shaper-symbol.png -------------------------------------------------------------------------------- /res/img/itemicons/socket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/socket.png -------------------------------------------------------------------------------- /res/img/itemicons/synthetic-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/synthetic-symbol.png -------------------------------------------------------------------------------- /res/img/itemicons/vaal-title.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/vaal-title.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled-symbol.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/prefix_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/prefix_01.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/prefix_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/prefix_02.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/prefix_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/prefix_03.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/prefix_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/prefix_04.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/prefix_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/prefix_05.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/prefix_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/prefix_06.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/suffix_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/suffix_01.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/suffix_02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/suffix_02.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/suffix_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/suffix_03.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/suffix_04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/suffix_04.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/suffix_05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/suffix_05.png -------------------------------------------------------------------------------- /res/img/itemicons/veiled/suffix_06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/veiled/suffix_06.png -------------------------------------------------------------------------------- /res/img/itemicons/warlord-symbol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemicons/warlord-symbol.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/catalyst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/catalyst.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/currency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/currency.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/delve.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/delve.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/divcard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/divcard.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/essence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/essence.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/fragment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/fragment.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/gem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/gem.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/incubator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/incubator.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/map.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/nonunique.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/nonunique.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/oil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/oil.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/prophecy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/prophecy.png -------------------------------------------------------------------------------- /res/img/itemtypeicons/unique.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/itemtypeicons/unique.png -------------------------------------------------------------------------------- /res/img/kofi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/kofi.png -------------------------------------------------------------------------------- /res/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/loading.gif -------------------------------------------------------------------------------- /res/img/loadingcomplete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/loadingcomplete.png -------------------------------------------------------------------------------- /res/img/mirror.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/mirror.png -------------------------------------------------------------------------------- /res/img/novalue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/novalue.png -------------------------------------------------------------------------------- /res/img/patreon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/patreon.png -------------------------------------------------------------------------------- /res/img/q.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/q.png -------------------------------------------------------------------------------- /res/img/redBeast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/redBeast.png -------------------------------------------------------------------------------- /res/img/redeemer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/redeemer.png -------------------------------------------------------------------------------- /res/img/shaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shaper.png -------------------------------------------------------------------------------- /res/img/shrineicons/Acceleration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Acceleration.png -------------------------------------------------------------------------------- /res/img/shrineicons/Brutal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Brutal.png -------------------------------------------------------------------------------- /res/img/shrineicons/Diamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Diamond.png -------------------------------------------------------------------------------- /res/img/shrineicons/Divine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Divine.png -------------------------------------------------------------------------------- /res/img/shrineicons/Echoing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Echoing.png -------------------------------------------------------------------------------- /res/img/shrineicons/Freezing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Freezing.png -------------------------------------------------------------------------------- /res/img/shrineicons/Gloom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Gloom.png -------------------------------------------------------------------------------- /res/img/shrineicons/Impenetrable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Impenetrable.png -------------------------------------------------------------------------------- /res/img/shrineicons/Lightning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Lightning.png -------------------------------------------------------------------------------- /res/img/shrineicons/Massive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Massive.png -------------------------------------------------------------------------------- /res/img/shrineicons/Replenishing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Replenishing.png -------------------------------------------------------------------------------- /res/img/shrineicons/Resistance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Resistance.png -------------------------------------------------------------------------------- /res/img/shrineicons/Resonating.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Resonating.png -------------------------------------------------------------------------------- /res/img/shrineicons/Shrouded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Shrouded.png -------------------------------------------------------------------------------- /res/img/shrineicons/Skeletal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/shrineicons/Skeletal.png -------------------------------------------------------------------------------- /res/img/stash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/stash.png -------------------------------------------------------------------------------- /res/img/sulphite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/sulphite.png -------------------------------------------------------------------------------- /res/img/synthesised.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/synthesised.png -------------------------------------------------------------------------------- /res/img/tabicons/BlightStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/BlightStash.png -------------------------------------------------------------------------------- /res/img/tabicons/CurrencyStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/CurrencyStash.png -------------------------------------------------------------------------------- /res/img/tabicons/DeliriumStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/DeliriumStash.png -------------------------------------------------------------------------------- /res/img/tabicons/DelveStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/DelveStash.png -------------------------------------------------------------------------------- /res/img/tabicons/DivinationCardStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/DivinationCardStash.png -------------------------------------------------------------------------------- /res/img/tabicons/EssenceStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/EssenceStash.png -------------------------------------------------------------------------------- /res/img/tabicons/FragmentStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/FragmentStash.png -------------------------------------------------------------------------------- /res/img/tabicons/MapStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/MapStash.png -------------------------------------------------------------------------------- /res/img/tabicons/MetamorphStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/MetamorphStash.png -------------------------------------------------------------------------------- /res/img/tabicons/NormalStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/NormalStash.png -------------------------------------------------------------------------------- /res/img/tabicons/PremiumStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/PremiumStash.png -------------------------------------------------------------------------------- /res/img/tabicons/QuadStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/QuadStash.png -------------------------------------------------------------------------------- /res/img/tabicons/UniqueStash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/tabicons/UniqueStash.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/AilmentandCurseReflection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/AilmentandCurseReflection.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/AilmentandCurseReflectionHinderingFlasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/AilmentandCurseReflectionHinderingFlasks.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/BlisteringCold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/BlisteringCold.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/BonusChaosDamage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/BonusChaosDamage.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/BuffsExpireFaster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/BuffsExpireFaster.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/ChokingMiasma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/ChokingMiasma.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/EscalatingDamageTaken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/EscalatingDamageTaken.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/EscalatingMonsterSpeed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/EscalatingMonsterSpeed.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/HinderingFlasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/HinderingFlasks.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/LessCooldownrecovery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/LessCooldownrecovery.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/LessenedReach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/LessenedReach.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/LightningDamageFromManaCosts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/LightningDamageFromManaCosts.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/LimitedArena.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/LimitedArena.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/LimitedFlasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/LimitedFlasks.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/OccasionalImpotence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/OccasionalImpotence.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/RagingDead.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/RagingDead.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/RandomProjectiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/RandomProjectiles.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/RazorDance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/RazorDance.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/ReducedRecovery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/ReducedRecovery.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/RestlessGround.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/RestlessGround.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/Ruin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/Ruin.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/SiphonedCharges.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/SiphonedCharges.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/StalkingRuin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/StalkingRuin.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/StormcallerRunes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/StormcallerRunes.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/TotemofCostlyMight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/TotemofCostlyMight.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/TotemofCostlyPotency.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/TotemofCostlyPotency.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/TreacherousAuras.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/TreacherousAuras.png -------------------------------------------------------------------------------- /res/img/ultimatumicons/UnluckyCriticals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/ultimatumicons/UnluckyCriticals.png -------------------------------------------------------------------------------- /res/img/unknownBeast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/unknownBeast.png -------------------------------------------------------------------------------- /res/img/veiled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/veiled.png -------------------------------------------------------------------------------- /res/img/warlord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/warlord.png -------------------------------------------------------------------------------- /res/img/weaponswap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/weaponswap.png -------------------------------------------------------------------------------- /res/img/yellowBeast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/briansd9/exile-diary/8cb9bfb66a0d67700364c89fa44b49a7341e393e/res/img/yellowBeast.png -------------------------------------------------------------------------------- /res/maptable.js: -------------------------------------------------------------------------------- 1 | class MapTable { 2 | 3 | static DEFAULT = [ "date", "area", "level", "iiq", "iir", "packsize", "time", "xpRate", "c", "deaths", "kills" ]; 4 | static fmt = new Intl.NumberFormat(); 5 | 6 | static COLUMNS = { 7 | date: { label: "Date", f: map => moment(map.id, "YYYYMMDDHHmmss").format("YYYY-MM-DD HH:mm:ss") }, 8 | area: { label: "Area", f: map => map.name }, 9 | region: { label: "Atlas Region", f: map => map.runinfo.atlasRegion || "" }, 10 | level: { label: "Level", f: map => Utils.getMapTierString(map) }, 11 | iiq: { label: "IIQ", f: map => (map.iiq ? `+${map.iiq}%` : '') }, 12 | iir: { label: "IIR", f: map => (map.iir ? `+${map.iir}%` : '') }, 13 | packsize: { label: "Pack Size", f: map => (map.packsize ? `+${map.packsize}%` : '') }, 14 | time: { label: "Time", f: map => Utils.getRunningTime(map.firstevent, map.lastevent) }, 15 | xp: { label: "XP", f: map => this.fmt.format(map.xpgained) }, 16 | xpRate: { label: "XP/hr", f: map => map.xpgained > 0 ? Utils.getXPRate(map.xpgained, map.firstevent, map.lastevent) : "" }, 17 | c: { label: "", f: map => Number(map.gained).toFixed(2) }, 18 | cRate: { 19 | label: "/hr", 20 | f: map => Number( map.gained * 3600 / Utils.getRunningTime(map.firstevent, map.lastevent, "s", {useGrouping:false}) ).toFixed(2) 21 | }, 22 | deaths: { label: "Deaths", f: map => Utils.getDeathCount(map.deaths) }, 23 | kills: { label: "Kills", f: map => map.kills > 0 ? this.fmt.format(map.kills) : "" }, 24 | encounters: { label: "Encounters", f: map => this.getEncounters(map.runinfo) } 25 | }; 26 | 27 | static getEncounters(r) { 28 | 29 | let str = ""; 30 | if(r.maven) { 31 | str += Utils.getEncounterIcon("maven"); 32 | } 33 | if(r.elderGuardian) { 34 | let id = r.elderGuardian.replace("The ", "").toLowerCase(); 35 | str += Utils.getEncounterIcon(id); 36 | } 37 | if(r.masters) { 38 | Object.keys(r.masters).forEach(m => { 39 | if(r.masters[m].encountered) { 40 | let id = m.substr(0, m.indexOf(",")).toLowerCase(); 41 | str += Utils.getEncounterIcon(id); 42 | } 43 | }) 44 | } 45 | if(r.conqueror) { 46 | Object.keys(r.conqueror).forEach(c => { 47 | let id = c.substr(c.indexOf("the") + 4).toLowerCase(); 48 | str += Utils.getEncounterIcon(id); 49 | }) 50 | } 51 | if(r.strangeVoiceEncountered) { 52 | str += Utils.getEncounterIcon("delirium"); 53 | } 54 | if(r.blightEncounter) { 55 | str += Utils.getEncounterIcon("blight"); 56 | } 57 | if(r.blightedMap) { 58 | str += Utils.getEncounterIcon("blightedmap"); 59 | } 60 | if(r.metamorph) { 61 | str += Utils.getEncounterIcon("metamorph"); 62 | } 63 | if(r.oshabiBattle) { 64 | str += Utils.getEncounterIcon("oshabi"); 65 | } 66 | if(r.abnormalDisconnect) { 67 | str += Utils.getEncounterIcon("abnormaldisconnect"); 68 | } 69 | if(r.ultimatum) { 70 | str += Utils.getEncounterIcon("ultimatum"); 71 | } 72 | return `
${str}
`; 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /res/page-utils.js: -------------------------------------------------------------------------------- 1 | 2 | $(document).ready(() => { 3 | var e = require('electron'); 4 | var width = e.remote.getCurrentWindow().getBounds().width; 5 | setZoomFactor(Math.min(width, 1100) / 1100); 6 | }); 7 | 8 | require('electron').ipcRenderer.on("rescale", (event, f) => { 9 | setZoomFactor(f); 10 | }); 11 | 12 | function setZoomFactor(f) { 13 | require('electron').webFrame.setZoomFactor(f); 14 | } 15 | 16 | function openShell(dir) { 17 | require("electron").shell.openExternal(dir); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /res/poedit.css: -------------------------------------------------------------------------------- 1 | /* font definitions */ 2 | 3 | @font-face { 4 | font-family: FontinSmallCaps; 5 | src: url("Fontin-SmallCaps.ttf"); 6 | } 7 | 8 | /* positioning */ 9 | 10 | #header { 11 | position: absolute; 12 | margin: 0px; 13 | padding: 0px; 14 | text-align: center; 15 | 16 | left: 0px; 17 | right: 0px; 18 | top: 0px; 19 | height: 32px; 20 | 21 | background-color: yellow; 22 | } 23 | 24 | #header > a { 25 | color: red; 26 | font-family: Fontin; 27 | font-size: 24px; 28 | text-decoration: None; 29 | text-align: center; 30 | } 31 | 32 | #main { 33 | position: absolute; 34 | margin: 10px; 35 | 36 | left: 0px; 37 | right: 0px; 38 | top: 30px; 39 | bottom: 0px; 40 | } 41 | 42 | #code-area { 43 | position: absolute; 44 | margin: 2px; 45 | 46 | left: 0px; 47 | right: 50%; 48 | top: 0px; 49 | bottom: 0px; 50 | } 51 | 52 | #code-window-container { 53 | position: absolute; 54 | 55 | left: 0px; 56 | right: 0px; 57 | top: 0px; 58 | bottom: 0px; 59 | padding-bottom: 90px; 60 | } 61 | 62 | #code-window { 63 | position: relative; 64 | width: 100%; 65 | height: 100%; 66 | } 67 | 68 | #log-window { 69 | position: absolute; 70 | height: 60px; 71 | 72 | left: 0px; 73 | right: 0px; 74 | bottom: 0px; 75 | } 76 | 77 | #items-area { 78 | position: absolute; 79 | margin: 2px; 80 | 81 | left: 50%; 82 | right: 0px; 83 | top: 0px; 84 | bottom: 0px; 85 | } 86 | 87 | #items-editor { 88 | position: absolute; 89 | margin: 2px; 90 | 91 | left: 50%; 92 | right: 0px; 93 | top: 0px; 94 | bottom: 0px; 95 | 96 | display: none; /* initially hidden */ 97 | } 98 | 99 | #items-editor > p { 100 | margin: 1px 5px; 101 | } 102 | 103 | #help-window { 104 | position: absolute; 105 | margin: 2px; 106 | z-index: 0; 107 | overflow-y: scroll; 108 | 109 | left: 50%; 110 | right: 0px; 111 | top: 0px; 112 | bottom: 0px; 113 | 114 | display: none; /* initially hidden */ 115 | } 116 | 117 | #buttons { 118 | position: absolute; 119 | margin: 2px; 120 | bottom: 0px; 121 | right: 0px; 122 | z-index: 100; 123 | } 124 | 125 | #buttons > button { 126 | display: inline-block; 127 | margin: 0px; 128 | } 129 | 130 | #code-toolbar { 131 | position: absolute; 132 | height: 30px; 133 | left: 0px; 134 | right: 0px; 135 | bottom: 60px; 136 | } 137 | 138 | #colorpicker { 139 | position: absolute; 140 | height: 30px; 141 | left: 0px; 142 | right: 100px; 143 | } 144 | 145 | #download-button { 146 | position: absolute; 147 | width: 90px; 148 | right: 5px; 149 | } 150 | 151 | #RGB-color { 152 | height: 22px; 153 | margin: 2px 0px 1px 0px; 154 | } 155 | /* code editor style */ 156 | 157 | #code-window { 158 | padding: 3px; 159 | background-color: #252525; 160 | color: #ddddaa; 161 | font-weight: bold; 162 | font-family: monospace; 163 | overflow-y: scroll; 164 | } 165 | 166 | #log-window { 167 | background-color: white; 168 | border: 1px solid black; 169 | overflow-y: scroll; 170 | 171 | font-family: sans-serif; 172 | color: red; 173 | } 174 | 175 | #code-window > .code-error { 176 | color: red; 177 | } 178 | 179 | #code-window > .code-comment { 180 | color: rgb(0, 200, 0); 181 | font-style: italic; 182 | } 183 | 184 | .highlighted { 185 | background-color: rgba(255, 255, 255, 0.25); 186 | } 187 | 188 | #code-toolbar { 189 | padding: 2px 0px 0px 2px; 190 | background-color: white; 191 | border: 1px solid black; 192 | border-bottom: none; 193 | } 194 | 195 | #RGB-picker > input { 196 | width: 49%; 197 | } 198 | 199 | #RGB-color { 200 | display: inline-block; 201 | width: 49%; 202 | border: 1px solid black; 203 | font-family: sans-serif; 204 | } 205 | 206 | /* intellisense */ 207 | 208 | #intellisense { 209 | position: absolute; 210 | display: none; 211 | 212 | border: 1px solid grey; 213 | background-color: rgba(0, 0, 0, 0.75); 214 | } 215 | 216 | #intellisense > p { 217 | font-family: monospace; 218 | font-weight: bold; 219 | color: white; 220 | margin: 0px; 221 | padding: 0px 5px; 222 | } 223 | 224 | #intellisense > p.selected { 225 | background-color: rgba(220, 180, 80, 0.75); 226 | } 227 | 228 | /* items area style */ 229 | 230 | #items-area { 231 | background-image: url("../img/background.jpg"); 232 | background-size: cover; 233 | background-position: center; 234 | } 235 | 236 | .item-container { 237 | display: inline-block; 238 | margin: 2px; 239 | } 240 | 241 | .hidden-item-container { 242 | display: inline-block; 243 | margin: 2px; 244 | } 245 | 246 | .hidden-item-container:hover { 247 | border: 1px dashed grey; 248 | } 249 | 250 | .item { 251 | position: relative; 252 | margin: 0px; 253 | padding: 1px 6px; 254 | background-color: rgba(0, 0, 0, 0.75); 255 | } 256 | 257 | .item > span { 258 | display: inline-block; 259 | margin: 0px; 260 | margin-top: -2px; 261 | padding: 0px; 262 | text-align: center; 263 | vertical-align: middle; 264 | 265 | color: white; 266 | font-family: Fontin; 267 | font-size: 17.5px; 268 | 269 | cursor: default; 270 | } 271 | 272 | .item > .influence { 273 | margin-right: 3px; 274 | height: 16px; 275 | } 276 | 277 | .item > .sockets { 278 | position: relative; 279 | display: inline-block; 280 | width: 10px; 281 | height: 16px; 282 | margin-left: 2px; 283 | margin-top: -5px; 284 | vertical-align: middle; 285 | } 286 | 287 | .item > .sockets > .socket { 288 | position: absolute; 289 | z-index: 5; 290 | width: 4px; 291 | height: 4px; 292 | /* position and color is set by script */ 293 | } 294 | 295 | .item > .sockets > .link-vertical { 296 | position: absolute; 297 | z-index: 10; 298 | width: 2px; 299 | height: 4px; 300 | background-color: white; 301 | /* position is set by script */ 302 | } 303 | 304 | .item > .sockets > .link-horizontal { 305 | position: absolute; 306 | z-index: 10; 307 | width: 4px; 308 | height: 2px; 309 | background-color: white; 310 | /* position is set by script */ 311 | } 312 | 313 | .item > .beam { 314 | position: absolute; 315 | left: 50%; 316 | margin-left: -20px; 317 | bottom: -10px; 318 | } 319 | 320 | .item > .mapIcon { 321 | position: absolute; 322 | left: 50%; 323 | margin-left: 30px; 324 | bottom: 0px; 325 | } 326 | 327 | /* item details */ 328 | 329 | #item-details { 330 | display: none; /* this is set to block by script */ 331 | position: absolute; 332 | margin: 0px; 333 | z-index: 20; 334 | /* position is also set by script */ 335 | 336 | border: 1px solid white; 337 | background-color: rgba(0, 0, 0, 0.75); 338 | 339 | font-family: Fontin; 340 | font-size: 15px; 341 | } 342 | 343 | #item-details > p { 344 | margin: 0px 5px; 345 | } 346 | 347 | #item-details > p > .label { 348 | color: #aaaaaa; 349 | } 350 | 351 | #item-details > p > .value { 352 | color: white; 353 | } 354 | 355 | /* add item dialog */ 356 | 357 | #additem-dialog { 358 | display: none; /* this is set to block by script */ 359 | position: absolute; 360 | padding: 5px; 361 | margin: 0px; 362 | z-index: 30; 363 | /* position is also set by script */ 364 | 365 | border: 1px solid white; 366 | background-color: rgba(0, 0, 0, 0.75); 367 | box-shadow: 0px 0px 15px #888; 368 | 369 | font-family: Fontin; 370 | font-size: 15px; 371 | } 372 | 373 | #additem-dialog > p { 374 | margin: 1px 0px 2px 0px; 375 | text-align: right; 376 | } 377 | 378 | #additem-dialog > p > .label { 379 | color: #aaaaaa; 380 | } 381 | 382 | #additem-dialog > p > .value { 383 | color: white; 384 | } 385 | 386 | #additem-dialog > p > input[type=text] { 387 | width: 170px; 388 | } 389 | #additem-dialog > p > .checkbox-align { 390 | width: 170px; 391 | margin: 0px; 392 | padding: 0px; 393 | text-align: left; 394 | display: inline-block; 395 | } 396 | #additem-dialog > p > select { 397 | width: 170px; 398 | } 399 | 400 | @keyframes additem-button-anim { 401 | from { opacity: 1.0 } 402 | to { opacity: 0.7 } 403 | } 404 | 405 | #additem-button { 406 | animation-name: additem-button-anim; 407 | animation-duration: 1s; 408 | animation-direction: alternate; 409 | animation-iteration-count: infinite; 410 | animation-timing-function: ease-in-out; 411 | } 412 | 413 | /* items editor */ 414 | 415 | #items-editor { 416 | padding: 3px; 417 | background-color: white; 418 | color: black; 419 | font-family: monospace; 420 | overflow-y: scroll; 421 | } 422 | 423 | /* help window */ 424 | 425 | #help-window { 426 | padding: 3px 10px; 427 | background-color: #252525; 428 | color: #ddddaa; 429 | font-family: sans-serif; 430 | font-size: 16px; 431 | } 432 | 433 | #help-window > h1 { 434 | font-family: Fontin; 435 | } 436 | 437 | #help-window > p { 438 | padding: 2px 10px; 439 | } 440 | 441 | #help-window li { 442 | padding: 5px; 443 | } 444 | 445 | #help-window a:link { color: rgb(250, 150, 50); } 446 | #help-window a:visited { color: rgb(250, 150, 50); } 447 | 448 | #help-window .url { font-style: italic; } 449 | #help-window .url .highlight { font-weight: bold; color: red; } 450 | #help-window .url .highlight2 { font-weight: bold; color: green; } 451 | 452 | #help-window img.inline { display:inline-block; } 453 | 454 | /* settings dialog */ 455 | 456 | #settings-dialog { 457 | display:none; 458 | 459 | position:absolute; 460 | left:50%; 461 | top:50%; 462 | width:300px; 463 | height:230px; 464 | margin-left:-150px; 465 | margin-top:-200px; 466 | z-index: 1000; 467 | 468 | border:2px solid black; 469 | box-shadow: 0px 0px 15px #888; 470 | background-color: white; 471 | color: black; 472 | font-family: Tahoma,Verdana,Arial,sans-serif; 473 | } 474 | 475 | #settings-dialog > h1 { 476 | padding:0px 0px 2px 5px; 477 | margin:0px; 478 | font-size: 16pt; 479 | background-color: grey; 480 | } 481 | 482 | #settings-dialog > h2 { 483 | padding:10px 0px 2px 5px; 484 | margin:0px; 485 | font-size: 14pt; 486 | } 487 | 488 | #settings-dialog > p { 489 | padding:0px 0px 2px 5px; 490 | margin:5px; 491 | } 492 | 493 | #settings-dialog select { 494 | width:50%; 495 | float:right; 496 | } 497 | 498 | #settings-dialog input { 499 | float:right; 500 | } 501 | 502 | #settings-dialog button { 503 | float:right; 504 | margin:10px 2px 2px 2px; 505 | } 506 | 507 | /* debug button */ 508 | 509 | #manual-update-button { 510 | position: absolute; 511 | left: 50%; 512 | margin-left: -40px; 513 | z-index: 100; 514 | display: none; 515 | } 516 | 517 | /* generic stuff */ 518 | 519 | * { box-sizing: border-box; } -------------------------------------------------------------------------------- /res/utils.js: -------------------------------------------------------------------------------- 1 | var StrUtils = { 2 | 3 | // Checks if string haystack contains needle 4 | contains: function (needle, haystack) { 5 | return haystack.indexOf( needle ) > -1; 6 | }, 7 | 8 | // Checks if str starts with prefix. 9 | startsWith: function (str, prefix) { 10 | return str.indexOf( prefix ) === 0; 11 | }, 12 | 13 | // Checks if str ends with the suffix. 14 | endsWith: function (str, suffix) { 15 | return str.indexOf( suffix ) === str.length - suffix.length; 16 | }, 17 | 18 | // Alphabetically sorts all characters in the string. 19 | // Characters must either be all uppercase or all lowercase. 20 | // Does not take locale into account. 21 | sortChars: function (str) { 22 | return str.split('').sort().join(''); 23 | }, 24 | 25 | // Checks if string A consists only of the characters in string B 26 | consistsOf: function (a, b) { 27 | var validChars = b.split(''); 28 | return a.split('').every( function(c) { return validChars.indexOf(c) >= 0; } ); 29 | }, 30 | 31 | // Counts how often a character occurs in the string. 32 | countChar: function (c, str) { 33 | var count = 0; 34 | for (var i=0; i < str.length; i++) { 35 | if (str[i] === c) { 36 | count++; 37 | } 38 | } 39 | return count; 40 | }, 41 | 42 | // Counts all chars in the string. Returns an object with char -> amount. 43 | countChars: function (str) { 44 | var result = { }; 45 | for (var i=0; i < str.length; i++) { 46 | var c = str[i]; 47 | if (c in result) { 48 | result[c]++; 49 | } 50 | else { 51 | result[c] = 1; 52 | } 53 | } 54 | return result; 55 | }, 56 | 57 | ltrim: function (stringToTrim) { 58 | return stringToTrim.replace(/^\s+/,""); 59 | }, 60 | 61 | rtrim: function (stringToTrim) { 62 | return stringToTrim.replace(/\s+$/,""); 63 | }, 64 | 65 | replaceAll: function (str, old, replacement) { 66 | return str.split( old ).join( replacement ); 67 | }, 68 | 69 | parseIntOrDefault: function (str, defaultValue) { 70 | var result = parseInt(str); 71 | return isNaN(result) ? defaultValue : result; 72 | } 73 | }; 74 | 75 | var ArrayUtils = { 76 | //+ Jonas Raoni Soares Silva 77 | //@ http://jsfromhell.com/array/shuffle [v1.0] 78 | shuffle: function(o) { 79 | for(var j, x, i = o.length; i; j = Math.floor(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x); 80 | return o; 81 | }, 82 | 83 | areEqual: function (a, b) { 84 | if (a.length !== b.length) return false; 85 | for (var i=0; i < a.length; i++) { 86 | if (a[i] !== b[i]) return false; 87 | } 88 | return true; 89 | }, 90 | 91 | contains: function (a,x) { 92 | var idx = a.indexOf(x); 93 | return idx >= 0; 94 | } 95 | }; 96 | 97 | var DomUtils = { 98 | 99 | endsWithBR: function (elem) { 100 | if (elem.childNodes.length == 0) { 101 | return false; 102 | } 103 | 104 | // look at child nodes, start at the end 105 | for (var i = elem.childNodes.length - 1; i >= 0; i++) { 106 | var child = elem.childNodes[i]; 107 | 108 | // skip comments 109 | if (child.nodeType === 8) { 110 | continue; 111 | } 112 | 113 | if (child.nodeType === 1) { 114 | if (child.nodeName === 'BR') { 115 | return true; 116 | } 117 | } 118 | 119 | return false; 120 | } 121 | }, 122 | 123 | // Returns all text inside the given node(s). 124 | //
elements and blocks like
appear as newlines in the text. 125 | // This behaves similarly to the innerText property available in Chrome and IE. 126 | getText: function (elem) { 127 | return DomUtils._getText( elem.childNodes, '' ); 128 | }, 129 | 130 | _getText: function (elems, text) { 131 | for (var i = 0; elems[i]; i++) { 132 | var elem = elems[i]; 133 | 134 | // Get the text from Text and CDATA nodes 135 | if (elem.nodeType === 3 || elem.nodeType === 4) { 136 | text += elem.nodeValue; 137 | } 138 | // Special handling for certain elements: 139 | else if (elem.nodeType === 1) { 140 | // Insert newlines for
elements 141 | if (elem.nodeName === 'BR') { 142 | text += '\n'; 143 | continue; 144 | } 145 | 146 | // Ignore invisible elements 147 | var style = window.getComputedStyle( elem ); 148 | if (style.display === 'none' || style.visibility === 'hidden') { 149 | continue; 150 | } 151 | 152 | // Add newline before each block, unless we are already on a new line 153 | // or this is the very first block in the list 154 | if (style.display === 'block') { 155 | if (text.length > 0 && text[text.length - 1] !== '\n') { 156 | text += '\n'; 157 | } 158 | } 159 | 160 | // Traverse child nodes 161 | text = DomUtils._getText( elem.childNodes, text ); 162 | 163 | // Add newline after each block unless there already is a new line. 164 | if (style.display === 'block') { 165 | if (text.length > 0 && text[text.length - 1] !== '\n') { 166 | text += '\n'; 167 | } 168 | } 169 | } 170 | // Traverse all other nodes, except for comments 171 | else if ( elem.nodeType !== 8 ) { 172 | text = DomUtils._getText( elem.childNodes, text ); 173 | } 174 | } 175 | 176 | return text; 177 | }, 178 | 179 | setText: function (elem, text) { 180 | var lines = StrUtils.replaceAll( text, ' ', '  ' ).split( '\n' ); 181 | 182 | DomUtils.removeAllChildren( elem ); 183 | for (var i=0; i < lines.length; i++) { 184 | elem.appendChild( document.createTextNode( lines[i] ) ); 185 | if (i < lines.length - 1) { 186 | elem.appendChild( document.createElement( 'br' ) ); 187 | } 188 | } 189 | }, 190 | 191 | // Returns the current selection in the given element. 192 | // Selection is stored as character offsets. 193 | saveSelection: function (element) { 194 | return rangy.getSelection().saveCharacterRanges( element ); 195 | }, 196 | 197 | // Restores a saved selection on the given element. 198 | // Selection is applied based on character counts. 199 | // If text is inserted, you need to manually adjust the selection by providing an offset. 200 | restoreSelection: function (element, selection, offset) { 201 | if (selection !== null && selection.length > 0) { 202 | selection[0].characterRange.start += offset; 203 | selection[0].characterRange.end += offset; 204 | } 205 | rangy.getSelection().restoreCharacterRanges( element, selection ); 206 | }, 207 | 208 | // Returns the number of characters before the cursor in the given selection. 209 | getSelectionCharOffset: function (selection) { 210 | return selection[0].characterRange.start; 211 | }, 212 | 213 | isValidSelection: function (selection) { 214 | return selection && selection.length; 215 | }, 216 | 217 | removeAllChildren: function (elem) { 218 | while (elem.lastChild) { 219 | elem.removeChild( elem.lastChild ); 220 | } 221 | } 222 | }; 223 | 224 | var MathUtils = { 225 | clamp: function (value, min, max) { 226 | return Math.min( max, Math.max( min, value ) ); 227 | }, 228 | 229 | remap: function (value, oldmin, oldmax, newmin, newmax) { 230 | var t = (value - oldmin) / (oldmax - oldmin); 231 | return newmin + t * (newmax - newmin); 232 | } 233 | }; 234 | 235 | var StorageUtils = { 236 | save: function (key, value) { 237 | if (typeof(Storage) !== 'undefined') { 238 | localStorage.setItem( key, value ); 239 | } 240 | }, 241 | 242 | load: function (key, defaultValue) { 243 | if (typeof(Storage) !== 'undefined') { 244 | if (key in localStorage) { 245 | return localStorage[key]; 246 | } 247 | } 248 | return defaultValue; 249 | }, 250 | }; 251 | 252 | var UrlUtils = { 253 | isSSL: function() { 254 | return window.location.protocol === 'https:'; 255 | }, 256 | 257 | redirectToProtocol: function (protocol) { 258 | window.location.href = protocol + ':' + window.location.href.substring( window.location.protocol.length ); 259 | } 260 | }; 261 | 262 | var EventUtils = { 263 | // There are different ways how the key code may be stored in the event on different browsers 264 | getKeyCode: function (event) { 265 | if (event.keyCode) { 266 | return event.keyCode; 267 | } 268 | else if (event.which) { 269 | return event.which; 270 | } 271 | } 272 | } 273 | 274 | var MiscUtils = { 275 | 276 | preload : function(src) { 277 | img = new Image(); 278 | img.src = src; 279 | }, 280 | 281 | 282 | getDateString : function(date) { 283 | return( 284 | date.getFullYear() 285 | + zeropad(date.getMonth()+1) 286 | + zeropad(date.getDate()) 287 | + "_" 288 | + zeropad(date.getHours()) 289 | + zeropad(date.getMinutes()) 290 | + zeropad(date.getSeconds()) 291 | ); 292 | 293 | function zeropad(n) { 294 | return (n < 10 ? "0" : "") + n; 295 | } 296 | }, 297 | 298 | getRunningTime : function(time1, time2) { 299 | t1 = getDate(time1); 300 | t2 = getDate(time2); 301 | secdiff = (t2.getTime() - t1.getTime()) / 1000; 302 | seconds = secdiff % 60; 303 | minutes = Math.floor(secdiff / 60) % 60; 304 | hours = Math.floor(Math.floor(secdiff / 60) / 60); 305 | if(seconds < 10) { 306 | seconds = "0" + seconds; 307 | } 308 | if(minutes < 10 && hours > 0) { 309 | minutes = "0" + minutes; 310 | } 311 | return (hours > 0 ? hours + ":" : "") + minutes + ":" + seconds; 312 | 313 | function getDate(time1) { 314 | return new Date( 315 | time1.substring(0,4), 316 | (time1.substring(4,6)) - 1, 317 | time1.substring(6,8), 318 | time1.substring(9,11), 319 | time1.substring(11,13), 320 | time1.substring(13, 15) 321 | ); 322 | } 323 | }, 324 | 325 | formatDate : function(e) { 326 | return"[" + e.substring(0,4) + "-" + e.substring(4,6) + "-" + e.substring(6,8) + " " + e.substring(8,10) + ":" + e.substring(10,12) + ":" + e.substring(12,14) + "]"; 327 | } 328 | 329 | } -------------------------------------------------------------------------------- /sidenav.html: -------------------------------------------------------------------------------- 1 | 152 | 153 |
154 | 155 | Exile Diary v 156 |
157 | 158 |
159 |
160 | 161 | 162 | 163 | 164 | 165 | 166 |
167 | 168 |
169 |
170 | 171 | About 172 |
173 | 174 | 175 |
176 |
177 |
178 | 179 |
180 | 181 |
182 | 183 |
184 | 185 | 191 | 192 | 193 | --------------------------------------------------------------------------------