├── .dockerignore ├── .env ├── .eslintrc.js ├── .gitignore ├── .vscode └── launch.json ├── Dockerfile ├── README.md ├── api ├── routes.ts └── v1 │ ├── config.routes.ts │ ├── d2s.routes.ts │ └── img.routes.ts ├── app.ts ├── babel.config.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── gemsocket.png │ ├── inv_armor.png │ ├── inv_belt.png │ ├── inv_boots.png │ ├── inv_grid.png │ ├── inv_helm_glove__0__0.png │ ├── inv_helm_glove__0__1.png │ ├── inv_ring_amulet__0__0.png │ ├── inv_ring_amulet__0__1.png │ └── inv_weapons.png ├── components │ ├── Armory.vue │ ├── Character.vue │ ├── Equipped.vue │ ├── Golem.vue │ ├── Grid.vue │ ├── Item.vue │ ├── List.vue │ └── Mercenary.vue ├── main.js ├── router │ └── index.js ├── store │ └── index.js └── styles │ ├── _globals.scss │ └── _variables.scss └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .vscode 3 | README.md 4 | node_modules 5 | dist 6 | out -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | PORT=3030 2 | 3 | #read 4 bits instead of 3 for stash 4 | EXTENDED_STASH=true 5 | 6 | #where are saves 7 | SAVE_DIR=/mnt/d/Git/d2/113c/d2s/save 8 | #SAVE_DIR=/Users/drschuma/Documents/d2/113c/d2s/save 9 | 10 | #mpq data. txt files, images, and palette data 11 | MPQ_DATA_DIR=/mnt/d/Git/d2/113c/data 12 | #MPQ_DATA_DIR=/Users/drschuma/Documents/d2/113c/data 13 | 14 | #optional img directory if different than mpq 15 | #IMG_DATA_DIR=/Users/drschuma/Documents/d2/113c/data 16 | 17 | VUE_APP_TITLE=D2S-UI 18 | 19 | #VUE_APP_INV_WIDTH=10 20 | #VUE_APP_INV_HEIGHT=4 21 | 22 | #VUE_APP_STASH_WIDTH=10 23 | #VUE_APP_STASH_HEIGHT=10 24 | 25 | #VUE_APP_CUBE_WIDTH=5 26 | #VUE_APP_CUBE_HEIGHT=5 -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | 'plugin:vue/essential', 8 | '@vue/airbnb', 9 | ], 10 | rules: { 11 | 'no-param-reassign': [2, { props: false }], 12 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 13 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 14 | }, 15 | parserOptions: { 16 | parser: 'babel-eslint', 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /out 5 | 6 | build.js 7 | 8 | # local env files 9 | .env.local 10 | .env.*.local 11 | 12 | # Log files 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | 25 | 26 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Launch Program", 11 | "args": ["${workspaceRoot}/app.ts"], 12 | "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], 13 | "sourceMaps": true, 14 | "cwd": "${workspaceRoot}", 15 | "protocol": "inspector", 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10-alpine 2 | 3 | WORKDIR /usr/src/app 4 | 5 | RUN apk update && \ 6 | apk upgrade && \ 7 | apk add --no-cache --virtual install \ 8 | python \ 9 | make \ 10 | g++ \ 11 | git 12 | 13 | COPY package*.json ./ 14 | 15 | RUN npm install --unsafe-perm && \ 16 | apk del install 17 | 18 | COPY . . 19 | 20 | RUN npm run build && \ 21 | npm prune --production 22 | 23 | CMD ["node", "./out/app.js"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### D2S-UI 2 | 3 | Barebones UI for displaying a d2s file. 4 | 5 | ![image](https://user-images.githubusercontent.com/1458109/92019197-390d4d00-ed24-11ea-93f1-7fd7333e3cc7.png) 6 | 7 | You need to provide the images/txt data extracted from the MPQ as env variables: 8 | 9 | * `MPQ_DATA_DIR` - path to extracted MPQ data. 10 | * `IMG_DATA_DIR` - optional image directory instead of MPQ data directory. 11 | 12 | ##### Build 13 | ``` 14 | docker build . -t dschu012/d2s:latest 15 | ``` 16 | 17 | Run 18 | ``` 19 | docker run dschu012/d2s:latest \ 20 | -p 8080:3030 \ 21 | -e EXTENDED_STASH=true 22 | -v /path/to/saves:/data/saves:ro \ 23 | -v /path/to/txt:/data/txt:ro \ 24 | -v /path/to/img:/data/img:ro \ 25 | ``` 26 | 27 | ##### Examples 28 | * https://diablo.dannyschumacher.com/#/ 29 | * https://resurgence.dannyschumacher.com/#/ 30 | -------------------------------------------------------------------------------- /api/routes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import d2s from "./v1/d2s.routes"; 3 | import img from "./v1/img.routes"; 4 | import config from "./v1/config.routes"; 5 | 6 | let router: express.Router = express.Router(); 7 | 8 | router.use("/v1/d2s", d2s); 9 | router.use("/v1/img", img); 10 | router.use("/v1/config", config); 11 | 12 | 13 | export default router; -------------------------------------------------------------------------------- /api/v1/config.routes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import dotenv from "dotenv"; 3 | 4 | dotenv.config(); 5 | 6 | let router: express.Router = express.Router(); 7 | 8 | router.get("/", async (req, res) => { 9 | res.json({ 10 | VUE_APP_INV_WIDTH: process.env.VUE_APP_INV_WIDTH || 10, 11 | VUE_APP_INV_HEIGHT: process.env.VUE_APP_INV_HEIGHT || 4, 12 | VUE_APP_STASH_WIDTH: process.env.VUE_APP_STASH_WIDTH || 6, 13 | VUE_APP_STASH_HEIGHT: process.env.VUE_APP_STASH_HEIGHT || 8, 14 | VUE_APP_CUBE_WIDTH: process.env.VUE_APP_CUBE_WIDTH || 3, 15 | VUE_APP_CUBE_HEIGHT: process.env.VUE_APP_CUBE_HEIGHT || 4, 16 | }); 17 | }); 18 | 19 | export default router; -------------------------------------------------------------------------------- /api/v1/d2s.routes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import { read, readConstantData, types } from "d2s"; 5 | import dotenv from "dotenv"; 6 | import glob from "glob"; 7 | 8 | dotenv.config(); 9 | 10 | let router: express.Router = express.Router(); 11 | let c: types.IConstantData; 12 | 13 | /* 14 | * Load up constant data when start server 15 | */ 16 | (async () => { 17 | let base = process.env.MPQ_DATA_DIR as string; 18 | let files = {} as any; 19 | let dir = path.join(`${base}/global/excel/`); 20 | try { 21 | fs.readdirSync(dir).forEach(file => { 22 | if (file.endsWith(".txt")) { 23 | files[file] = fs.readFileSync(path.join(dir, file), 'utf8'); 24 | } 25 | }); 26 | dir = path.join(`${base}/local/LNG/ENG/`); 27 | fs.readdirSync(dir).forEach(file => { 28 | if (file.endsWith(".txt")) { 29 | files[file] = fs.readFileSync(path.join(dir, file), 'utf8'); 30 | } 31 | }); 32 | c = await readConstantData(files); 33 | } catch (e) { 34 | console.error(e); 35 | process.exit(1); 36 | } 37 | })(); 38 | 39 | router.get("/character/:character", async (req, res) => { 40 | try { 41 | const base = process.env.SAVE_DIR as string; 42 | const files = await glob.sync(path.join(base, `**/${req.params.character}?(.d2s)`), { nodir: true, nocase: true }); 43 | if(!files.length) { 44 | throw new Error(`${req.params.character} save not found.`); 45 | } 46 | const buffer = fs.readFileSync(files[0]); 47 | const s = await read(buffer, c, { extendedStash: process.env.EXTENDED_STASH == "true"}); 48 | res.json(s); 49 | } catch (e) { 50 | console.log(e); 51 | } 52 | }); 53 | 54 | router.get("/list", async (req, res) => { 55 | const base = process.env.SAVE_DIR as string; 56 | const files = await glob.sync(path.join(base, `**`), { nodir: true }).map(file => path.basename(file, '.d2s')); 57 | res.json(files); 58 | }); 59 | 60 | export default router; -------------------------------------------------------------------------------- /api/v1/img.routes.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import path from "path"; 3 | import fs from "fs"; 4 | import dotenv from "dotenv"; 5 | import jimp from "jimp"; 6 | import glob from "glob"; 7 | 8 | dotenv.config(); 9 | 10 | let router: express.Router = express.Router(); 11 | 12 | const palette = [] as any; 13 | const mappings = [] as any; 14 | 15 | const colormaps = { 16 | 1: '/global/items/Palette/grey.dat', 17 | 2: '/global/items/Palette/grey2.dat', 18 | 5: '/global/items/Palette/greybrown.dat', 19 | 6: '/global/items/Palette/invgrey.dat', 20 | 7: '/global/items/Palette/invgrey2.dat', 21 | 8: '/global/items/Palette/invgreybrown.dat', 22 | }; 23 | 24 | const colors = { 25 | whit: 0, 26 | lgry: 1, 27 | dgry: 2, 28 | blac: 3, 29 | lblu: 4, 30 | dblu: 5, 31 | cblu: 6, 32 | lred: 7, 33 | dred: 8, 34 | cred: 9, 35 | lgrn: 10, 36 | dgrn: 11, 37 | cgrn: 12, 38 | lyel: 13, 39 | dyel: 14, 40 | lgld: 15, 41 | dgld: 16, 42 | lpur: 17, 43 | dpur: 18, 44 | oran: 19, 45 | bwht: 20, 46 | }; 47 | 48 | 49 | const base = process.env.IMG_DATA_DIR as string || process.env.MPQ_DATA_DIR as string; 50 | /* 51 | * Load up constant data when start server 52 | */ 53 | (async () => { 54 | 55 | async function loadPalette(f: string) { 56 | const buffer = fs.readFileSync(path.join(base, f)); 57 | for (let i = 0; i < 256; i += 1) { 58 | palette.push([buffer[i * 3 + 2], buffer[i * 3 + 1], buffer[i * 3]]); 59 | } 60 | }; 61 | async function loadMapping(key: any, f: string) { 62 | const buffer = fs.readFileSync(path.join(base, f)); 63 | const mapping = []; 64 | for (let i = 0; i < Object.keys(colors).length; i += 1) { 65 | mapping.push(buffer.slice(0 + (i * 256), 256 + (i * 256))); 66 | } 67 | mappings[key] = mapping; 68 | }; 69 | 70 | try { 71 | loadPalette(`/global/palette/ACT1/pal.dat`); 72 | for(const key in colormaps) { 73 | loadMapping(key, colormaps[key]); 74 | } 75 | } catch(e) { 76 | console.log(e); 77 | process.exit(1); 78 | } 79 | })(); 80 | 81 | 82 | router.get("/:file.png", async (req, res) => { 83 | try { 84 | //we only care about first frame 85 | const files = await glob.sync(path.join(base, '/global/items', `${req.params.file}.dc6`), { nodir: true, nocase: true }); 86 | if(files.length === 0) { 87 | throw new Error(`File not found ${req.params.file}.dc6`); 88 | } 89 | const dc6 = fs.readFileSync(files[0]); 90 | const width = dc6.readUInt32LE(32); 91 | const height = dc6.readUInt32LE(36); 92 | const length = dc6.readUInt32LE(56); 93 | let x = 0, y = height - 1; 94 | const indexed = [] as any; 95 | for (let i = 0; i < height; i += 1) { 96 | indexed[i] = Array(width).fill(255); 97 | } 98 | for (let i = 0; i < length;) { 99 | let b = dc6.readUInt8(60 + i++); 100 | if (b === 0x80) { //eol 101 | x = 0, y--; 102 | } else if (b & 0x80) { 103 | x += b & 0x7F; //transparent repeat for N bytes 104 | } else { 105 | //read N bytes 106 | for(let j = 0; j < b; j++) { 107 | indexed[y][x++] = dc6.readUInt8(60 + i++); 108 | } 109 | } 110 | } 111 | new jimp(width, height, async function (err, image) { 112 | let data = image.bitmap.data; 113 | for (let y = 0; y < height; y += 1) { 114 | for (let x = 0; x < width; x += 1) { 115 | let paletteIdx = indexed[y][x]; 116 | const offset = (y * width + x) * 4; 117 | if(paletteIdx === 255) { //transparent 118 | continue; 119 | } 120 | if(req.query.inv_transform && req.query.transform_color) { 121 | let transformIdx = colors[req.query.transform_color]; 122 | if(transformIdx >= 0 && mappings[req.query.inv_transform]) { 123 | paletteIdx = mappings[req.query.inv_transform][transformIdx][paletteIdx]; 124 | } 125 | } 126 | const rgb = palette[paletteIdx]; 127 | data[offset] = rgb[0]; 128 | data[offset + 1] = rgb[1]; 129 | data[offset + 2] = rgb[2]; 130 | data[offset + 3] = 255; 131 | } 132 | } 133 | let buffer = await image.getBufferAsync(jimp.MIME_PNG); 134 | res.writeHead(200, { 135 | 'Content-Type': 'image/png', 136 | 'Cache-Control': 'public, max-age=31557600, s-maxage=31557600' 137 | }); 138 | res.end(buffer, 'binary'); 139 | }) 140 | } catch (e) { 141 | console.log(e); 142 | res.writeHead(404); 143 | res.end(); 144 | } 145 | }); 146 | 147 | export default router; -------------------------------------------------------------------------------- /app.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import dotenv from "dotenv"; 3 | import path from "path"; 4 | import api from "./api/routes"; 5 | 6 | dotenv.config(); 7 | 8 | const app: express.Application = express(); 9 | const port = process.env.PORT || 8080; 10 | 11 | app.use("/", express.static(path.join(process.cwd(), "dist"))); 12 | app.use("/api", api); 13 | 14 | app.listen(port, () => { 15 | console.log(`server started on ${port}`); 16 | }); -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d2s-ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "tsc && vue-cli-service build", 8 | "lint": "vue-cli-service lint --fix", 9 | "tsc": "tsc", 10 | "dev-server": "ts-node-dev --respawn --transpileOnly ./app.ts", 11 | "dev-ui": "webpack --config ./node_modules/@vue/cli-service/webpack.config.js --watch", 12 | "dev": "concurrently 'npm run dev-ui' 'npm run dev-server'" 13 | }, 14 | "dependencies": { 15 | "core-js": "^3.4.3", 16 | "express": "4.17.1", 17 | "vue": "^2.6.10", 18 | "vuex": "3.1.2", 19 | "vue-router": "3.1.3", 20 | "dotenv": "8.2.0", 21 | "d2s": "dschu012/d2s#v1.0.0", 22 | "jimp": "0.9.3", 23 | "@jimp/png": "0.9.3", 24 | "tippy.js": "5.1.2", 25 | "glob": "7.1.6" 26 | }, 27 | "devDependencies": { 28 | "@vue/cli-plugin-babel": "^4.1.0", 29 | "@vue/cli-plugin-eslint": "^4.1.0", 30 | "@vue/cli-service": "^4.1.0", 31 | "@vue/eslint-config-airbnb": "^4.0.0", 32 | "@types/express": "4.17.1", 33 | "babel-eslint": "^10.0.3", 34 | "eslint": "^5.16.0", 35 | "eslint-plugin-vue": "^5.0.0", 36 | "vue-template-compiler": "^2.6.10", 37 | "typescript": "^3.7.3", 38 | "ts-node-dev": "^1.0.0-pre.44", 39 | "webpack-cli": "3.3.10", 40 | "concurrently": "5.0.0", 41 | "node-sass": "4.13.0", 42 | "sass-loader": "8.0.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= process.env.VUE_APP_TITLE %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 27 | -------------------------------------------------------------------------------- /src/assets/gemsocket.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/src/assets/gemsocket.png -------------------------------------------------------------------------------- /src/assets/inv_armor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/src/assets/inv_armor.png -------------------------------------------------------------------------------- /src/assets/inv_belt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/src/assets/inv_belt.png -------------------------------------------------------------------------------- /src/assets/inv_boots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/src/assets/inv_boots.png -------------------------------------------------------------------------------- /src/assets/inv_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/src/assets/inv_grid.png -------------------------------------------------------------------------------- /src/assets/inv_helm_glove__0__0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/src/assets/inv_helm_glove__0__0.png -------------------------------------------------------------------------------- /src/assets/inv_helm_glove__0__1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/src/assets/inv_helm_glove__0__1.png -------------------------------------------------------------------------------- /src/assets/inv_ring_amulet__0__0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/src/assets/inv_ring_amulet__0__0.png -------------------------------------------------------------------------------- /src/assets/inv_ring_amulet__0__1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/src/assets/inv_ring_amulet__0__1.png -------------------------------------------------------------------------------- /src/assets/inv_weapons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dschu012/d2s-ui/13ab879c7e2f6cdcbe3dd76b3f1201c1f858fce5/src/assets/inv_weapons.png -------------------------------------------------------------------------------- /src/components/Armory.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 48 | 49 | 51 | -------------------------------------------------------------------------------- /src/components/Character.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 74 | 75 | 78 | -------------------------------------------------------------------------------- /src/components/Equipped.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 69 | 70 | 73 | -------------------------------------------------------------------------------- /src/components/Golem.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /src/components/Grid.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 27 | 28 | 30 | -------------------------------------------------------------------------------- /src/components/Item.vue: -------------------------------------------------------------------------------- 1 | 65 | 66 | 238 | 239 | 241 | -------------------------------------------------------------------------------- /src/components/List.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | 27 | 30 | -------------------------------------------------------------------------------- /src/components/Mercenary.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 31 | 32 | 35 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | import router from './router'; 4 | import store from './store'; 5 | 6 | new Vue({ 7 | store, 8 | render: h => h(App), 9 | router, 10 | }).$mount('#app'); 11 | -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Router from 'vue-router'; 3 | import Armory from '@/components/Armory.vue'; 4 | import List from '@/components/List.vue'; 5 | 6 | Vue.use(Router); 7 | 8 | export default new Router({ 9 | routes: [ 10 | { 11 | path: '/', 12 | name: 'list', 13 | component: List, 14 | }, 15 | { 16 | path: '/character/:character', 17 | name: 'armory', 18 | component: Armory, 19 | }, 20 | ], 21 | }); 22 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | Vue.use(Vuex); 5 | 6 | export default new Vuex.Store({ 7 | state: { 8 | detailed: false, 9 | config: null, 10 | }, 11 | mutations: { 12 | detailed(state, value) { 13 | state.detailed = value; 14 | }, 15 | config(state, value) { 16 | state.config = value; 17 | }, 18 | }, 19 | getters: { 20 | config(state) { 21 | return state.config; 22 | }, 23 | }, 24 | actions: { 25 | async loadConfig({ commit }) { 26 | const response = await fetch('/api/v1/config'); 27 | commit('config', await response.json()); 28 | }, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /src/styles/_globals.scss: -------------------------------------------------------------------------------- 1 | @import "@/styles/_variables.scss"; 2 | 3 | #app { 4 | font-family: 'Avenir', Helvetica, Arial, sans-serif; 5 | -webkit-font-smoothing: antialiased; 6 | -moz-osx-font-smoothing: grayscale; 7 | text-align: center; 8 | color: #2c3e50; 9 | margin-top: 60px; 10 | } 11 | 12 | .grid { 13 | position: relative; 14 | background: url('~@/assets/inv_grid.png'); 15 | background-repeat: repeat; 16 | margin: 1em auto; 17 | } 18 | 19 | .inventory { 20 | position: relative; 21 | margin: 1em auto; 22 | width: #{$gridSize * 10}px; 23 | height: #{$gridSize * 7}px; 24 | } 25 | 26 | .head { 27 | position: absolute; 28 | background: url('~@/assets/inv_helm_glove__0__1.png'); 29 | background-size: contain; 30 | top: 0; 31 | left: #{($gridSize * 5) - ($gridSize)}px; 32 | width: #{$gridSize * 2}px; 33 | height: #{$gridSize * 2}px; 34 | } 35 | 36 | .neck { 37 | position: absolute; 38 | background: url('~@/assets/inv_ring_amulet__0__0.png'); 39 | background-size: contain; 40 | top: #{($gridSize * .25)}px; 41 | left: #{($gridSize * 5) + ($gridSize * 1.25)}px; 42 | width: #{$gridSize * 1}px; 43 | height: #{$gridSize * 1}px; 44 | } 45 | 46 | .torso { 47 | position: absolute; 48 | background: url('~@/assets/inv_armor.png'); 49 | background-size: contain; 50 | top: #{$gridSize * 2}px; 51 | left: #{($gridSize * 5) - ($gridSize)}px; 52 | width: #{$gridSize * 2}px; 53 | height: #{$gridSize * 3}px; 54 | } 55 | 56 | .weapon { 57 | background: url('~@/assets/inv_weapons.png'); 58 | background-size: contain; 59 | width: #{$gridSize * 2}px; 60 | height: #{$gridSize * 4}px; 61 | } 62 | 63 | .right-hand, .alt-right-hand { 64 | top: #{$gridSize * 1.25}px; 65 | left: #{($gridSize * 5) - ($gridSize * 4.5)}px; 66 | position: absolute; 67 | } 68 | 69 | .left-hand, .alt-left-hand { 70 | top: #{$gridSize * 1.25}px; 71 | left: #{($gridSize * 5) + ($gridSize * 2.5)}px; 72 | position: absolute; 73 | } 74 | 75 | .ring { 76 | background: url('~@/assets/inv_ring_amulet__0__0.png'); 77 | background-size: contain; 78 | width: #{$gridSize * 1}px; 79 | height: #{$gridSize * 1}px; 80 | } 81 | 82 | .right-finger { 83 | position: absolute; 84 | top: #{$gridSize * 5.25}px; 85 | left: #{($gridSize * 5) - ($gridSize * 2.25)}px; 86 | } 87 | 88 | .left-finger { 89 | position: absolute; 90 | top: #{$gridSize * 5.25}px; 91 | left: #{($gridSize * 5) + ($gridSize * 1.25)}px; 92 | } 93 | 94 | .waist { 95 | position: absolute; 96 | background: url('~@/assets/inv_belt.png'); 97 | background-size: contain; 98 | top: #{$gridSize * 5.25}px; 99 | left: #{($gridSize * 5) - ($gridSize)}px; 100 | width: #{$gridSize * 2}px; 101 | height: #{$gridSize * 1}px; 102 | } 103 | 104 | .feet { 105 | position: absolute; 106 | background: url('~@/assets/inv_boots.png'); 107 | background-size: contain; 108 | top: #{$gridSize * 5.25}px; 109 | left: #{($gridSize * 5) + ($gridSize * 2.5)}px; 110 | width: #{$gridSize * 2}px; 111 | height: #{$gridSize * 2}px; 112 | } 113 | 114 | .hands { 115 | position: absolute; 116 | background: url('~@/assets/inv_helm_glove__0__0.png'); 117 | background-size: contain; 118 | top: #{$gridSize * 5.25}px; 119 | left: #{($gridSize * 5) - ($gridSize * 4.5)}px; 120 | width: #{$gridSize * 2}px; 121 | height: #{$gridSize * 2}px; 122 | } 123 | 124 | .right-tab { 125 | top: #{$gridSize * .75}px; 126 | left: #{($gridSize * 5) - ($gridSize * 4.5)}px; 127 | position: absolute; 128 | } 129 | 130 | .left-tab { 131 | top: #{$gridSize * .75}px; 132 | left: #{($gridSize * 5) + ($gridSize * 2.5)}px; 133 | position: absolute; 134 | } 135 | 136 | .tab { 137 | padding: 10px; 138 | } 139 | 140 | .inventory .item { 141 | background: none; 142 | width: 100%; 143 | height: 100%; 144 | } 145 | 146 | .item { 147 | position: absolute; 148 | background: rgba(0, 0, 45, 0.5); 149 | } 150 | 151 | .ethereal { 152 | opacity: 0.5; 153 | } 154 | 155 | 156 | .item img, .socket img { 157 | position: absolute; 158 | top: 50%; 159 | left: 50%; 160 | transform: translateX(-50%) translateY(-50%); 161 | max-width: 100%; 162 | max-height: 100%; 163 | } 164 | 165 | .empty-socket { 166 | background: url('~@/assets/gemsocket.png'); 167 | opacity: 0.5; 168 | background-repeat: no-repeat; 169 | background-position: center; 170 | } 171 | 172 | .socket { 173 | position: absolute; 174 | min-width: #{$gridSize}px; 175 | min-height: #{$gridSize}px; 176 | z-index: 999; 177 | } 178 | 179 | .tippy-tooltip { 180 | font-family: 'Avenir', Helvetica, Arial, sans-serif; 181 | -webkit-font-smoothing: antialiased; 182 | -moz-osx-font-smoothing: grayscale; 183 | text-align: center; 184 | background: rgba(0, 0, 0, 0.8); 185 | color: white; 186 | padding: 8px; 187 | font-size: 14px; 188 | } 189 | 190 | .dgrey { 191 | color: rgb(105, 105, 105); 192 | } 193 | 194 | .red { 195 | color: rgb(255, 77, 77); 196 | } 197 | 198 | .green { 199 | color: rgb(0, 255, 0); 200 | } 201 | 202 | .blue { 203 | color: rgb(105, 105, 255); 204 | } 205 | 206 | .gold { 207 | color: rgb(199,179,119); 208 | } 209 | 210 | .grey { 211 | color: rgb(105, 105, 105); 212 | } 213 | 214 | .black { 215 | color: rgb(0, 0, 0); 216 | } 217 | 218 | .orange { 219 | color: rgb(255, 168, 0); 220 | } 221 | 222 | .yellow { 223 | color: rgb(255, 255, 100); 224 | } 225 | 226 | .white { 227 | color: rgb(255, 255, 255); 228 | } 229 | 230 | @mixin grid-size-list { 231 | @for $i from $minGridWidth through $maxGridWidth { 232 | .x-#{$i} { 233 | left: #{$gridSize * $i}px; 234 | } 235 | .y-#{$i} { 236 | top: #{$gridSize * $i}px; 237 | } 238 | .w-#{$i} { 239 | width: #{$gridSize * $i}px; 240 | } 241 | .h-#{$i} { 242 | height: #{$gridSize * $i}px; 243 | } 244 | } 245 | } 246 | 247 | @include grid-size-list; -------------------------------------------------------------------------------- /src/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | $gridSize: 32; 2 | 3 | //just used for generating classes for max inv/stash/cube sizes. 4 | $minGridWidth: 1; 5 | $maxGridWidth: 20; 6 | 7 | :root { 8 | --grid-size: #{$gridSize}; 9 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "strict": true, 5 | "baseUrl": "./", 6 | "target": "es6", 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "suppressImplicitAnyIndexErrors": true, 10 | "outDir": "./out" 11 | }, 12 | "exclude": [ 13 | "./src/", 14 | "node_modules" 15 | ] 16 | } --------------------------------------------------------------------------------