chore: apply prettier on all files

This commit is contained in:
Stefano Borzi 2022-03-22 10:40:02 +01:00
parent 6933e8ee86
commit 5ba5244e4b
19 changed files with 12727 additions and 2254 deletions

15
.prettierignore Normal file
View file

@ -0,0 +1,15 @@
.tmp
.idea
.vscode
.npmrc
.nvmrc
node_modules/
build/
build-tools/
reports/
coverage/
dist/
**/*.md
**/*.yml
package.json
package-lock.json

10
.prettierrc Normal file
View file

@ -0,0 +1,10 @@
{
"printWidth": 140,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "always"
}

22
package-lock.json generated
View file

@ -41,6 +41,7 @@
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"concurrently": "^7.0.0", "concurrently": "^7.0.0",
"nodemon": "^2.0.15", "nodemon": "^2.0.15",
"prettier": "^2.6.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "^4.5.5" "typescript": "^4.5.5"
} }
@ -3644,6 +3645,21 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-ms": { "node_modules/pretty-ms": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",
@ -7983,6 +7999,12 @@
"integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=",
"dev": true "dev": true
}, },
"prettier": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.0.tgz",
"integrity": "sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A==",
"dev": true
},
"pretty-ms": { "pretty-ms": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz",

View file

@ -26,6 +26,7 @@
"@types/uuid": "^8.3.4", "@types/uuid": "^8.3.4",
"concurrently": "^7.0.0", "concurrently": "^7.0.0",
"nodemon": "^2.0.15", "nodemon": "^2.0.15",
"prettier": "^2.6.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"typescript": "^4.5.5" "typescript": "^4.5.5"
}, },

View file

@ -1,18 +1,18 @@
import * as path from "path"; import * as path from 'path';
import * as uuid from "uuid"; import * as uuid from 'uuid';
import { Express } from "express"; import { Express } from 'express';
import * as express from "express"; import * as express from 'express';
import * as winston from "winston"; import * as winston from 'winston';
import * as morgan from "morgan"; import * as morgan from 'morgan';
import { Pool, createPool } from "mysql2/promise"; import { Pool, createPool } from 'mysql2/promise';
import { engine as handlebarsEngine } from "express-handlebars"; import { engine as handlebarsEngine } from 'express-handlebars';
import { Config, IRealmConfig } from "./Config"; import { Config, IRealmConfig } from './Config';
import { DbcManager } from "./data/DbcReader"; import { DbcManager } from './data/DbcReader';
import { CharacterCustomization } from "./data/CharacterCustomization"; import { CharacterCustomization } from './data/CharacterCustomization';
import { IndexController } from "./controllers/IndexController"; import { IndexController } from './controllers/IndexController';
import { CharacterController } from "./controllers/CharacterController"; import { CharacterController } from './controllers/CharacterController';
import { GuildController } from "./controllers/GuildController"; import { GuildController } from './controllers/GuildController';
export class Armory { export class Armory {
public characterCustomization: CharacterCustomization; public characterCustomization: CharacterCustomization;
@ -31,30 +31,30 @@ export class Armory {
this.characterCustomization = new CharacterCustomization(); this.characterCustomization = new CharacterCustomization();
this.charsDbs = {}; this.charsDbs = {};
this.logger = winston.createLogger({ this.logger = winston.createLogger({
level: "info", level: 'info',
format: winston.format.combine( format: winston.format.combine(
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss:ms' }),
winston.format.printf((info) => `[${info.timestamp}] [${info.level.toUpperCase()}]: ${info.message}`), winston.format.printf((info) => `[${info.timestamp}] [${info.level.toUpperCase()}]: ${info.message}`),
), ),
transports: [ transports: [
new winston.transports.Console({ level: "debug" }), new winston.transports.Console({ level: 'debug' }),
new winston.transports.File({ filename: path.join("logs", "armory.error.log"), level: "error" }), new winston.transports.File({ filename: path.join('logs', 'armory.error.log'), level: 'error' }),
new winston.transports.File({ filename: path.join("logs", "armory.combined.log"), level: "http" }), new winston.transports.File({ filename: path.join('logs', 'armory.combined.log'), level: 'http' }),
], ],
}); });
this.charsetCache = {}; this.charsetCache = {};
this.errorNames = { this.errorNames = {
400: "Bad Request", 400: 'Bad Request',
401: "Unauthorized", 401: 'Unauthorized',
403: "Forbidden", 403: 'Forbidden',
404: "Not Found", 404: 'Not Found',
500: "Internal Server Error", 500: 'Internal Server Error',
}; };
this.errorDescriptions = { this.errorDescriptions = {
400: "Invalid request.", 400: 'Invalid request.',
404: "Sorry, we could not find what you were looking for.", 404: 'Sorry, we could not find what you were looking for.',
500: "An unexpected internal error has occurred. Please contact the site owner.", 500: 'An unexpected internal error has occurred. Please contact the site owner.',
}; };
} }
@ -62,21 +62,21 @@ export class Armory {
const app: Express = express(); const app: Express = express();
const listenPort = 48733; const listenPort = 48733;
this.logger.info("Loading config..."); this.logger.info('Loading config...');
this.config = await Config.load(this.logger); this.config = await Config.load(this.logger);
this.logger.info("Loading data files..."); this.logger.info('Loading data files...');
if (this.config.loadDbcs) { if (this.config.loadDbcs) {
await this.dbc.loadAllFiles(); await this.dbc.loadAllFiles();
} }
await this.characterCustomization.loadData(); await this.characterCustomization.loadData();
this.logger.info("Connecting to databases..."); this.logger.info('Connecting to databases...');
this.worldDb = createPool(this.config.worldDatabase); this.worldDb = createPool(this.config.worldDatabase);
for (const realm of this.config.realms) { for (const realm of this.config.realms) {
this.charsDbs[realm.name.toLowerCase()] = createPool(realm.charactersDatabase); this.charsDbs[realm.name.toLowerCase()] = createPool(realm.charactersDatabase);
} }
this.logger.info("Starting server..."); this.logger.info('Starting server...');
const locals = { const locals = {
aowow: this.config.aowowUrl, aowow: this.config.aowowUrl,
@ -92,66 +92,71 @@ export class Armory {
} }
app.locals.locals = locals; app.locals.locals = locals;
app.engine(".hbs", handlebarsEngine({ app.engine(
extname: "hbs", '.hbs',
partialsDir: path.join(process.cwd(), "static", "partials"), handlebarsEngine({
layoutsDir: path.join(process.cwd(), "static"), extname: 'hbs',
defaultLayout: "layout.hbs", partialsDir: path.join(process.cwd(), 'static', 'partials'),
layoutsDir: path.join(process.cwd(), 'static'),
defaultLayout: 'layout.hbs',
helpers: { helpers: {
...require("handlebars-helpers")(), ...require('handlebars-helpers')(),
}, },
})); }),
app.set("view engine", "handlebars"); );
app.set("views", path.join(process.cwd(), "static")); app.set('view engine', 'handlebars');
app.set('views', path.join(process.cwd(), 'static'));
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
req.id = uuid.v4(); req.id = uuid.v4();
next(); next();
}); });
morgan.token("id", (req: express.Request) => { morgan.token('id', (req: express.Request) => {
return req.id; return req.id;
}); });
morgan.token("ip", (req: express.Request) => { morgan.token('ip', (req: express.Request) => {
const forwardedFor = req.headers["x-forwarded-for"]; const forwardedFor = req.headers['x-forwarded-for'];
if (forwardedFor) { if (forwardedFor) {
if (typeof forwardedFor === "string") { if (typeof forwardedFor === 'string') {
return forwardedFor; return forwardedFor;
} }
return forwardedFor.join(", "); return forwardedFor.join(', ');
} }
return req.socket.remoteAddress; return req.socket.remoteAddress;
}); });
app.use(morgan(":method :url :status - ID :id - IP :ip - :response-time ms", { app.use(
morgan(':method :url :status - ID :id - IP :ip - :response-time ms', {
stream: { stream: {
write: (msg) => this.logger.http(msg.trim()), write: (msg) => this.logger.http(msg.trim()),
}, },
})); }),
);
app.use("/js", express.static(`static/js`)); app.use('/js', express.static(`static/js`));
app.use("/css", express.static(`static/css`)); app.use('/css', express.static(`static/css`));
app.use("/img", express.static(`static/img`)); app.use('/img', express.static(`static/img`));
app.use("/data/mo3", express.static(`data/mo3`)); app.use('/data/mo3', express.static(`data/mo3`));
app.use("/data/meta", express.static(`data/meta`)); app.use('/data/meta', express.static(`data/meta`));
app.use("/data/bone", express.static(`data/bone`)); app.use('/data/bone', express.static(`data/bone`));
app.use("/data/textures", express.static(`data/textures`)); app.use('/data/textures', express.static(`data/textures`));
app.use("/data/background.png", express.static(`data/modelviewer-background.png`)); app.use('/data/background.png', express.static(`data/modelviewer-background.png`));
const indexController = new IndexController(this); const indexController = new IndexController(this);
app.get("/", this.wrapRoute(indexController.index.bind(indexController))); app.get('/', this.wrapRoute(indexController.index.bind(indexController)));
app.get("/search", this.wrapRoute(indexController.search.bind(indexController))); app.get('/search', this.wrapRoute(indexController.search.bind(indexController)));
const charsController = new CharacterController(this); const charsController = new CharacterController(this);
await charsController.load(); await charsController.load();
app.get("/character/:realm/:name", this.wrapRoute(charsController.character.bind(charsController))); app.get('/character/:realm/:name', this.wrapRoute(charsController.character.bind(charsController)));
app.get("/character/:realm/:name/talents", this.wrapRoute(charsController.talents.bind(charsController))); app.get('/character/:realm/:name/talents', this.wrapRoute(charsController.talents.bind(charsController)));
app.get("/character/:realm/:name/achievements", this.wrapRoute(charsController.achievements.bind(charsController))); app.get('/character/:realm/:name/achievements', this.wrapRoute(charsController.achievements.bind(charsController)));
app.get("/character/:realm/:character/achievements/data", this.wrapRoute(charsController.achievementsData.bind(charsController))); app.get('/character/:realm/:character/achievements/data', this.wrapRoute(charsController.achievementsData.bind(charsController)));
app.get("/character/:realm/:name/pvp", this.wrapRoute(charsController.pvp.bind(charsController))); app.get('/character/:realm/:name/pvp', this.wrapRoute(charsController.pvp.bind(charsController)));
const guildsController = new GuildController(this); const guildsController = new GuildController(this);
app.get("/guild/:realm/:name", this.wrapRoute(guildsController.guild.bind(guildsController))); app.get('/guild/:realm/:name', this.wrapRoute(guildsController.guild.bind(guildsController)));
app.get("/guild/:realm/:guild/members", this.wrapRoute(guildsController.members.bind(guildsController))); app.get('/guild/:realm/:guild/members', this.wrapRoute(guildsController.members.bind(guildsController)));
app.use((err, req: express.Request, res: express.Response, next: express.NextFunction) => { app.use((err, req: express.Request, res: express.Response, next: express.NextFunction) => {
// Error handler // Error handler
@ -162,11 +167,11 @@ export class Armory {
} }
let status = 500; let status = 500;
if (typeof err === "number") { if (typeof err === 'number') {
status = err; status = err;
} }
res.status(status).render("error.hbs", this.getErrorViewData(status, req)); res.status(status).render('error.hbs', this.getErrorViewData(status, req));
}); });
app.use((req: express.Request, res: express.Response, next: express.NextFunction) => { app.use((req: express.Request, res: express.Response, next: express.NextFunction) => {
@ -174,21 +179,21 @@ export class Armory {
res.status(404); res.status(404);
// Respond with html page // Respond with html page
if (req.accepts("html")) { if (req.accepts('html')) {
return res.render("error.hbs", this.getErrorViewData(404, req)); return res.render('error.hbs', this.getErrorViewData(404, req));
} }
// Respond with json // Respond with json
if (req.accepts("json")) { if (req.accepts('json')) {
return res.json({ error: this.errorNames[404] }); return res.json({ error: this.errorNames[404] });
} }
// Default to plain-text // Default to plain-text
res.type("txt").send(this.errorNames[404]); res.type('txt').send(this.errorNames[404]);
}); });
this.gc(); this.gc();
app.listen(listenPort, "0.0.0.0", () => { app.listen(listenPort, '0.0.0.0', () => {
this.logger.info(`Server is listening on 0.0.0.0:${listenPort}.`); this.logger.info(`Server is listening on 0.0.0.0:${listenPort}.`);
}); });
} }
@ -198,7 +203,7 @@ export class Armory {
} }
public getRealm(realm: string): IRealmConfig { public getRealm(realm: string): IRealmConfig {
return this.config.realms.find(r => r.name.toLowerCase() === realm.toLowerCase()); return this.config.realms.find((r) => r.name.toLowerCase() === realm.toLowerCase());
} }
public async getDatabaseCharset(realm: string): Promise<string> { public async getDatabaseCharset(realm: string): Promise<string> {
@ -246,8 +251,8 @@ export class Armory {
private getErrorViewData(status: number, req: express.Request) { private getErrorViewData(status: number, req: express.Request) {
return { return {
status, status,
name: this.errorNames[status] || "An error occurred", name: this.errorNames[status] || 'An error occurred',
description: this.errorDescriptions[status] || "", description: this.errorDescriptions[status] || '',
reqId: req.id, reqId: req.id,
}; };
} }

View file

@ -1,7 +1,7 @@
import * as fs from "fs"; import * as fs from 'fs';
const fsp = fs.promises; const fsp = fs.promises;
import * as winston from "winston"; import * as winston from 'winston';
export interface IDatabaseConfig { export interface IDatabaseConfig {
host: string; host: string;
@ -35,12 +35,12 @@ export class Config {
public worldDatabase: IDatabaseConfig; public worldDatabase: IDatabaseConfig;
public dbQueryTimeout: number; public dbQueryTimeout: number;
private static envPrefix: string = "ACORE_ARMORY"; private static envPrefix: string = 'ACORE_ARMORY';
private static checkedMissingField: boolean = false; private static checkedMissingField: boolean = false;
public static async load(logger: winston.Logger): Promise<Config> { public static async load(logger: winston.Logger): Promise<Config> {
try { try {
await fsp.access("config.json"); await fsp.access('config.json');
return await Config.loadFromFile(logger); return await Config.loadFromFile(logger);
} catch (err) { } catch (err) {
return await Config.loadFromEnv(logger); return await Config.loadFromEnv(logger);
@ -48,11 +48,11 @@ export class Config {
} }
private static async loadFromFile(logger: winston.Logger): Promise<Config> { private static async loadFromFile(logger: winston.Logger): Promise<Config> {
const json: Buffer = await fsp.readFile("config.json"); const json: Buffer = await fsp.readFile('config.json');
const config = JSON.parse(json.toString()) as Config; const config = JSON.parse(json.toString()) as Config;
if (!Config.checkedMissingField) { if (!Config.checkedMissingField) {
const defaultConfigJson = await fsp.readFile("config.default.json"); const defaultConfigJson = await fsp.readFile('config.default.json');
const defaultConfig = JSON.parse(defaultConfigJson.toString()); const defaultConfig = JSON.parse(defaultConfigJson.toString());
Config.checkAllMissingFields(logger, config, defaultConfig); Config.checkAllMissingFields(logger, config, defaultConfig);
Config.checkedMissingField = true; Config.checkedMissingField = true;
@ -63,16 +63,16 @@ export class Config {
private static async loadFromEnv(logger: winston.Logger): Promise<Config> { private static async loadFromEnv(logger: winston.Logger): Promise<Config> {
const config = {}; const config = {};
const json = await fsp.readFile("config.default.json"); const json = await fsp.readFile('config.default.json');
const defaultConfig = JSON.parse(json.toString()); const defaultConfig = JSON.parse(json.toString());
Config.loadObjFromEnv(logger, config, defaultConfig); Config.loadObjFromEnv(logger, config, defaultConfig);
Config.checkedMissingField = true; Config.checkedMissingField = true;
return config as Config; return config as Config;
} }
private static loadObjFromEnv(logger: winston.Logger, obj: object, model: object, parentName: string = "") { private static loadObjFromEnv(logger: winston.Logger, obj: object, model: object, parentName: string = '') {
if (parentName !== "") { if (parentName !== '') {
parentName += "."; parentName += '.';
} }
for (const field in model) { for (const field in model) {
@ -82,7 +82,7 @@ export class Config {
if (Array.isArray(model[field])) { if (Array.isArray(model[field])) {
obj[field] = Config.loadArrayFromEnv(logger, model[field][0], parentName + field); obj[field] = Config.loadArrayFromEnv(logger, model[field][0], parentName + field);
} else if (typeof model[field] === "object") { } else if (typeof model[field] === 'object') {
obj[field] = {}; obj[field] = {};
Config.loadObjFromEnv(logger, obj[field], model[field], parentName + field); Config.loadObjFromEnv(logger, obj[field], model[field], parentName + field);
} else if (!obj.hasOwnProperty(field)) { } else if (!obj.hasOwnProperty(field)) {
@ -96,23 +96,23 @@ export class Config {
} }
} }
private static loadArrayFromEnv(logger: winston.Logger, model: any, parentName: string = ""): any[] { private static loadArrayFromEnv(logger: winston.Logger, model: any, parentName: string = ''): any[] {
if (parentName !== "") { if (parentName !== '') {
parentName += "."; parentName += '.';
} }
const arr = []; const arr = [];
let i = 0; let i = 0;
while (true) { while (true) {
const key = Config.getEnvKey(parentName + i); const key = Config.getEnvKey(parentName + i);
const found = Object.keys(process.env).some(k => k.startsWith(key)); const found = Object.keys(process.env).some((k) => k.startsWith(key));
if (!found) { if (!found) {
break; break;
} }
if (Array.isArray(model)) { if (Array.isArray(model)) {
arr.push(Config.loadArrayFromEnv(logger, model[0], parentName + i)); arr.push(Config.loadArrayFromEnv(logger, model[0], parentName + i));
} else if (typeof model === "object") { } else if (typeof model === 'object') {
const obj = {}; const obj = {};
Config.loadObjFromEnv(logger, obj, model, parentName + i); Config.loadObjFromEnv(logger, obj, model, parentName + i);
if (Object.keys(obj).length > 0) { if (Object.keys(obj).length > 0) {
@ -131,34 +131,38 @@ export class Config {
} }
private static getEnvKey(key: string): string { private static getEnvKey(key: string): string {
return Config.envPrefix + "_" + key return (
.replace(/\./g, "__") Config.envPrefix +
.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`) '_' +
.toUpperCase(); key
.replace(/\./g, '__')
.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
.toUpperCase()
);
} }
private static parseEnvValue(value: string, model: any): any { private static parseEnvValue(value: string, model: any): any {
const type = typeof model; const type = typeof model;
const lower = value.toLowerCase(); const lower = value.toLowerCase();
if (type === "boolean") { if (type === 'boolean') {
return lower === "true" || value === "1"; return lower === 'true' || value === '1';
} }
if (type === "number") { if (type === 'number') {
return parseFloat(value); return parseFloat(value);
} }
return value; return value;
} }
private static checkAllMissingFields(logger: winston.Logger, obj: object, model: object, parentName: string = "") { private static checkAllMissingFields(logger: winston.Logger, obj: object, model: object, parentName: string = '') {
const missing = Config.hasMissingFields(obj, model); const missing = Config.hasMissingFields(obj, model);
if (parentName !== "") { if (parentName !== '') {
parentName += "."; parentName += '.';
} }
for (const field of missing) { for (const field of missing) {
logger.warn(`Field ${parentName}${field} is missing from config.json!`); logger.warn(`Field ${parentName}${field} is missing from config.json!`);
} }
for (const key in model) { for (const key in model) {
if (typeof model[key] === "object" && obj.hasOwnProperty(key)) { if (typeof model[key] === 'object' && obj.hasOwnProperty(key)) {
Config.checkAllMissingFields(logger, obj[key], model[key], parentName + key); Config.checkAllMissingFields(logger, obj[key], model[key], parentName + key);
} }
} }
@ -173,4 +177,4 @@ export class Config {
} }
return missing; return missing;
} }
}; }

View file

@ -1,5 +1,5 @@
import { Pool } from "mysql2/promise"; import { Pool } from 'mysql2/promise';
import { Query } from "express-serve-static-core"; import { Query } from 'express-serve-static-core';
export interface IResult { export interface IResult {
recordsTotal: number; recordsTotal: number;
@ -22,7 +22,7 @@ export interface IColumnJoin {
table2: string; table2: string;
column2: string; column2: string;
database2?: string; database2?: string;
kind: "INNER" | "FULL OUTER" | "LEFT" | "RIGHT"; kind: 'INNER' | 'FULL OUTER' | 'LEFT' | 'RIGHT';
} }
export class DataTablesSsp { export class DataTablesSsp {
@ -38,52 +38,52 @@ export class DataTablesSsp {
private start: number; private start: number;
private length: number; private length: number;
private _order: { private _order: {
column: number, column: number;
dir: string, dir: string;
}[]; }[];
private columns: { private columns: {
data: number, data: number;
name: string, name: string;
searchable: boolean, searchable: boolean;
orderable: boolean, orderable: boolean;
search: { search: {
value: string, value: string;
regex: boolean, regex: boolean;
} };
}[]; }[];
private search: { private search: {
value: string, value: string;
regex: boolean, regex: boolean;
}; };
private wheres: string[] = []; private wheres: string[] = [];
private filterBindings: (string | number)[] = []; private filterBindings: (string | number)[] = [];
private customBindings: (string | number)[] = []; private customBindings: (string | number)[] = [];
private filterWhereSql: string = "1"; private filterWhereSql: string = '1';
private customWhereSql: string = "1"; private customWhereSql: string = '1';
private limitSql: string = ""; private limitSql: string = '';
private orderSql: string = ""; private orderSql: string = '';
private joinSql: string = ""; private joinSql: string = '';
public constructor(query: Query, db: Pool, table: string, primaryKey: string, columnSettings: IColumnSettings[]) { public constructor(query: Query, db: Pool, table: string, primaryKey: string, columnSettings: IColumnSettings[]) {
this.start = parseInt(query.start as string, 10); this.start = parseInt(query.start as string, 10);
this.length = parseInt(query.length as string, 10); this.length = parseInt(query.length as string, 10);
this.draw = parseInt(query.draw as string, 10); this.draw = parseInt(query.draw as string, 10);
this._order = (query.order as { column: string, dir: string }[]) this._order = (query.order as { column: string; dir: string }[]).map((order) => {
.map(order => { return { column: parseInt(order.column, 10), dir: order.dir, }; }); return { column: parseInt(order.column, 10), dir: order.dir };
this.columns = (query.columns as { data: string, name: string, searchable: string, orderable: string, search: any }[]) });
.map(column => { this.columns = (query.columns as { data: string; name: string; searchable: string; orderable: string; search: any }[]).map((column) => {
return { return {
data: parseInt(column.data, 10), data: parseInt(column.data, 10),
name: column.name, name: column.name,
searchable: column.searchable === "true", searchable: column.searchable === 'true',
orderable: column.orderable === "true", orderable: column.orderable === 'true',
search: { value: column.search.value, regex: column.search.regex === "true" }, search: { value: column.search.value, regex: column.search.regex === 'true' },
}; };
}); });
this.search = { this.search = {
value: (query.search as any).value as string, value: (query.search as any).value as string,
regex: (query.search as any).regex === "true", regex: (query.search as any).regex === 'true',
}; };
this.db = db; this.db = db;
@ -93,7 +93,7 @@ export class DataTablesSsp {
} }
private colSettingsToStr(colSettings: IColumnSettings) { private colSettingsToStr(colSettings: IColumnSettings) {
const db = colSettings.database ? ("`" + colSettings.database + "`.") : ""; const db = colSettings.database ? '`' + colSettings.database + '`.' : '';
return `${db}\`${colSettings.table || this.table}\`.\`${colSettings.name}\``; return `${db}\`${colSettings.table || this.table}\`.\`${colSettings.name}\``;
} }
@ -122,7 +122,7 @@ export class DataTablesSsp {
orderBy.push(`\`${this.table}\`.\`${this.primaryKey}\``); orderBy.push(`\`${this.table}\`.\`${this.primaryKey}\``);
if (orderBy.length > 0) { if (orderBy.length > 0) {
this.orderSql = "ORDER BY " + orderBy.join(", "); this.orderSql = 'ORDER BY ' + orderBy.join(', ');
} }
return this; return this;
@ -130,7 +130,7 @@ export class DataTablesSsp {
private join() { private join() {
for (const join of this.joins) { for (const join of this.joins) {
const db2 = join.database2 ? "`" + join.database2 + "`." : ""; const db2 = join.database2 ? '`' + join.database2 + '`.' : '';
this.joinSql += `${join.kind} JOIN ${db2}\`${join.table2}\` ON ${db2}\`${join.table2}\`.\`${join.column2}\` = \`${join.table1}\`.\`${join.column1}\`\n`; this.joinSql += `${join.kind} JOIN ${db2}\`${join.table2}\` ON ${db2}\`${join.table2}\`.\`${join.column2}\` = \`${join.table1}\`.\`${join.column1}\`\n`;
} }
@ -148,26 +148,23 @@ export class DataTablesSsp {
} }
const colSettings = this.columnSettings[col.data]; const colSettings = this.columnSettings[col.data];
const collate = colSettings.collation !== undefined ? `COLLATE ${colSettings.collation} ` : ""; const collate = colSettings.collation !== undefined ? `COLLATE ${colSettings.collation} ` : '';
filterWheres.push(`${this.colSettingsToStr(colSettings)} ${collate}LIKE ?`); filterWheres.push(`${this.colSettingsToStr(colSettings)} ${collate}LIKE ?`);
this.filterBindings.push(`%${this.search.value}%`); this.filterBindings.push(`%${this.search.value}%`);
} }
if (filterWheres.length > 0) { if (filterWheres.length > 0) {
this.filterWhereSql = "(" + filterWheres.map(w => `(${w})`).join(" OR ") + ")"; this.filterWhereSql = '(' + filterWheres.map((w) => `(${w})`).join(' OR ') + ')';
} }
} }
this.customWhereSql = this.wheres.map(w => `(${w})`).join(" AND "); this.customWhereSql = this.wheres.map((w) => `(${w})`).join(' AND ');
return this; return this;
} }
public sql(): string { public sql(): string {
const columns = [ const columns = [...this.columnSettings.map((c) => this.colSettingsToStr(c)), ...this.extraDataColumns];
...this.columnSettings.map(c => this.colSettingsToStr(c)),
...this.extraDataColumns,
];
return ` return `
SELECT ${columns.join(", ")} SELECT ${columns.join(', ')}
FROM ${this.table} FROM ${this.table}
${this.joinSql} ${this.joinSql}
WHERE WHERE
@ -199,10 +196,7 @@ export class DataTablesSsp {
} }
public async run(queryTimeout: number = 10_000): Promise<IResult> { public async run(queryTimeout: number = 10_000): Promise<IResult> {
this.limit() this.limit().order().join().filter();
.order()
.join()
.filter();
const bindings = [...this.filterBindings, ...this.customBindings]; const bindings = [...this.filterBindings, ...this.customBindings];
@ -226,7 +220,7 @@ export class DataTablesSsp {
values: bindings, values: bindings,
timeout: queryTimeout, timeout: queryTimeout,
}); });
rows = (rows as any[][]).map(row => { rows = (rows as any[][]).map((row) => {
for (let i = 0; i < this.columnSettings.length; ++i) { for (let i = 0; i < this.columnSettings.length; ++i) {
const col = this.columnSettings[i]; const col = this.columnSettings[i];
if (col.formatter !== undefined) { if (col.formatter !== undefined) {

View file

@ -13,28 +13,28 @@ export interface IEmblem {
export class Utils { export class Utils {
public static raceNames = { public static raceNames = {
1: "human", 1: 'human',
2: "orc", 2: 'orc',
3: "dwarf", 3: 'dwarf',
4: "nightelf", 4: 'nightelf',
5: "scourge", 5: 'scourge',
6: "tauren", 6: 'tauren',
7: "gnome", 7: 'gnome',
8: "troll", 8: 'troll',
10: "bloodelf", 10: 'bloodelf',
11: "draenei", 11: 'draenei',
}; };
public static classNames = { public static classNames = {
1: "warrior", 1: 'warrior',
2: "paladin", 2: 'paladin',
3: "hunter", 3: 'hunter',
4: "rogue", 4: 'rogue',
5: "priest", 5: 'priest',
6: "deathknight", 6: 'deathknight',
7: "shaman", 7: 'shaman',
8: "mage", 8: 'mage',
9: "warlock", 9: 'warlock',
11: "druid", 11: 'druid',
}; };
public static getFactionFromRaceId(race: number): EFaction { public static getFactionFromRaceId(race: number): EFaction {
@ -44,11 +44,11 @@ export class Utils {
public static makeEmblemObject(obj: any, padWithZeroes: boolean = true): IEmblem { public static makeEmblemObject(obj: any, padWithZeroes: boolean = true): IEmblem {
const padLength = padWithZeroes ? 2 : 0; const padLength = padWithZeroes ? 2 : 0;
return { return {
icon: obj.emblemStyle.toString().padStart(padLength, "0"), icon: obj.emblemStyle.toString().padStart(padLength, '0'),
iconColor: obj.emblemColor.toString().padStart(padLength, "0"), iconColor: obj.emblemColor.toString().padStart(padLength, '0'),
border: obj.borderStyle.toString().padStart(padLength, "0"), border: obj.borderStyle.toString().padStart(padLength, '0'),
borderColor: obj.borderColor.toString().padStart(padLength, "0"), borderColor: obj.borderColor.toString().padStart(padLength, '0'),
background: obj.background.toString().padStart(padLength, "0"), background: obj.background.toString().padStart(padLength, '0'),
}; };
} }
} }

View file

@ -1,10 +1,10 @@
import * as express from "express"; import * as express from 'express';
import { RowDataPacket } from "mysql2/promise"; import { RowDataPacket } from 'mysql2/promise';
import { Utils } from "../Utils"; import { Utils } from '../Utils';
import { Armory } from "../Armory"; import { Armory } from '../Armory';
import { IRealmConfig } from "../Config"; import { IRealmConfig } from '../Config';
import { IAchievement } from "../data/DbcReader"; import { IAchievement } from '../data/DbcReader';
interface ICharacterData { interface ICharacterData {
guid: number; guid: number;
@ -47,28 +47,28 @@ interface IMount {
const ItemClassGem = 3; const ItemClassGem = 3;
const SpellMechanicMounted = 21; const SpellMechanicMounted = 21;
const RaceDisplayName = { const RaceDisplayName = {
1: "Human", 1: 'Human',
2: "Orc", 2: 'Orc',
3: "Dwarf", 3: 'Dwarf',
4: "Night Elf", 4: 'Night Elf',
5: "Undead", 5: 'Undead',
6: "Tauren", 6: 'Tauren',
7: "Gnome", 7: 'Gnome',
8: "Troll", 8: 'Troll',
10: "Blood Elf", 10: 'Blood Elf',
11: "Draenei", 11: 'Draenei',
}; };
const ClassDisplayName = { const ClassDisplayName = {
1: "Warrior", 1: 'Warrior',
2: "Paladin", 2: 'Paladin',
3: "Hunter", 3: 'Hunter',
4: "Rogue", 4: 'Rogue',
5: "Priest", 5: 'Priest',
6: "Death Knight", 6: 'Death Knight',
7: "Shaman", 7: 'Shaman',
8: "Mage", 8: 'Mage',
9: "Warlock", 9: 'Warlock',
11: "Druid", 11: 'Druid',
}; };
export class CharacterController { export class CharacterController {
@ -90,7 +90,7 @@ export class CharacterController {
this.itemInventoryTypes = {}; this.itemInventoryTypes = {};
const itemsRetail = await this.armory.dbc.itemRetail().toArray(); const itemsRetail = await this.armory.dbc.itemRetail().toArray();
for await (const item of this.armory.dbc.item()) { for await (const item of this.armory.dbc.item()) {
const retailItem = itemsRetail.find(row => row.id === item.id); const retailItem = itemsRetail.find((row) => row.id === item.id);
if (retailItem !== undefined) { if (retailItem !== undefined) {
this.itemInventoryTypes[item.id] = retailItem.inventoryType; this.itemInventoryTypes[item.id] = retailItem.inventoryType;
} }
@ -109,7 +109,7 @@ export class CharacterController {
} }
this.gemItems = {}; this.gemItems = {};
for await (const row of this.armory.dbc.item().filter(item => item.classId === ItemClassGem)) { for await (const row of this.armory.dbc.item().filter((item) => item.classId === ItemClassGem)) {
this.gemItems[row.id] = true; this.gemItems[row.id] = true;
} }
@ -120,28 +120,29 @@ export class CharacterController {
this.itemSocketBonuses = {}; this.itemSocketBonuses = {};
let [rows, fields] = await this.armory.worldDb.query({ let [rows, fields] = await this.armory.worldDb.query({
sql: "SELECT entry, socketBonus FROM item_template WHERE socketBonus <> 0", sql: 'SELECT entry, socketBonus FROM item_template WHERE socketBonus <> 0',
timeout: this.armory.config.dbQueryTimeout, timeout: this.armory.config.dbQueryTimeout,
}); });
for (const row of rows as RowDataPacket[]) { for (const row of rows as RowDataPacket[]) {
this.itemSocketBonuses[row.entry] = row.socketBonus; this.itemSocketBonuses[row.entry] = row.socketBonus;
} }
const mountSpells = await this.armory.dbc.spell() const mountSpells = await this.armory.dbc
.filter(m => m.mechanic === SpellMechanicMounted) .spell()
.filter((m) => m.mechanic === SpellMechanicMounted)
.toArray(); .toArray();
this.mountSpells = mountSpells.map(spell => spell.id); this.mountSpells = mountSpells.map((spell) => spell.id);
this.mountBySpellId = {}; this.mountBySpellId = {};
for (const spell of mountSpells) { for (const spell of mountSpells) {
const mount = await this.armory.dbc.mount().find(m => m.sourceSpellId === spell.id); const mount = await this.armory.dbc.mount().find((m) => m.sourceSpellId === spell.id);
const icon = await this.armory.dbc.spellIcon().find(icon => icon.id === spell.spellIconId); const icon = await this.armory.dbc.spellIcon().find((icon) => icon.id === spell.spellIconId);
if (mount !== undefined) { if (mount !== undefined) {
const display = await this.armory.dbc.mountDisplay().find(d => d.mountId === mount.id); const display = await this.armory.dbc.mountDisplay().find((d) => d.mountId === mount.id);
if (display !== undefined) { if (display !== undefined) {
this.mountBySpellId[spell.id] = { this.mountBySpellId[spell.id] = {
creatureDisplayId: display.creatureDisplayInfoId, creatureDisplayId: display.creatureDisplayInfoId,
spell: spell.id, spell: spell.id,
icon: this.processSpellIconTexture(icon?.textureFilename ?? ""), icon: this.processSpellIconTexture(icon?.textureFilename ?? ''),
}; };
} }
} }
@ -171,7 +172,7 @@ export class CharacterController {
const equipmentData = await this.getEquipmentData(realmName, charData.guid); const equipmentData = await this.getEquipmentData(realmName, charData.guid);
const customization = this.getCustomizationOptions(charData); const customization = this.getCustomizationOptions(charData);
const equipment = equipmentData.map(row => { const equipment = equipmentData.map((row) => {
(row as any).icon = this.itemIcons[row.itemEntry]; (row as any).icon = this.itemIcons[row.itemEntry];
(row as any).gems = this.getGemsFromEnchantments(row.enchantments); (row as any).gems = this.getGemsFromEnchantments(row.enchantments);
(row as any).enchantments = this.filterEnchantments(row.itemEntry, row.enchantments); (row as any).enchantments = this.filterEnchantments(row.itemEntry, row.enchantments);
@ -179,7 +180,7 @@ export class CharacterController {
}); });
const mounts = await this.getMounts(realmName, charData.guid); const mounts = await this.getMounts(realmName, charData.guid);
res.render("character.hbs", { res.render('character.hbs', {
title: `Armory - ${charData.name}`, title: `Armory - ${charData.name}`,
...this.makeSharedDataObject(realm, charData), ...this.makeSharedDataObject(realm, charData),
data: { data: {
@ -213,7 +214,7 @@ export class CharacterController {
return next(404); return next(404);
} }
res.render("character-talents.hbs", { res.render('character-talents.hbs', {
title: `Armory - ${charData.name} - Talents`, title: `Armory - ${charData.name} - Talents`,
...this.makeSharedDataObject(realm, charData), ...this.makeSharedDataObject(realm, charData),
data: { data: {
@ -240,7 +241,7 @@ export class CharacterController {
return next(404); return next(404);
} }
res.render("character-achievements.hbs", { res.render('character-achievements.hbs', {
title: `Armory - ${charData.name} - Achievements`, title: `Armory - ${charData.name} - Achievements`,
...this.makeSharedDataObject(realm, charData), ...this.makeSharedDataObject(realm, charData),
}); });
@ -264,7 +265,7 @@ export class CharacterController {
res.json({ res.json({
categories: await this.armory.dbc.achievementCategory().toArray(), categories: await this.armory.dbc.achievementCategory().toArray(),
...await this.getAchievements(realm.name, charData), ...(await this.getAchievements(realm.name, charData)),
}); });
} }
@ -284,7 +285,7 @@ export class CharacterController {
return next(404); return next(404);
} }
res.render("character-pvp.hbs", { res.render('character-pvp.hbs', {
title: `Armory - ${charData.name} - PvP`, title: `Armory - ${charData.name} - PvP`,
...this.makeSharedDataObject(realm, charData), ...this.makeSharedDataObject(realm, charData),
faction: Utils.getFactionFromRaceId(charData.race), faction: Utils.getFactionFromRaceId(charData.race),
@ -307,7 +308,7 @@ export class CharacterController {
} }
private async getCharacterData(realm: IRealmConfig, character: string | number): Promise<ICharacterData> { private async getCharacterData(realm: IRealmConfig, character: string | number): Promise<ICharacterData> {
const where = typeof character === "string" ? "LOWER(`characters`.`name`) = LOWER(?)" : "`characters`.`guid` = ?"; const where = typeof character === 'string' ? 'LOWER(`characters`.`name`) = LOWER(?)' : '`characters`.`guid` = ?';
const [rows, fields] = await this.armory.getCharactersDb(realm.name).query({ const [rows, fields] = await this.armory.getCharactersDb(realm.name).query({
sql: ` sql: `
SELECT \`characters\`.\`guid\`, \`characters\`.\`name\`, \`race\`, \`class\`, \`gender\`, \`level\`, \`skin\`, \`face\`, \`hairStyle\`, \`hairColor\`, \`facialStyle\`, \`playerFlags\`, \`online\`, \`guild\`.\`name\` AS \`guild\` SELECT \`characters\`.\`guid\`, \`characters\`.\`name\`, \`race\`, \`class\`, \`gender\`, \`level\`, \`skin\`, \`face\`, \`hairStyle\`, \`hairColor\`, \`facialStyle\`, \`playerFlags\`, \`online\`, \`guild\`.\`name\` AS \`guild\`
@ -344,7 +345,7 @@ export class CharacterController {
const data = rows as RowDataPacket[] as IEquipmentData[]; const data = rows as RowDataPacket[] as IEquipmentData[];
for (const row of data) { for (const row of data) {
const item = await this.armory.dbc.item().find(item => item.id === row.itemEntry); const item = await this.armory.dbc.item().find((item) => item.id === row.itemEntry);
row.classId = item.classId; row.classId = item.classId;
row.subclassId = item.subclassId; row.subclassId = item.subclassId;
} }
@ -363,29 +364,27 @@ export class CharacterController {
timeout: this.armory.config.dbQueryTimeout, timeout: this.armory.config.dbQueryTimeout,
}); });
return (rows as RowDataPacket[]) return (rows as RowDataPacket[]).map((row) => this.mountBySpellId[row.spell]).filter((m) => m !== undefined);
.map(row => this.mountBySpellId[row.spell])
.filter(m => m !== undefined);
} }
private async getModelViewerItems(equipmentData: IEquipmentData[], charClass: number): Promise<number[][]> { private async getModelViewerItems(equipmentData: IEquipmentData[], charClass: number): Promise<number[][]> {
if (charClass !== 3) { if (charClass !== 3) {
// Keep ranged weapon only if the character is a hunter // Keep ranged weapon only if the character is a hunter
equipmentData = equipmentData.filter(row => row.slot !== 17); equipmentData = equipmentData.filter((row) => row.slot !== 17);
} }
const visibleEquipment = equipmentData. const visibleEquipment = equipmentData.filter(
filter(item => (item) =>
[0, 2, 3, 4, 5, 6, 7, 8, 9, 14, 15, 16, 17, 18].includes(item.slot) && // visible slots [0, 2, 3, 4, 5, 6, 7, 8, 9, 14, 15, 16, 17, 18].includes(item.slot) && // visible slots
item.itemEntry !== 5976 // filter out Guild Tabard (displays blank otherwise) item.itemEntry !== 5976, // filter out Guild Tabard (displays blank otherwise)
); );
const items: number[][] = []; const items: number[][] = [];
for (const equipment of visibleEquipment) { for (const equipment of visibleEquipment) {
const modifiedAppearance = await this.armory.dbc.itemModifiedAppearance().find(row => row.itemId === equipment.itemEntry); const modifiedAppearance = await this.armory.dbc.itemModifiedAppearance().find((row) => row.itemId === equipment.itemEntry);
if (modifiedAppearance === undefined) { if (modifiedAppearance === undefined) {
continue; continue;
} }
const appearance = await this.armory.dbc.itemAppearance().find(row => row.id === modifiedAppearance.itemAppearanceId); const appearance = await this.armory.dbc.itemAppearance().find((row) => row.id === modifiedAppearance.itemAppearanceId);
if (appearance === undefined) { if (appearance === undefined) {
continue; continue;
} }
@ -399,56 +398,57 @@ export class CharacterController {
private parseEnchantmentsString(enchantments: string): number[] { private parseEnchantmentsString(enchantments: string): number[] {
return enchantments return enchantments
.trim() .trim()
.split(" ") .split(' ')
.map(enchant => parseInt(enchant)) .map((enchant) => parseInt(enchant))
.filter(enchant => enchant !== 0); .filter((enchant) => enchant !== 0);
} }
private getGemsFromEnchantments(enchantments: string): number[] { private getGemsFromEnchantments(enchantments: string): number[] {
return this.parseEnchantmentsString(enchantments) return this.parseEnchantmentsString(enchantments)
.filter(enchant => enchant in this.enchantSrcItems && this.enchantSrcItems[enchant] in this.gemItems) .filter((enchant) => enchant in this.enchantSrcItems && this.enchantSrcItems[enchant] in this.gemItems)
.map(enchant => this.enchantSrcItems[enchant]); .map((enchant) => this.enchantSrcItems[enchant]);
} }
private filterEnchantments(item: number, enchantments: string): number[] { private filterEnchantments(item: number, enchantments: string): number[] {
const socketBonus = this.itemSocketBonuses[item]; const socketBonus = this.itemSocketBonuses[item];
return this.parseEnchantmentsString(enchantments) return this.parseEnchantmentsString(enchantments).filter(
.filter(enchant => enchant in this.enchantSrcItems && !(this.enchantSrcItems[enchant] in this.gemItems) && enchant !== socketBonus); (enchant) => enchant in this.enchantSrcItems && !(this.enchantSrcItems[enchant] in this.gemItems) && enchant !== socketBonus,
);
} }
private getCustomizationOptions(charData: ICharacterData): ICustomizationOption[] { private getCustomizationOptions(charData: ICharacterData): ICustomizationOption[] {
const data = this.armory.characterCustomization.getCharacterCustomizationData(charData.race, charData.gender); const data = this.armory.characterCustomization.getCharacterCustomizationData(charData.race, charData.gender);
const options = []; const options = [];
const setOptionByChoiceIndex = (optionName: string, choiceIndex: number) => { const setOptionByChoiceIndex = (optionName: string, choiceIndex: number) => {
const option = data.Options.find(opt => opt.Name === optionName); const option = data.Options.find((opt) => opt.Name === optionName);
if (option !== undefined) { if (option !== undefined) {
const choice = option.Choices.find(choice => choice.OrderIndex === choiceIndex); const choice = option.Choices.find((choice) => choice.OrderIndex === choiceIndex);
if (choice !== undefined) { if (choice !== undefined) {
options.push({ optionId: option.Id, choiceId: choice.Id }); options.push({ optionId: option.Id, choiceId: choice.Id });
} }
} }
}; };
const setOptionByChoiceName = (optionName: string, choiceName: string) => { const setOptionByChoiceName = (optionName: string, choiceName: string) => {
const option = data.Options.find(opt => opt.Name === optionName); const option = data.Options.find((opt) => opt.Name === optionName);
if (option !== undefined) { if (option !== undefined) {
const choice = option.Choices.find(ch => ch.Name === choiceName); const choice = option.Choices.find((ch) => ch.Name === choiceName);
if (choice !== undefined) { if (choice !== undefined) {
options.push({ optionId: option.Id, choiceId: choice.Id }); options.push({ optionId: option.Id, choiceId: choice.Id });
} }
} }
}; };
const setOptionByChoiceId = (optionName: string, choiceId: number) => { const setOptionByChoiceId = (optionName: string, choiceId: number) => {
const option = data.Options.find(opt => opt.Name === optionName); const option = data.Options.find((opt) => opt.Name === optionName);
if (option !== undefined) { if (option !== undefined) {
options.push({ optionId: option.Id, choiceId: choiceId }); options.push({ optionId: option.Id, choiceId: choiceId });
} }
}; };
const optionMapping = { const optionMapping = {
"Face": charData.face, Face: charData.face,
"Skin Color": charData.skin, 'Skin Color': charData.skin,
"Hair Style": charData.hairStyle, 'Hair Style': charData.hairStyle,
"Hair Color": charData.hairColor, 'Hair Color': charData.hairColor,
}; };
for (const optionName in optionMapping) { for (const optionName in optionMapping) {
setOptionByChoiceIndex(optionName, optionMapping[optionName]); setOptionByChoiceIndex(optionName, optionMapping[optionName]);
@ -458,230 +458,414 @@ export class CharacterController {
switch (charData.race) { switch (charData.race) {
case 1: // Human case 1: // Human
if (charData.gender === 0) { if (charData.gender === 0) {
setOptionByChoiceName("Mustache", { 0: "Horseshoe", 1: "Brush", 2: "Horseshoe", 3: "None", 4: "Brush", 5: "Brush", 6: "Horseshoe", 7: "Brush", 8: "None" }[charData.facialStyle]); setOptionByChoiceName(
setOptionByChoiceName("Beard", { 0: "Short", 1: "Chin Puff", 2: "Soul Patch", 3: "Goatee", 4: "Goatee", 5: "None", 6: "Goatee", 7: "None", 8: "None" }[charData.facialStyle]); 'Mustache',
setOptionByChoiceName("Sideburns", { 0: "Medium", 1: "None", 2: "None", 3: "Medium", 4: "Long", 5: "Long", 6: "None", 8: "None", 7: "None" }[charData.facialStyle]); { 0: 'Horseshoe', 1: 'Brush', 2: 'Horseshoe', 3: 'None', 4: 'Brush', 5: 'Brush', 6: 'Horseshoe', 7: 'Brush', 8: 'None' }[
setOptionByChoiceName("Eyebrows", "Natural"); charData.facialStyle
setOptionByChoiceName("Face Shape", "Narrow"); ],
setOptionByChoiceId("Eye Color", { 0: 4138, 1: 4140, 2: 4130, 3: 4136, 4: 4141, 5: 4134, 6: 4130, 7: 4138, 8: 4144, 9: 4135, 10: 4126, 11: 4136 }[charData.face]); );
setOptionByChoiceName(
'Beard',
{ 0: 'Short', 1: 'Chin Puff', 2: 'Soul Patch', 3: 'Goatee', 4: 'Goatee', 5: 'None', 6: 'Goatee', 7: 'None', 8: 'None' }[
charData.facialStyle
],
);
setOptionByChoiceName(
'Sideburns',
{ 0: 'Medium', 1: 'None', 2: 'None', 3: 'Medium', 4: 'Long', 5: 'Long', 6: 'None', 8: 'None', 7: 'None' }[charData.facialStyle],
);
setOptionByChoiceName('Eyebrows', 'Natural');
setOptionByChoiceName('Face Shape', 'Narrow');
setOptionByChoiceId(
'Eye Color',
{ 0: 4138, 1: 4140, 2: 4130, 3: 4136, 4: 4141, 5: 4134, 6: 4130, 7: 4138, 8: 4144, 9: 4135, 10: 4126, 11: 4136 }[charData.face],
);
} else { } else {
setOptionByChoiceIndex("Piercings", charData.facialStyle); setOptionByChoiceIndex('Piercings', charData.facialStyle);
setOptionByChoiceName("Eyebrows", "Natural"); setOptionByChoiceName('Eyebrows', 'Natural');
setOptionByChoiceName("Face Shape", "Narrow"); setOptionByChoiceName('Face Shape', 'Narrow');
setOptionByChoiceName("Makeup", "None"); setOptionByChoiceName('Makeup', 'None');
setOptionByChoiceName("Necklace", "None"); setOptionByChoiceName('Necklace', 'None');
setOptionByChoiceId("Eye Color", { 0: 4162, 1: 4153, 2: 4161, 3: 4164, 4: 4154, 5: 4160, 6: 4160, 7: 4157, 8: 4152, 9: 4154, 10: 4155, 11: 4165, 12: 4163, 13: 4155, 14: 4151 }[charData.face]); setOptionByChoiceId(
'Eye Color',
{
0: 4162,
1: 4153,
2: 4161,
3: 4164,
4: 4154,
5: 4160,
6: 4160,
7: 4157,
8: 4152,
9: 4154,
10: 4155,
11: 4165,
12: 4163,
13: 4155,
14: 4151,
}[charData.face],
);
} }
if (charData.class === 6) { if (charData.class === 6) {
// Death Knight // Death Knight
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 4534 : 4535); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 4534 : 4535);
} }
break; break;
case 3: // Dwarf case 3: // Dwarf
setOptionByChoiceName("Tattoo", "None"); setOptionByChoiceName('Tattoo', 'None');
setOptionByChoiceIndex("Tattoo Color", 0); setOptionByChoiceIndex('Tattoo Color', 0);
setOptionByChoiceIndex("Eyebrows", 0); setOptionByChoiceIndex('Eyebrows', 0);
if (charData.gender === 0) { if (charData.gender === 0) {
setOptionByChoiceName("Mustache", { 0: "Trimmed", 1: "Bushy", 2: "Grand", 3: "Thin Braids", 4: "Wise", 5: "Thick Braids", 6: "Fancy", 7: "Bold", 8: "Tied", 9: "None", 10: "None", }[charData.facialStyle]); setOptionByChoiceName(
setOptionByChoiceIndex("Beard", charData.facialStyle); 'Mustache',
setOptionByChoiceName("Earrings", "None"); {
setOptionByChoiceName("Nose Ring", "None"); 0: 'Trimmed',
setOptionByChoiceIndex("Eye Color", 0); // TODO 1: 'Bushy',
2: 'Grand',
3: 'Thin Braids',
4: 'Wise',
5: 'Thick Braids',
6: 'Fancy',
7: 'Bold',
8: 'Tied',
9: 'None',
10: 'None',
}[charData.facialStyle],
);
setOptionByChoiceIndex('Beard', charData.facialStyle);
setOptionByChoiceName('Earrings', 'None');
setOptionByChoiceName('Nose Ring', 'None');
setOptionByChoiceIndex('Eye Color', 0); // TODO
} else { } else {
setOptionByChoiceIndex("Earrings", { 0: 0, 1: 1, 2: 2, 3: 3, 4: 0, 5: 4 }[charData.facialStyle]); setOptionByChoiceIndex('Earrings', { 0: 0, 1: 1, 2: 2, 3: 3, 4: 0, 5: 4 }[charData.facialStyle]);
setOptionByChoiceName("Piercings", { 0: "None", 1: "None", 2: "None", 3: "None", 4: "Right Nostril", 5: "None" }[charData.facialStyle]); setOptionByChoiceName(
setOptionByChoiceIndex("Eye Color", 0); // TODO 'Piercings',
{ 0: 'None', 1: 'None', 2: 'None', 3: 'None', 4: 'Right Nostril', 5: 'None' }[charData.facialStyle],
);
setOptionByChoiceIndex('Eye Color', 0); // TODO
} }
if (charData.class === 6) { if (charData.class === 6) {
// Death Knight // Death Knight
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 5559 : 5587); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 5559 : 5587);
} }
break; break;
case 7: // Gnome case 7: // Gnome
if (charData.gender === 0) { if (charData.gender === 0) {
setOptionByChoiceIndex("Mustache", charData.facialStyle > 1 ? charData.facialStyle - 1 : 0); setOptionByChoiceIndex('Mustache', charData.facialStyle > 1 ? charData.facialStyle - 1 : 0);
setOptionByChoiceIndex("Beard", charData.facialStyle < 7 ? charData.facialStyle : 0); setOptionByChoiceIndex('Beard', charData.facialStyle < 7 ? charData.facialStyle : 0);
setOptionByChoiceIndex("Eyebrows", charData.facialStyle < 6 ? charData.facialStyle : 1); setOptionByChoiceIndex('Eyebrows', charData.facialStyle < 6 ? charData.facialStyle : 1);
setOptionByChoiceIndex("Eye Color", 0); // TODO setOptionByChoiceIndex('Eye Color', 0); // TODO
} else { } else {
setOptionByChoiceIndex("Earrings", charData.facialStyle); setOptionByChoiceIndex('Earrings', charData.facialStyle);
setOptionByChoiceId("Earring Color", 8796); setOptionByChoiceId('Earring Color', 8796);
setOptionByChoiceIndex("Eye Color", 0); // TODO setOptionByChoiceIndex('Eye Color', 0); // TODO
} }
if (charData.class === 6) { if (charData.class === 6) {
// Death Knight // Death Knight
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 5629 : 5643); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 5629 : 5643);
} }
break; break;
case 4: // Night Elf case 4: // Night Elf
setOptionByChoiceName("Vines", "None"); setOptionByChoiceName('Vines', 'None');
setOptionByChoiceIndex("Vine Color", 0); setOptionByChoiceIndex('Vine Color', 0);
setOptionByChoiceName("Ears", "Thin"); setOptionByChoiceName('Ears', 'Thin');
setOptionByChoiceName("Scars", "None"); setOptionByChoiceName('Scars', 'None');
if (charData.gender === 0) { if (charData.gender === 0) {
setOptionByChoiceName("Sideburns", { 0: "None", 1: "Groomed", 2: "None", 3: "Short", 4: "Medium", 5: "Groomed" }[charData.facialStyle]); setOptionByChoiceName(
setOptionByChoiceName("Mustache", { 0: "None", 1: "Groomed", 2: "None", 3: "Thin", 4: "None", 5: "None" }[charData.facialStyle]); 'Sideburns',
setOptionByChoiceName("Beard", { 0: "None", 1: "Trimmed", 2: "Full", 3: "None", 4: "Short", 5: "Long" }[charData.facialStyle]); { 0: 'None', 1: 'Groomed', 2: 'None', 3: 'Short', 4: 'Medium', 5: 'Groomed' }[charData.facialStyle],
setOptionByChoiceName("Eyebrows", { 0: "Shaved", 1: "Short", 2: "Long", 3: "Flat", 4: "Short", 5: "Owl" }[charData.facialStyle]); );
setOptionByChoiceName('Mustache', { 0: 'None', 1: 'Groomed', 2: 'None', 3: 'Thin', 4: 'None', 5: 'None' }[charData.facialStyle]);
setOptionByChoiceName('Beard', { 0: 'None', 1: 'Trimmed', 2: 'Full', 3: 'None', 4: 'Short', 5: 'Long' }[charData.facialStyle]);
setOptionByChoiceName('Eyebrows', { 0: 'Shaved', 1: 'Short', 2: 'Long', 3: 'Flat', 4: 'Short', 5: 'Owl' }[charData.facialStyle]);
} else { } else {
setOptionByChoiceName("Eyebrows", "Long"); setOptionByChoiceName('Eyebrows', 'Long');
setOptionByChoiceIndex("Markings", charData.facialStyle + 1); setOptionByChoiceIndex('Markings', charData.facialStyle + 1);
setOptionByChoiceIndex("Markings Color", { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 3, 6: 6, 7: 7 }[charData.hairColor]); setOptionByChoiceIndex('Markings Color', { 0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 3, 6: 6, 7: 7 }[charData.hairColor]);
} }
setOptionByChoiceName("Blindfold", ""); setOptionByChoiceName('Blindfold', '');
setOptionByChoiceName("Headdress", "None"); setOptionByChoiceName('Headdress', 'None');
setOptionByChoiceName("Earrings", "None"); setOptionByChoiceName('Earrings', 'None');
setOptionByChoiceName("Nose Ring", "None"); setOptionByChoiceName('Nose Ring', 'None');
setOptionByChoiceName("Necklace", "None"); setOptionByChoiceName('Necklace', 'None');
setOptionByChoiceName("Horns", "None"); setOptionByChoiceName('Horns', 'None');
setOptionByChoiceName("Tattoo", "None"); setOptionByChoiceName('Tattoo', 'None');
setOptionByChoiceName("Tattoo Color", "None"); setOptionByChoiceName('Tattoo Color', 'None');
if (charData.class === 6) { if (charData.class === 6) {
// Death Knight // Death Knight
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 7618 : 7634); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 7618 : 7634);
} else { } else {
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 7610 : 7619); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 7610 : 7619);
} }
break; break;
case 11: // Draenei case 11: // Draenei
setOptionByChoiceName("Circlet", "None"); setOptionByChoiceName('Circlet', 'None');
setOptionByChoiceId("Jewelry Color", charData.gender === 0 ? 8707 : 8646); setOptionByChoiceId('Jewelry Color', charData.gender === 0 ? 8707 : 8646);
setOptionByChoiceName("Horn Decoration", "None"); setOptionByChoiceName('Horn Decoration', 'None');
setOptionByChoiceName("Tail", charData.gender === 0 ? "Long" : "Short"); setOptionByChoiceName('Tail', charData.gender === 0 ? 'Long' : 'Short');
if (charData.gender === 0) { if (charData.gender === 0) {
setOptionByChoiceName("Facial Hair", { 0: "Bare", 1: "Bare", 2: "Burns", 3: "Chops", 4: "Mustache", 5: "Soul Patch", 6: "Handlebar", 7: "Bare" }[charData.facialStyle]); setOptionByChoiceName(
setOptionByChoiceName("Tendrils", { 0: "None", 1: "Splayed", 2: "Double", 3: "Fanned", 4: "Single", 5: "Paired", 6: "Uniform", 7: "Twin" }[charData.facialStyle]); 'Facial Hair',
{ 0: 'Bare', 1: 'Bare', 2: 'Burns', 3: 'Chops', 4: 'Mustache', 5: 'Soul Patch', 6: 'Handlebar', 7: 'Bare' }[
charData.facialStyle
],
);
setOptionByChoiceName(
'Tendrils',
{ 0: 'None', 1: 'Splayed', 2: 'Double', 3: 'Fanned', 4: 'Single', 5: 'Paired', 6: 'Uniform', 7: 'Twin' }[charData.facialStyle],
);
} else { } else {
setOptionByChoiceName("Horns", { 0: "Sweeping", 1: "Curled", 2: "Curved", 3: "Thick", 4: "Wide", 5: "Grand", 6: "Short" }[charData.facialStyle]); setOptionByChoiceName(
'Horns',
{ 0: 'Sweeping', 1: 'Curled', 2: 'Curved', 3: 'Thick', 4: 'Wide', 5: 'Grand', 6: 'Short' }[charData.facialStyle],
);
} }
if (charData.class === 6) { if (charData.class === 6) {
// Death Knight // Death Knight
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 6977 : 6979); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 6977 : 6979);
} else { } else {
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 6976 : 6978); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 6976 : 6978);
} }
break; break;
case 2: // Orc case 2: // Orc
setOptionByChoiceName("Scars", "None"); setOptionByChoiceName('Scars', 'None');
setOptionByChoiceName("Grime", "None"); setOptionByChoiceName('Grime', 'None');
setOptionByChoiceName("Tattoo", "None"); setOptionByChoiceName('Tattoo', 'None');
setOptionByChoiceName("War Paint", "None"); setOptionByChoiceName('War Paint', 'None');
setOptionByChoiceName("War Paint Color", "None"); setOptionByChoiceName('War Paint Color', 'None');
if (charData.gender === 0) { if (charData.gender === 0) {
setOptionByChoiceName("Beard", { 0: "None", 1: "Stubble", 2: "Thick", 3: "Full", 4: "Tied", 5: "Braid", 6: "Twin Braids", 7: "None", 8: "Ringed", 9: "Split", 10: "Goatee" }[charData.facialStyle]); setOptionByChoiceName(
setOptionByChoiceName("Sideburns", { 0: "None", 1: "None", 2: "Full", 3: "Low", 4: "Full", 5: "None", 6: "None", 7: "Braids", 8: "None", 9: "Full", 10: "Thick" }[charData.facialStyle]); 'Beard',
setOptionByChoiceName("Earrings", "None"); {
setOptionByChoiceName("Nose Ring", "None"); 0: 'None',
setOptionByChoiceName("Tusks", "Natural"); 1: 'Stubble',
setOptionByChoiceName("Upright", "Hunched"); 2: 'Thick',
setOptionByChoiceIndex("Eye Color", 0); // TODO 3: 'Full',
4: 'Tied',
5: 'Braid',
6: 'Twin Braids',
7: 'None',
8: 'Ringed',
9: 'Split',
10: 'Goatee',
}[charData.facialStyle],
);
setOptionByChoiceName(
'Sideburns',
{ 0: 'None', 1: 'None', 2: 'Full', 3: 'Low', 4: 'Full', 5: 'None', 6: 'None', 7: 'Braids', 8: 'None', 9: 'Full', 10: 'Thick' }[
charData.facialStyle
],
);
setOptionByChoiceName('Earrings', 'None');
setOptionByChoiceName('Nose Ring', 'None');
setOptionByChoiceName('Tusks', 'Natural');
setOptionByChoiceName('Upright', 'Hunched');
setOptionByChoiceIndex('Eye Color', 0); // TODO
} else { } else {
setOptionByChoiceIndex("Earrings", { 0: 0, 1: 1, 2: 2, 3: 0, 4: 1, 5: 2, 6: 4 }[charData.facialStyle]); setOptionByChoiceIndex('Earrings', { 0: 0, 1: 1, 2: 2, 3: 0, 4: 1, 5: 2, 6: 4 }[charData.facialStyle]);
setOptionByChoiceIndex("Nose Ring", { 0: 0, 1: 0, 2: 0, 3: 1, 4: 1, 5: 1, 6: 0 }[charData.facialStyle]); setOptionByChoiceIndex('Nose Ring', { 0: 0, 1: 0, 2: 0, 3: 1, 4: 1, 5: 1, 6: 0 }[charData.facialStyle]);
setOptionByChoiceName("Necklace", "None"); setOptionByChoiceName('Necklace', 'None');
setOptionByChoiceIndex("Eye Color", 0); // TODO setOptionByChoiceIndex('Eye Color', 0); // TODO
} }
if (charData.class === 6) { if (charData.class === 6) {
// Death Knight // Death Knight
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 9289 : 9313); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 9289 : 9313);
} }
break; break;
case 5: // Undead case 5: // Undead
setOptionByChoiceName("Skin Type", "Bony"); setOptionByChoiceName('Skin Type', 'Bony');
if (charData.gender === 0) { if (charData.gender === 0) {
setOptionByChoiceName("Jaw Features", { 0: "Intact", 1: "Rot-Kissed", 2: "Intact", 3: "Slackjawed", 4: "Drooler", 5: "Intact", 6: "Slackjawed", 7: "Drooler", 8: "Bonejawed", 9: "Jawsome", 10: "Toothy", 11: "Unhinged", 12: "Cheeky", 13: "Loose", 14: "Intact", 15: "Slackjawed", 16: "Slobber" }[charData.facialStyle]); setOptionByChoiceName(
setOptionByChoiceIndex("Face Features", { 0: 0, 1: 0, 2: 1, 3: 1, 4: 1, 5: 2, 6: 3, 7: 3, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 4, 15: 4, 16: 0 }[charData.facialStyle]); 'Jaw Features',
setOptionByChoiceId("Eye Color", { 0: 5330, 1: 5330, 2: 6304, 3: 6304, 4: 6304, 5: 5330, 6: 5330, 7: 5330, 8: 5330, 9: 5330, 10: 6304, 11: 6304, 12: 5330, 13: 5330, 14: 5330, 15: 5330, 16: 5330 }[charData.facialStyle]); {
0: 'Intact',
1: 'Rot-Kissed',
2: 'Intact',
3: 'Slackjawed',
4: 'Drooler',
5: 'Intact',
6: 'Slackjawed',
7: 'Drooler',
8: 'Bonejawed',
9: 'Jawsome',
10: 'Toothy',
11: 'Unhinged',
12: 'Cheeky',
13: 'Loose',
14: 'Intact',
15: 'Slackjawed',
16: 'Slobber',
}[charData.facialStyle],
);
setOptionByChoiceIndex(
'Face Features',
{ 0: 0, 1: 0, 2: 1, 3: 1, 4: 1, 5: 2, 6: 3, 7: 3, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 4, 15: 4, 16: 0 }[
charData.facialStyle
],
);
setOptionByChoiceId(
'Eye Color',
{
0: 5330,
1: 5330,
2: 6304,
3: 6304,
4: 6304,
5: 5330,
6: 5330,
7: 5330,
8: 5330,
9: 5330,
10: 6304,
11: 6304,
12: 5330,
13: 5330,
14: 5330,
15: 5330,
16: 5330,
}[charData.facialStyle],
);
} else { } else {
setOptionByChoiceName("Face Features", { 0: "None", 1: "None", 2: "Strapped", 3: "Rotting", 4: "None", 5: "None", 6: "None", 7: "Putrid" }[charData.facialStyle]); setOptionByChoiceName(
setOptionByChoiceName("Jaw Features", { 0: "Intact", 1: "Stitched", 2: "Intact", 3: "Intact", 4: "Bonejawed", 5: "Toothy", 6: "Cheeky", 7: "Intact" }[charData.facialStyle]); 'Face Features',
setOptionByChoiceId("Eye Color", { 0: 5337, 1: 5337, 2: 6305, 3: 5337, 4: 5337, 5: 6305, 6: 5337, 7: 5337 }[charData.facialStyle]); { 0: 'None', 1: 'None', 2: 'Strapped', 3: 'Rotting', 4: 'None', 5: 'None', 6: 'None', 7: 'Putrid' }[charData.facialStyle],
);
setOptionByChoiceName(
'Jaw Features',
{ 0: 'Intact', 1: 'Stitched', 2: 'Intact', 3: 'Intact', 4: 'Bonejawed', 5: 'Toothy', 6: 'Cheeky', 7: 'Intact' }[
charData.facialStyle
],
);
setOptionByChoiceId(
'Eye Color',
{ 0: 5337, 1: 5337, 2: 6305, 3: 5337, 4: 5337, 5: 6305, 6: 5337, 7: 5337 }[charData.facialStyle],
);
} }
if (charData.class === 6) { if (charData.class === 6) {
// Death Knight // Death Knight
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 5344 : 5345); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 5344 : 5345);
} }
break; break;
case 6: // Tauren case 6: // Tauren
setOptionByChoiceIndex("Horn Style", charData.hairStyle); setOptionByChoiceIndex('Horn Style', charData.hairStyle);
setOptionByChoiceIndex("Horn Color", charData.hairColor); setOptionByChoiceIndex('Horn Color', charData.hairColor);
setOptionByChoiceName("Foremane", "Short"); setOptionByChoiceName('Foremane', 'Short');
setOptionByChoiceName("Face Paint", "None"); setOptionByChoiceName('Face Paint', 'None');
setOptionByChoiceName("Headdress", "None"); setOptionByChoiceName('Headdress', 'None');
setOptionByChoiceName("Necklace", "None"); setOptionByChoiceName('Necklace', 'None');
setOptionByChoiceIndex("Jewelry Color", 0); setOptionByChoiceIndex('Jewelry Color', 0);
setOptionByChoiceName("Flower", "None"); setOptionByChoiceName('Flower', 'None');
setOptionByChoiceName("Body Paint", "None"); setOptionByChoiceName('Body Paint', 'None');
setOptionByChoiceIndex("Paint Color", 0); setOptionByChoiceIndex('Paint Color', 0);
if (charData.gender === 0) { if (charData.gender === 0) {
setOptionByChoiceName("Hair", { 0: "Mane", 1: "Braids", 2: "Chops", 3: "Sideburns", 4: "Mane", 5: "Wrapped", 6: "Braids" }[charData.facialStyle]); setOptionByChoiceName(
setOptionByChoiceName("Facial Hair", { 0: "Clean", 1: "Braid", 2: "Beard", 3: "Wrapped", 4: "Curtain", 5: "Clean", 6: "Split" }[charData.facialStyle]); 'Hair',
setOptionByChoiceName("Nose Ring", { 0: "None", 1: "Small", 2: "Open", 3: "None", 4: "None", 5: "Bead", 6: "Open" }[charData.facialStyle]); { 0: 'Mane', 1: 'Braids', 2: 'Chops', 3: 'Sideburns', 4: 'Mane', 5: 'Wrapped', 6: 'Braids' }[charData.facialStyle],
setOptionByChoiceIndex("Eye Color", 0); // TODO );
setOptionByChoiceName(
'Facial Hair',
{ 0: 'Clean', 1: 'Braid', 2: 'Beard', 3: 'Wrapped', 4: 'Curtain', 5: 'Clean', 6: 'Split' }[charData.facialStyle],
);
setOptionByChoiceName(
'Nose Ring',
{ 0: 'None', 1: 'Small', 2: 'Open', 3: 'None', 4: 'None', 5: 'Bead', 6: 'Open' }[charData.facialStyle],
);
setOptionByChoiceIndex('Eye Color', 0); // TODO
} else { } else {
setOptionByChoiceIndex("Hair", charData.facialStyle); setOptionByChoiceIndex('Hair', charData.facialStyle);
setOptionByChoiceName("Earrings", "None"); setOptionByChoiceName('Earrings', 'None');
setOptionByChoiceName("Nose Ring", "None"); setOptionByChoiceName('Nose Ring', 'None');
setOptionByChoiceIndex("Eye Color", 0); // TODO setOptionByChoiceIndex('Eye Color', 0); // TODO
} }
if (charData.class === 6) { if (charData.class === 6) {
// Death Knight // Death Knight
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 7281 : 7289); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 7281 : 7289);
} }
break; break;
case 8: // Troll case 8: // Troll
setOptionByChoiceName("Body Paint", "None"); setOptionByChoiceName('Body Paint', 'None');
setOptionByChoiceName("Body Paint Color", "None"); setOptionByChoiceName('Body Paint Color', 'None');
setOptionByChoiceName("Piercing", "None"); setOptionByChoiceName('Piercing', 'None');
if (charData.gender === 0) { if (charData.gender === 0) {
setOptionByChoiceName("Tusks", { 0: "Tusked", 1: "Gougers", 2: "Mammoth", 3: "Spears", 4: "Bridle", 5: "Tusked", 6: "Gougers", 7: "Mammoth", 8: "Spears", 9: "Bridle", 10: "Gougers" }[charData.facialStyle]); setOptionByChoiceName(
setOptionByChoiceName("Face Paint", { 0: "None", 1: "None", 2: "None", 3: "None", 4: "None", 5: "Berserker", 6: "Fangs", 7: "Mask", 8: "Oni", 9: "Prophet", 10: "War" }[charData.facialStyle]); 'Tusks',
setOptionByChoiceIndex("Face Paint Color", charData.hairColor + 1); {
setOptionByChoiceName("Earrings", "None"); 0: 'Tusked',
setOptionByChoiceIndex("Eye Color", 0); // TODO 1: 'Gougers',
2: 'Mammoth',
3: 'Spears',
4: 'Bridle',
5: 'Tusked',
6: 'Gougers',
7: 'Mammoth',
8: 'Spears',
9: 'Bridle',
10: 'Gougers',
}[charData.facialStyle],
);
setOptionByChoiceName(
'Face Paint',
{
0: 'None',
1: 'None',
2: 'None',
3: 'None',
4: 'None',
5: 'Berserker',
6: 'Fangs',
7: 'Mask',
8: 'Oni',
9: 'Prophet',
10: 'War',
}[charData.facialStyle],
);
setOptionByChoiceIndex('Face Paint Color', charData.hairColor + 1);
setOptionByChoiceName('Earrings', 'None');
setOptionByChoiceIndex('Eye Color', 0); // TODO
} else { } else {
setOptionByChoiceIndex("Tusks", charData.facialStyle); setOptionByChoiceIndex('Tusks', charData.facialStyle);
setOptionByChoiceName("Face Paint", "None"); setOptionByChoiceName('Face Paint', 'None');
setOptionByChoiceIndex("Face Paint Color", 0); setOptionByChoiceIndex('Face Paint Color', 0);
setOptionByChoiceName("Earrings", "Hoops"); setOptionByChoiceName('Earrings', 'Hoops');
setOptionByChoiceIndex("Eye Color", 0); // TODO setOptionByChoiceIndex('Eye Color', 0); // TODO
} }
if (charData.class === 6) { if (charData.class === 6) {
// Death Knight // Death Knight
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 8451 : 8468); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 8451 : 8468);
} }
break; break;
case 10: // Blood Elf case 10: // Blood Elf
setOptionByChoiceName("Ears", "Long"); setOptionByChoiceName('Ears', 'Long');
setOptionByChoiceName("Horns", "None"); setOptionByChoiceName('Horns', 'None');
setOptionByChoiceName("Blindfold", "None"); setOptionByChoiceName('Blindfold', 'None');
setOptionByChoiceName("Tattoo", "None"); setOptionByChoiceName('Tattoo', 'None');
setOptionByChoiceIndex("Tattoo Color", 0); setOptionByChoiceIndex('Tattoo Color', 0);
if (charData.gender === 0) { if (charData.gender === 0) {
setOptionByChoiceIndex("Facial Hair", charData.facialStyle); setOptionByChoiceIndex('Facial Hair', charData.facialStyle);
} else { } else {
setOptionByChoiceIndex("Earrings", charData.facialStyle); setOptionByChoiceIndex('Earrings', charData.facialStyle);
setOptionByChoiceIndex("Jewelry Color", 0); setOptionByChoiceIndex('Jewelry Color', 0);
setOptionByChoiceName("Necklace", "None"); setOptionByChoiceName('Necklace', 'None');
setOptionByChoiceName("Armbands", "None"); setOptionByChoiceName('Armbands', 'None');
setOptionByChoiceName("Bracelets", "None"); setOptionByChoiceName('Bracelets', 'None');
} }
if (charData.class === 6) { if (charData.class === 6) {
// Death Knight // Death Knight
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 6586 : 6605); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 6586 : 6605);
} else { } else {
setOptionByChoiceId("Eye Color", charData.gender === 0 ? 6570 : 6589); setOptionByChoiceId('Eye Color', charData.gender === 0 ? 6570 : 6589);
} }
break; break;
} }
if ([4, 6].includes(charData.race)) { if ([4, 6].includes(charData.race)) {
// Races that can choose the druid class // Races that can choose the druid class
setOptionByChoiceIndex("Bear Form", 0); setOptionByChoiceIndex('Bear Form', 0);
setOptionByChoiceIndex("Cat Form", 0); setOptionByChoiceIndex('Cat Form', 0);
setOptionByChoiceIndex("Aquatic Form", 0); setOptionByChoiceIndex('Aquatic Form', 0);
setOptionByChoiceIndex("Travel Form", 0); setOptionByChoiceIndex('Travel Form', 0);
setOptionByChoiceIndex("Flight Form", 0); setOptionByChoiceIndex('Flight Form', 0);
setOptionByChoiceIndex("Moonkin Form", 0); setOptionByChoiceIndex('Moonkin Form', 0);
} }
return options; return options;
@ -712,16 +896,18 @@ export class CharacterController {
} }
private async getTalentTrees(classId: number) { private async getTalentTrees(classId: number) {
const items = await this.armory.dbc.talentTab() const items = await this.armory.dbc
.filter(tab => tab.classMask === Math.pow(2, classId - 1)) .talentTab()
.map(async tab => { .filter((tab) => tab.classMask === Math.pow(2, classId - 1))
const icon = await this.armory.dbc.spellIcon().find(icon => icon.id === tab.spellIconId); .map(async (tab) => {
const spells = await this.armory.dbc.talent() const icon = await this.armory.dbc.spellIcon().find((icon) => icon.id === tab.spellIconId);
.filter(row => row.tabId === tab.id) const spells = await this.armory.dbc
.map(async row => { .talent()
const spell = await this.armory.dbc.spell().find(spell => spell.id === row.spellRank0); .filter((row) => row.tabId === tab.id)
const icon = await this.armory.dbc.spellIcon().find(icon => icon.id === spell?.spellIconId); .map(async (row) => {
return { ...row, icon: this.processSpellIconTexture(icon?.textureFilename ?? ""), }; const spell = await this.armory.dbc.spell().find((spell) => spell.id === row.spellRank0);
const icon = await this.armory.dbc.spellIcon().find((icon) => icon.id === spell?.spellIconId);
return { ...row, icon: this.processSpellIconTexture(icon?.textureFilename ?? '') };
}) })
.toArray(); .toArray();
return { return {
@ -735,11 +921,7 @@ export class CharacterController {
} }
private processSpellIconTexture(texturePath: string): string { private processSpellIconTexture(texturePath: string): string {
return texturePath return texturePath.toLowerCase().replace('interface\\icons\\', '').replace('interface\\spellbook\\', '').replace(/\.$/, '');
.toLowerCase()
.replace("interface\\icons\\", "")
.replace("interface\\spellbook\\", "")
.replace(/\.$/, "");
} }
private async getGlyphs(realm: string, character: number): Promise<any[][]> { private async getGlyphs(realm: string, character: number): Promise<any[][]> {
@ -755,9 +937,9 @@ export class CharacterController {
const glyphs = [[], []]; const glyphs = [[], []];
for (const row of rows as RowDataPacket[]) { for (const row of rows as RowDataPacket[]) {
const glyphIds = [row.glyph1, row.glyph2, row.glyph3, row.glyph4, row.glyph5, row.glyph6].filter(id => id !== 0); const glyphIds = [row.glyph1, row.glyph2, row.glyph3, row.glyph4, row.glyph5, row.glyph6].filter((id) => id !== 0);
for (const glyphId of glyphIds) { for (const glyphId of glyphIds) {
const glyph = await this.armory.dbc.glyphProperties().find(g => g.id === glyphId); const glyph = await this.armory.dbc.glyphProperties().find((g) => g.id === glyphId);
if (glyph === undefined) { if (glyph === undefined) {
continue; continue;
} }
@ -768,18 +950,19 @@ export class CharacterController {
return glyphs; return glyphs;
} }
private async getAchievements(realm: string, charData: ICharacterData): Promise<{ achievements: any[]; earned: { [key: number]: any }; }> { private async getAchievements(realm: string, charData: ICharacterData): Promise<{ achievements: any[]; earned: { [key: number]: any } }> {
const promises = await this.armory.dbc.achievement() const promises = await this.armory.dbc
.filter(ach => ach.faction === -1 || ach.faction === Utils.getFactionFromRaceId(charData.race)) .achievement()
.filter((ach) => ach.faction === -1 || ach.faction === Utils.getFactionFromRaceId(charData.race))
.map(async (ach) => { .map(async (ach) => {
const icon = await this.armory.dbc.spellIcon().find(icon => icon.id === ach.iconId); const icon = await this.armory.dbc.spellIcon().find((icon) => icon.id === ach.iconId);
return { return {
id: ach.id, id: ach.id,
category: ach.category, category: ach.category,
title: ach.titleLang0, title: ach.titleLang0,
description: ach.descriptionLang0, description: ach.descriptionLang0,
points: ach.points, points: ach.points,
icon: this.processSpellIconTexture(icon?.textureFilename ?? ""), icon: this.processSpellIconTexture(icon?.textureFilename ?? ''),
}; };
}) })
.toArray(); .toArray();
@ -807,7 +990,7 @@ export class CharacterController {
}; };
} }
private async getPvpKills(realm: string, charGuid: number): Promise<{ total: number, today: number, yesterday: number }> { private async getPvpKills(realm: string, charGuid: number): Promise<{ total: number; today: number; yesterday: number }> {
const [rows, fields] = await this.armory.getCharactersDb(realm).query({ const [rows, fields] = await this.armory.getCharactersDb(realm).query({
sql: ` sql: `
SELECT totalKills, todayKills, yesterdayKills SELECT totalKills, todayKills, yesterdayKills
@ -841,7 +1024,7 @@ export class CharacterController {
timeout: this.armory.config.dbQueryTimeout, timeout: this.armory.config.dbQueryTimeout,
}); });
return (rows as RowDataPacket[]).map(row => { return (rows as RowDataPacket[]).map((row) => {
row.emblem = Utils.makeEmblemObject(row, false); row.emblem = Utils.makeEmblemObject(row, false);
return row; return row;
}); });

View file

@ -1,11 +1,11 @@
import * as express from "express"; import * as express from 'express';
import { encode } from "html-entities"; import { encode } from 'html-entities';
import { RowDataPacket } from "mysql2/promise"; import { RowDataPacket } from 'mysql2/promise';
import { Utils } from "../Utils"; import { Utils } from '../Utils';
import { Armory } from "../Armory"; import { Armory } from '../Armory';
import { IRealmConfig } from "../Config"; import { IRealmConfig } from '../Config';
import { DataTablesSsp } from "../DataTablesSsp"; import { DataTablesSsp } from '../DataTablesSsp';
interface IGuildRank { interface IGuildRank {
id: number; id: number;
@ -35,7 +35,7 @@ export class GuildController {
return next(404); return next(404);
} }
res.render("guild.hbs", { res.render('guild.hbs', {
title: `Armory - ${guildName}`, title: `Armory - ${guildName}`,
realm: realm.name, realm: realm.name,
...guildData, ...guildData,
@ -60,28 +60,32 @@ export class GuildController {
const db = this.armory.getCharactersDb(realm.name); const db = this.armory.getCharactersDb(realm.name);
const charSet = await this.armory.getDatabaseCharset(realm.name); const charSet = await this.armory.getDatabaseCharset(realm.name);
let ssp = new DataTablesSsp(req.query, db, "guild_member", "guid", [ let ssp = new DataTablesSsp(req.query, db, 'guild_member', 'guid', [
{ name: "name", table: "characters", collation: `${charSet}_general_ci` }, { name: 'name', table: 'characters', collation: `${charSet}_general_ci` },
{ name: "rank" }, { name: 'rank' },
{ name: "level", table: "characters" }, { name: 'level', table: 'characters' },
{ name: "class", table: "characters", formatter: cls => Utils.classNames[cls] }, { name: 'class', table: 'characters', formatter: (cls) => Utils.classNames[cls] },
{ name: "race", table: "characters", formatter: (race, row) => `${Utils.raceNames[race]}_${row[6] === 0 ? "male" : "female"}` }, { name: 'race', table: 'characters', formatter: (race, row) => `${Utils.raceNames[race]}_${row[6] === 0 ? 'male' : 'female'}` },
{ name: "online", table: "characters", formatter: online => online === 1 }, { name: 'online', table: 'characters', formatter: (online) => online === 1 },
]); ]);
ssp.joins = [ ssp.joins = [{ table1: 'guild_member', column1: 'guid', table2: 'characters', column2: 'guid', kind: 'LEFT' }];
{ table1: "guild_member", column1: "guid", table2: "characters", column2: "guid", kind: "LEFT" }, ssp.extraDataColumns = ['`characters`.`gender`'];
];
ssp.extraDataColumns = ["`characters`.`gender`"];
if (this.armory.config.hideGameMasters) { if (this.armory.config.hideGameMasters) {
ssp.joins.push({ table1: "characters", column1: "account", table2: "account_access", column2: "id", database2: realm.authDatabase, kind: "LEFT" }); ssp.joins.push({
ssp = ssp.where(`\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0`); table1: 'characters',
column1: 'account',
table2: 'account_access',
column2: 'id',
database2: realm.authDatabase,
kind: 'LEFT',
});
ssp = ssp.where(
`\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0`,
);
} }
const result = await ssp const result = await ssp.where('`guildid` = ?', guildId).where('`deleteInfos_Account` IS NULL').run(this.armory.config.dbQueryTimeout);
.where("`guildid` = ?", guildId)
.where("`deleteInfos_Account` IS NULL")
.run(this.armory.config.dbQueryTimeout);
const ranks = await this.getGuildRanks(realm, guildId); const ranks = await this.getGuildRanks(realm, guildId);
(result as any).ranks = {}; (result as any).ranks = {};

View file

@ -1,8 +1,8 @@
import * as express from "express"; import * as express from 'express';
import { Utils } from "../Utils"; import { Utils } from '../Utils';
import { Armory } from "../Armory"; import { Armory } from '../Armory';
import { DataTablesSsp } from "../DataTablesSsp"; import { DataTablesSsp } from '../DataTablesSsp';
export class IndexController { export class IndexController {
private armory: Armory; private armory: Armory;
@ -12,17 +12,15 @@ export class IndexController {
} }
public async index(req: express.Request, res: express.Response): Promise<void> { public async index(req: express.Request, res: express.Response): Promise<void> {
res.render("index.hbs", { res.render('index.hbs', {
title: "Armory", title: 'Armory',
realms: this.armory.config.realms.map(r => r.name), realms: this.armory.config.realms.map((r) => r.name),
}); });
} }
public async search(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> { public async search(req: express.Request, res: express.Response, next: express.NextFunction): Promise<void> {
const realmName = req.query.realm as string; const realmName = req.query.realm as string;
const realm = realmName === undefined ? const realm = realmName === undefined ? this.armory.config.realms[0] : this.armory.config.realms.find((r) => r.name === realmName);
this.armory.config.realms[0] :
this.armory.config.realms.find(r => r.name === realmName);
if (realm === undefined) { if (realm === undefined) {
return next(400); return next(400);
} }
@ -30,28 +28,35 @@ export class IndexController {
const db = this.armory.getCharactersDb(realm.name); const db = this.armory.getCharactersDb(realm.name);
const charSet = await this.armory.getDatabaseCharset(realm.name); const charSet = await this.armory.getDatabaseCharset(realm.name);
let ssp = new DataTablesSsp(req.query, db, "characters", "guid", [ let ssp = new DataTablesSsp(req.query, db, 'characters', 'guid', [
{ name: "name", collation: `${charSet}_general_ci` }, { name: 'name', collation: `${charSet}_general_ci` },
{ table: "guild", name: "name" }, { table: 'guild', name: 'name' },
{ name: "level" }, { name: 'level' },
{ name: "class", formatter: cls => Utils.classNames[cls] }, { name: 'class', formatter: (cls) => Utils.classNames[cls] },
{ name: "race", formatter: (race, row) => `${Utils.raceNames[race]}_${row[6] === 0 ? "male" : "female"}` }, { name: 'race', formatter: (race, row) => `${Utils.raceNames[race]}_${row[6] === 0 ? 'male' : 'female'}` },
{ name: "online", formatter: online => online === 1 }, { name: 'online', formatter: (online) => online === 1 },
]); ]);
ssp.joins = [ ssp.joins = [
{ table1: "characters", column1: "guid", table2: "guild_member", column2: "guid", kind: "LEFT" }, { table1: 'characters', column1: 'guid', table2: 'guild_member', column2: 'guid', kind: 'LEFT' },
{ table1: "guild_member", column1: "guildid", table2: "guild", column2: "guildid", kind: "LEFT" }, { table1: 'guild_member', column1: 'guildid', table2: 'guild', column2: 'guildid', kind: 'LEFT' },
]; ];
ssp.extraDataColumns = ["`characters`.`gender`"]; ssp.extraDataColumns = ['`characters`.`gender`'];
if (this.armory.config.hideGameMasters) { if (this.armory.config.hideGameMasters) {
ssp.joins.push({ table1: "characters", column1: "account", table2: "account_access", column2: "id", database2: realm.authDatabase, kind: "LEFT" }); ssp.joins.push({
ssp = ssp.where(`\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0`); table1: 'characters',
column1: 'account',
table2: 'account_access',
column2: 'id',
database2: realm.authDatabase,
kind: 'LEFT',
});
ssp = ssp.where(
`\`account_access\`.\`id\` IS NULL OR \`account_access\`.\`RealmID\` NOT IN (-1, ${realm.realmId}) OR \`account_access\`.\`gmlevel\` = 0`,
);
} }
const result = await ssp const result = await ssp.where('`deleteInfos_Account` IS NULL').run(this.armory.config.dbQueryTimeout);
.where("`deleteInfos_Account` IS NULL")
.run(this.armory.config.dbQueryTimeout);
(result as any).realm = realm.name; (result as any).realm = realm.name;
res.json(result); res.json(result);

View file

@ -1,6 +1,6 @@
import * as fs from "fs"; import * as fs from 'fs';
const fsp = fs.promises; const fsp = fs.promises;
import * as path from "path"; import * as path from 'path';
export class CharacterCustomization { export class CharacterCustomization {
private data: { [key: number]: { [key: number]: any } }; private data: { [key: number]: { [key: number]: any } };

View file

@ -1,7 +1,7 @@
import * as fs from "fs"; import * as fs from 'fs';
import * as path from "path"; import * as path from 'path';
import * as camelCase from "camelcase"; import * as camelCase from 'camelcase';
export interface IGlyphProperties { export interface IGlyphProperties {
id: number; id: number;
@ -148,11 +148,13 @@ class AsyncGenWrapper<T> implements IAsyncGeneratorWithArrayMethods<T> {
} }
public static from<T>(array: T[]): AsyncGenWrapper<T> { public static from<T>(array: T[]): AsyncGenWrapper<T> {
return new AsyncGenWrapper<T>(async function* () { return new AsyncGenWrapper<T>(
(async function* () {
for (const x of array) { for (const x of array) {
yield x; yield x;
} }
}()); })(),
);
} }
public async *[Symbol.asyncIterator](): AsyncGenerator<T> { public async *[Symbol.asyncIterator](): AsyncGenerator<T> {
@ -217,11 +219,10 @@ class DbcReader<T> {
return; return;
} }
const headerCols = headerLine.value const headerCols = headerLine.value.map((header) => camelCase(header).replace(/[\[\]]/g, ''));
.map(header => camelCase(header).replace(/[\[\]]/g, ""));
for await (const arr of itr) { for await (const arr of itr) {
const cols = arr.map(value => isNaN(value as any) ? value : parseInt(value, 10)); const cols = arr.map((value) => (isNaN(value as any) ? value : parseInt(value, 10)));
const row = {}; const row = {};
headerCols.forEach((header, headerIdx) => { headerCols.forEach((header, headerIdx) => {
if (this.fields.length === 0 || this.fields.includes(header)) { if (this.fields.length === 0 || this.fields.includes(header)) {
@ -242,9 +243,10 @@ class DbcReader<T> {
const str = chunk.toString(); const str = chunk.toString();
// Iterate over each character, keep track of current column (of the returned array) // Iterate over each character, keep track of current column (of the returned array)
for (let c = 0; c < str.length; ++c) { for (let c = 0; c < str.length; ++c) {
let ch = str[c], nch = str[c + 1]; // Current character, next character let ch = str[c],
nch = str[c + 1]; // Current character, next character
if (!(col in arr)) { if (!(col in arr)) {
arr[col] = ""; // Create a new column (start with empty string) if necessary arr[col] = ''; // Create a new column (start with empty string) if necessary
} }
// If the current character is a quotation mark, and we're inside a // If the current character is a quotation mark, and we're inside a
@ -294,41 +296,53 @@ class DbcReader<T> {
} }
} }
const dir = path.join(process.cwd(), "data"); const dir = path.join(process.cwd(), 'data');
export const DbcFiles = { export const DbcFiles = {
achievement: path.join(dir, "Achievement_3.3.5_12340.csv"), achievement: path.join(dir, 'Achievement_3.3.5_12340.csv'),
achievementCategory: path.join(dir, "AchievementCategory_3.3.5_12340.csv"), achievementCategory: path.join(dir, 'AchievementCategory_3.3.5_12340.csv'),
glyphProperties: path.join(dir, "GlyphProperties_3.3.5_12340.csv"), glyphProperties: path.join(dir, 'GlyphProperties_3.3.5_12340.csv'),
item: path.join(dir, "Item_3.3.5_12340.csv"), item: path.join(dir, 'Item_3.3.5_12340.csv'),
itemRetail: path.join(dir, "Item_9.2.0_41462.csv"), itemRetail: path.join(dir, 'Item_9.2.0_41462.csv'),
itemAppearance: path.join(dir, "ItemAppearance_9.2.0_41462.csv"), itemAppearance: path.join(dir, 'ItemAppearance_9.2.0_41462.csv'),
itemModifiedAppearance: path.join(dir, "ItemModifiedAppearance_9.2.0_41462.csv"), itemModifiedAppearance: path.join(dir, 'ItemModifiedAppearance_9.2.0_41462.csv'),
itemDisplayInfo: path.join(dir, "ItemDisplayInfo_3.3.5_12340.csv"), itemDisplayInfo: path.join(dir, 'ItemDisplayInfo_3.3.5_12340.csv'),
mount: path.join(dir, "Mount_9.2.0_41462.csv"), mount: path.join(dir, 'Mount_9.2.0_41462.csv'),
mountDisplay: path.join(dir, "MountXDisplay_9.2.0_41462.csv"), mountDisplay: path.join(dir, 'MountXDisplay_9.2.0_41462.csv'),
spell: path.join(dir, "Spell_3.3.5_12340.csv"), spell: path.join(dir, 'Spell_3.3.5_12340.csv'),
spellItemEnchantment: path.join(dir, "SpellItemEnchantment_3.3.5_12340.csv"), spellItemEnchantment: path.join(dir, 'SpellItemEnchantment_3.3.5_12340.csv'),
spellIcon: path.join(dir, "SpellIcon_3.3.5_12340.csv"), spellIcon: path.join(dir, 'SpellIcon_3.3.5_12340.csv'),
talent: path.join(dir, "Talent_3.3.5_12340.csv"), talent: path.join(dir, 'Talent_3.3.5_12340.csv'),
talentTab: path.join(dir, "TalentTab_3.3.5_12340.csv"), talentTab: path.join(dir, 'TalentTab_3.3.5_12340.csv'),
}; };
const dbcFields = { const dbcFields = {
achievement: ["id", "faction", "titleLang0", "descriptionLang0", "category", "points", "flags", "iconId"], achievement: ['id', 'faction', 'titleLang0', 'descriptionLang0', 'category', 'points', 'flags', 'iconId'],
achievementCategory: ["id", "parent", "nameLang0"], achievementCategory: ['id', 'parent', 'nameLang0'],
glyphProperties: ["id", "spellId"], glyphProperties: ['id', 'spellId'],
item: ["id", "classId", "subclassId", "displayInfoId", "inventoryType"], item: ['id', 'classId', 'subclassId', 'displayInfoId', 'inventoryType'],
itemRetail: ["id", "inventoryType"], itemRetail: ['id', 'inventoryType'],
itemAppearance: ["id", "itemDisplayInfoId"], itemAppearance: ['id', 'itemDisplayInfoId'],
itemModifiedAppearance: ["id", "itemId", "itemAppearanceId"], itemModifiedAppearance: ['id', 'itemId', 'itemAppearanceId'],
itemDisplayInfo: ["id", "inventoryIcon0"], itemDisplayInfo: ['id', 'inventoryIcon0'],
mount: ["id", "sourceSpellId"], mount: ['id', 'sourceSpellId'],
mountDisplay: ["id", "creatureDisplayInfoId", "mountId"], mountDisplay: ['id', 'creatureDisplayInfoId', 'mountId'],
spell: ["id", "mechanic", "spellIconId"], spell: ['id', 'mechanic', 'spellIconId'],
spellItemEnchantment: ["id", "srcItemId"], spellItemEnchantment: ['id', 'srcItemId'],
spellIcon: ["id", "textureFilename"], spellIcon: ['id', 'textureFilename'],
talent: ["id", "tabId", "tierId", "columnIndex", "spellRank0", "spellRank1", "spellRank2", "spellRank3", "spellRank4", "prereqTalent0", "prereqRank0"], talent: [
talentTab: ["id", "nameLang0", "spellIconId", "classMask"], 'id',
'tabId',
'tierId',
'columnIndex',
'spellRank0',
'spellRank1',
'spellRank2',
'spellRank3',
'spellRank4',
'prereqTalent0',
'prereqRank0',
],
talentTab: ['id', 'nameLang0', 'spellIconId', 'classMask'],
}; };
export class DbcManager { export class DbcManager {
@ -350,17 +364,26 @@ export class DbcManager {
public async loadAllFiles(): Promise<void> { public async loadAllFiles(): Promise<void> {
this._achievement = await this.read<IAchievement>(DbcFiles.achievement, dbcFields.achievement).toArray(); this._achievement = await this.read<IAchievement>(DbcFiles.achievement, dbcFields.achievement).toArray();
this._achievementCategory = await this.read<IAchievementCategory>(DbcFiles.achievementCategory, dbcFields.achievementCategory).toArray(); this._achievementCategory = await this.read<IAchievementCategory>(
DbcFiles.achievementCategory,
dbcFields.achievementCategory,
).toArray();
this._glyphProperties = await this.read<IGlyphProperties>(DbcFiles.glyphProperties, dbcFields.glyphProperties).toArray(); this._glyphProperties = await this.read<IGlyphProperties>(DbcFiles.glyphProperties, dbcFields.glyphProperties).toArray();
this._item = await this.read<IItemDbc>(DbcFiles.item, dbcFields.item).toArray(); this._item = await this.read<IItemDbc>(DbcFiles.item, dbcFields.item).toArray();
this._itemRetail = await this.read<IItemRetailDbc>(DbcFiles.itemRetail, dbcFields.itemRetail).toArray(); this._itemRetail = await this.read<IItemRetailDbc>(DbcFiles.itemRetail, dbcFields.itemRetail).toArray();
this._itemAppearance = await this.read<IItemAppearanceDbc>(DbcFiles.itemAppearance, dbcFields.itemAppearance).toArray(); this._itemAppearance = await this.read<IItemAppearanceDbc>(DbcFiles.itemAppearance, dbcFields.itemAppearance).toArray();
this._itemModifiedAppearance = await this.read<IItemModifiedAppearanceDbc>(DbcFiles.itemModifiedAppearance, dbcFields.itemModifiedAppearance).toArray(); this._itemModifiedAppearance = await this.read<IItemModifiedAppearanceDbc>(
DbcFiles.itemModifiedAppearance,
dbcFields.itemModifiedAppearance,
).toArray();
this._itemDisplayInfo = await this.read<IItemDisplayInfoDbc>(DbcFiles.itemDisplayInfo, dbcFields.itemDisplayInfo).toArray(); this._itemDisplayInfo = await this.read<IItemDisplayInfoDbc>(DbcFiles.itemDisplayInfo, dbcFields.itemDisplayInfo).toArray();
this._mount = await this.read<IMountDbc>(DbcFiles.mount, dbcFields.mount).toArray(); this._mount = await this.read<IMountDbc>(DbcFiles.mount, dbcFields.mount).toArray();
this._mountDisplay = await this.read<IMountXDisplayDbc>(DbcFiles.mountDisplay, dbcFields.mountDisplay).toArray(); this._mountDisplay = await this.read<IMountXDisplayDbc>(DbcFiles.mountDisplay, dbcFields.mountDisplay).toArray();
this._spell = await this.read<ISpellDbc>(DbcFiles.spell, dbcFields.spell).toArray(); this._spell = await this.read<ISpellDbc>(DbcFiles.spell, dbcFields.spell).toArray();
this._spellItemEnchantment = await this.read<ISpellItemEnchantmentDbc>(DbcFiles.spellItemEnchantment, dbcFields.spellItemEnchantment).toArray(); this._spellItemEnchantment = await this.read<ISpellItemEnchantmentDbc>(
DbcFiles.spellItemEnchantment,
dbcFields.spellItemEnchantment,
).toArray();
this._spellIcon = await this.read<ISpellIcon>(DbcFiles.spellIcon, dbcFields.spellIcon).toArray(); this._spellIcon = await this.read<ISpellIcon>(DbcFiles.spellIcon, dbcFields.spellIcon).toArray();
this._talent = await this.read<ITalent>(DbcFiles.talent, dbcFields.talent).toArray(); this._talent = await this.read<ITalent>(DbcFiles.talent, dbcFields.talent).toArray();
this._talentTab = await this.read<ITalentTab>(DbcFiles.talentTab, dbcFields.talentTab).toArray(); this._talentTab = await this.read<ITalentTab>(DbcFiles.talentTab, dbcFields.talentTab).toArray();

View file

@ -1,8 +1,8 @@
import "dotenv/config"; import 'dotenv/config';
import { Armory } from "./Armory"; import { Armory } from './Armory';
async function main(): Promise<void> { async function main(): Promise<void> {
require("source-map-support").install(); require('source-map-support').install();
const armory = new Armory(); const armory = new Armory();
await armory.start(); await armory.start();

View file

@ -1,21 +1,21 @@
import * as fs from "fs"; import * as fs from 'fs';
const fsp = fs.promises; const fsp = fs.promises;
import * as path from "path"; import * as path from 'path';
import * as pako from "pako"; import * as pako from 'pako';
import fetch from "node-fetch"; import fetch from 'node-fetch';
import * as mkdirp from "mkdirp"; import * as mkdirp from 'mkdirp';
import * as glob from "glob-promise"; import * as glob from 'glob-promise';
import { Response } from "node-fetch"; import { Response } from 'node-fetch';
import * as prettyMs from "pretty-ms"; import * as prettyMs from 'pretty-ms';
import * as cliProgress from "cli-progress"; import * as cliProgress from 'cli-progress';
import promisepool = require("@supercharge/promise-pool"); import promisepool = require('@supercharge/promise-pool');
import { DbcManager, IItemAppearanceDbc, IItemModifiedAppearanceDbc, IMountDbc, IMountXDisplayDbc } from "../armory/data/DbcReader"; import { DbcManager, IItemAppearanceDbc, IItemModifiedAppearanceDbc, IMountDbc, IMountXDisplayDbc } from '../armory/data/DbcReader';
require("source-map-support").install(); require('source-map-support').install();
const baseUrl = "https://wow.zamimg.com/modelviewer/live"; const baseUrl = 'https://wow.zamimg.com/modelviewer/live';
class Stopwatch { class Stopwatch {
private startTime: number; private startTime: number;
@ -30,8 +30,8 @@ class Stopwatch {
public stop(text?: string): void { public stop(text?: string): void {
const dt = Date.now() - this.startTime; const dt = Date.now() - this.startTime;
const txt = text ?? "Done in {time}"; const txt = text ?? 'Done in {time}';
console.log(txt.replace("{time}", prettyMs(dt))); console.log(txt.replace('{time}', prettyMs(dt)));
} }
} }
@ -54,9 +54,12 @@ class Progress {
} }
private createProgressBar(text: string, total: number): cliProgress.SingleBar { private createProgressBar(text: string, total: number): cliProgress.SingleBar {
const progress = new cliProgress.SingleBar({ const progress = new cliProgress.SingleBar(
{
format: `${text} {bar} {percentage}% ({value} / {total})`, format: `${text} {bar} {percentage}% ({value} / {total})`,
}, cliProgress.Presets.shades_classic); },
cliProgress.Presets.shades_classic,
);
progress.start(total, 0); progress.start(total, 0);
return progress; return progress;
} }
@ -67,7 +70,7 @@ class HttpRequestError extends Error {
public constructor(response: Response) { public constructor(response: Response) {
super(`Could not download ${response.url} (${response.status})`); super(`Could not download ${response.url} (${response.status})`);
this.name = "HttpRequestError"; this.name = 'HttpRequestError';
this.response = response; this.response = response;
Object.setPrototypeOf(this, HttpRequestError.prototype); Object.setPrototypeOf(this, HttpRequestError.prototype);
} }
@ -90,7 +93,7 @@ const texturesDownloadQueue = new Set<number>();
const bonesDownloadQueue = new Set<number>(); const bonesDownloadQueue = new Set<number>();
async function download(dir: string, file: string): Promise<string | any> { async function download(dir: string, file: string): Promise<string | any> {
const dataDir = path.join(process.cwd(), "data"); const dataDir = path.join(process.cwd(), 'data');
const fullPath = `${dir}/${file}`; const fullPath = `${dir}/${file}`;
try { try {
@ -101,7 +104,7 @@ async function download(dir: string, file: string): Promise<string | any> {
await mkdirp(path.join(dataDir, dir)); await mkdirp(path.join(dataDir, dir));
if (res.headers.get("Content-Type") === "application/json") { if (res.headers.get('Content-Type') === 'application/json') {
const json = await res.json(); const json = await res.json();
fsp.writeFile(path.join(dataDir, fullPath), JSON.stringify(json)); fsp.writeFile(path.join(dataDir, fullPath), JSON.stringify(json));
return json; return json;
@ -109,8 +112,8 @@ async function download(dir: string, file: string): Promise<string | any> {
const fileStream = fs.createWriteStream(path.join(dataDir, fullPath)); const fileStream = fs.createWriteStream(path.join(dataDir, fullPath));
await new Promise((resolve, rej) => { await new Promise((resolve, rej) => {
res.body.pipe(fileStream); res.body.pipe(fileStream);
res.body.on("error", rej); res.body.on('error', rej);
fileStream.on("finish", resolve); fileStream.on('finish', resolve);
}); });
return fileStream.path.toString(); return fileStream.path.toString();
@ -141,7 +144,7 @@ function queueTexturesAndModels(item: any): void {
} }
} }
if (typeof item.Model === "number" && item.Model !== 0) { if (typeof item.Model === 'number' && item.Model !== 0) {
modelsDownloadQueue.add(item.Model); modelsDownloadQueue.add(item.Model);
} }
@ -163,38 +166,30 @@ function queueTexturesAndModels(item: any): void {
} }
async function downloadRaces(): Promise<void> { async function downloadRaces(): Promise<void> {
const races = [ const races = ['human', 'nightelf', 'dwarf', 'gnome', 'draenei', 'orc', 'troll', 'tauren', 'bloodelf', 'scourge'];
"human", const genders = ['male', 'female'];
"nightelf",
"dwarf",
"gnome",
"draenei",
"orc",
"troll",
"tauren",
"bloodelf",
"scourge",
];
const genders = ["male", "female"];
const raceGenderCombo = races.map((race) => [race + genders[0], race + genders[1]]).flat(); const raceGenderCombo = races.map((race) => [race + genders[0], race + genders[1]]).flat();
const progress = new Progress("Downloading races data...", raceGenderCombo.length); const progress = new Progress('Downloading races data...', raceGenderCombo.length);
await promisepool.PromisePool await promisepool.PromisePool.for(raceGenderCombo)
.for(raceGenderCombo)
.withConcurrency(4) .withConcurrency(4)
.process(async (race) => { .process(async (race) => {
const characterJson = await download("meta/character", `${race}.json`); const characterJson = await download('meta/character', `${race}.json`);
modelsDownloadQueue.add(characterJson.Model); modelsDownloadQueue.add(characterJson.Model);
const customizationJson = await download("meta/charactercustomization2", `${characterJson.Race}_${characterJson.Gender}.json`); const customizationJson = await download('meta/charactercustomization2', `${characterJson.Race}_${characterJson.Gender}.json`);
for (const option of customizationJson.Options) { for (const option of customizationJson.Options) {
for (const choice of option.Choices) { for (const choice of option.Choices) {
for (const element of choice.Elements) { for (const element of choice.Elements) {
if (element.SkinnedModel !== null && typeof element.SkinnedModel.CollectionFileDataID === "number" && element.SkinnedModel.CollectionFileDataID !== 0) { if (
element.SkinnedModel !== null &&
typeof element.SkinnedModel.CollectionFileDataID === 'number' &&
element.SkinnedModel.CollectionFileDataID !== 0
) {
modelsDownloadQueue.add(element.SkinnedModel.CollectionFileDataID); modelsDownloadQueue.add(element.SkinnedModel.CollectionFileDataID);
} }
if (element.BoneSet !== null && typeof element.BoneSet.BoneFileDataID === "number" && element.BoneSet.BoneFileDataID !== 0) { if (element.BoneSet !== null && typeof element.BoneSet.BoneFileDataID === 'number' && element.BoneSet.BoneFileDataID !== 0) {
bonesDownloadQueue.add(element.BoneSet.BoneFileDataID); bonesDownloadQueue.add(element.BoneSet.BoneFileDataID);
} }
} }
@ -202,7 +197,7 @@ async function downloadRaces(): Promise<void> {
} }
const textureFiles = Object.keys(customizationJson.TextureFiles) const textureFiles = Object.keys(customizationJson.TextureFiles)
.map(key => customizationJson.TextureFiles[key]) .map((key) => customizationJson.TextureFiles[key])
.flat(); .flat();
for (const file of textureFiles) { for (const file of textureFiles) {
texturesDownloadQueue.add(file.FileDataId); texturesDownloadQueue.add(file.FileDataId);
@ -215,12 +210,14 @@ async function downloadRaces(): Promise<void> {
} }
async function downloadArmors(): Promise<void> { async function downloadArmors(): Promise<void> {
const rows = await dbc.item().filter((row) => row.classId === classIdArmor).toArray(); const rows = await dbc
.item()
.filter((row) => row.classId === classIdArmor)
.toArray();
const progress = new Progress("Downloading armor data...", rows.length); const progress = new Progress('Downloading armor data...', rows.length);
await promisepool.PromisePool await promisepool.PromisePool.for(rows)
.for(rows)
.withConcurrency(50) .withConcurrency(50)
.process(async (row) => { .process(async (row) => {
const modifiedAppearance = dbcItemModifiedAppearanceByItemId[row.id]; const modifiedAppearance = dbcItemModifiedAppearanceByItemId[row.id];
@ -229,7 +226,7 @@ async function downloadArmors(): Promise<void> {
return; return;
} }
const appearance = dbcItemAppearanceById[modifiedAppearance.itemAppearanceId]; const appearance = dbcItemAppearanceById[modifiedAppearance.itemAppearanceId];
const metaPath = [invTypeShield, invTypeOffHand].includes(row.inventoryType) ? "item" : `armor/${row.inventoryType}`; const metaPath = [invTypeShield, invTypeOffHand].includes(row.inventoryType) ? 'item' : `armor/${row.inventoryType}`;
try { try {
const itemJson = await download(`meta/${metaPath}`, `${appearance.itemDisplayInfoId}.json`); const itemJson = await download(`meta/${metaPath}`, `${appearance.itemDisplayInfoId}.json`);
queueTexturesAndModels(itemJson); queueTexturesAndModels(itemJson);
@ -247,12 +244,14 @@ async function downloadArmors(): Promise<void> {
} }
async function downloadWeapons(): Promise<void> { async function downloadWeapons(): Promise<void> {
const rows = await dbc.item().filter((row) => row.classId === classIdWeapon).toArray(); const rows = await dbc
.item()
.filter((row) => row.classId === classIdWeapon)
.toArray();
const progress = new Progress("Downloading weapon data...", rows.length); const progress = new Progress('Downloading weapon data...', rows.length);
await promisepool.PromisePool await promisepool.PromisePool.for(rows)
.for(rows)
.withConcurrency(50) .withConcurrency(50)
.process(async (row) => { .process(async (row) => {
const modifiedAppearance = dbcItemModifiedAppearanceByItemId[row.id]; const modifiedAppearance = dbcItemModifiedAppearanceByItemId[row.id];
@ -278,7 +277,7 @@ async function downloadWeapons(): Promise<void> {
} }
async function readDbcData(): Promise<void> { async function readDbcData(): Promise<void> {
console.log("Reading DBC data..."); console.log('Reading DBC data...');
dbc = new DbcManager(); dbc = new DbcManager();
await dbc.loadAllFiles(); await dbc.loadAllFiles();
@ -307,11 +306,13 @@ async function readDbcData(): Promise<void> {
} }
async function downloadMounts(): Promise<void> { async function downloadMounts(): Promise<void> {
const mountSpells = await dbc.spell().filter(spell => spell.mechanic === spellMechanicMounted).toArray(); const mountSpells = await dbc
const progress = new Progress("Downloading mount data...", mountSpells.length); .spell()
.filter((spell) => spell.mechanic === spellMechanicMounted)
.toArray();
const progress = new Progress('Downloading mount data...', mountSpells.length);
await promisepool.PromisePool await promisepool.PromisePool.for(mountSpells)
.for(mountSpells)
.withConcurrency(50) .withConcurrency(50)
.process(async (spell) => { .process(async (spell) => {
const mount = dbcMountBySourceSpellId[spell.id]; const mount = dbcMountBySourceSpellId[spell.id];
@ -321,7 +322,7 @@ async function downloadMounts(): Promise<void> {
} }
const display = dbcMountDisplayByMountId[mount.id]; const display = dbcMountDisplayByMountId[mount.id];
const json = await download("meta/npc", `${display.creatureDisplayInfoId}.json`); const json = await download('meta/npc', `${display.creatureDisplayInfoId}.json`);
queueTexturesAndModels(json); queueTexturesAndModels(json);
progress.increment(); progress.increment();
@ -331,13 +332,12 @@ async function downloadMounts(): Promise<void> {
} }
async function downloadTextures(): Promise<void> { async function downloadTextures(): Promise<void> {
const progress = new Progress("Downloading textures...", texturesDownloadQueue.size); const progress = new Progress('Downloading textures...', texturesDownloadQueue.size);
await promisepool.PromisePool await promisepool.PromisePool.for(Array.from(texturesDownloadQueue))
.for(Array.from(texturesDownloadQueue))
.withConcurrency(25) .withConcurrency(25)
.process(async (fileDataId) => { .process(async (fileDataId) => {
await download("textures", `${fileDataId}.png`); await download('textures', `${fileDataId}.png`);
progress.increment(); progress.increment();
}); });
@ -345,13 +345,12 @@ async function downloadTextures(): Promise<void> {
} }
async function downloadModels(): Promise<void> { async function downloadModels(): Promise<void> {
const progress = new Progress("Downloading models...", modelsDownloadQueue.size); const progress = new Progress('Downloading models...', modelsDownloadQueue.size);
await promisepool.PromisePool await promisepool.PromisePool.for(Array.from(modelsDownloadQueue))
.for(Array.from(modelsDownloadQueue))
.withConcurrency(25) .withConcurrency(25)
.process(async (fileDataId) => { .process(async (fileDataId) => {
await download("mo3", `${fileDataId}.mo3`); await download('mo3', `${fileDataId}.mo3`);
progress.increment(); progress.increment();
}); });
@ -359,14 +358,13 @@ async function downloadModels(): Promise<void> {
} }
async function downloadBones(): Promise<void> { async function downloadBones(): Promise<void> {
const progress = new Progress("Downloading bones...", bonesDownloadQueue.size); const progress = new Progress('Downloading bones...', bonesDownloadQueue.size);
await promisepool.PromisePool await promisepool.PromisePool.for(Array.from(bonesDownloadQueue))
.for(Array.from(bonesDownloadQueue))
.withConcurrency(25) .withConcurrency(25)
.process(async (fileDataId) => { .process(async (fileDataId) => {
try { try {
await download("bone", `${fileDataId}.bone`); await download('bone', `${fileDataId}.bone`);
progress.increment(); progress.increment();
} catch (err) { } catch (err) {
if (err instanceof HttpRequestError && err.response.status === 404) { if (err instanceof HttpRequestError && err.response.status === 404) {
@ -381,11 +379,10 @@ async function downloadBones(): Promise<void> {
} }
async function parseModels(): Promise<void> { async function parseModels(): Promise<void> {
const files = await glob("data/mo3/*.mo3"); const files = await glob('data/mo3/*.mo3');
const progress = new Progress("Reading model files for texture references...", files.length); const progress = new Progress('Reading model files for texture references...', files.length);
await promisepool.PromisePool await promisepool.PromisePool.for(files)
.for(files)
.withConcurrency(20) .withConcurrency(20)
.process(async (file) => { .process(async (file) => {
const buffer = await fsp.readFile(file); const buffer = await fsp.readFile(file);
@ -428,7 +425,7 @@ async function main(): Promise<void> {
await downloadTextures(); // Download all queued textures await downloadTextures(); // Download all queued textures
await downloadBones(); // Download all queued bones await downloadBones(); // Download all queued bones
sw.stop("Everything done in {time}"); sw.stop('Everything done in {time}');
} }
main(); main();

View file

@ -1,30 +1,33 @@
function waitForEmblemImages($emblem) { function waitForEmblemImages($emblem) {
return Promise.all($emblem.find(".images img").map((idx, img) => { return Promise.all(
$emblem.find('.images img').map((idx, img) => {
return new Promise((res, rej) => { return new Promise((res, rej) => {
if (img.complete) { if (img.complete) {
res(); res();
} else { } else {
img.addEventListener("load", res); img.addEventListener('load', res);
img.addEventListener("error", rej); img.addEventListener('error', rej);
} }
}); });
})); }),
);
} }
function createGuildEmblem(emblem, el) { function createGuildEmblem(emblem, el) {
const $emblem = $(el); const $emblem = $(el);
const canvas = $emblem.find("canvas")[0]; const canvas = $emblem.find('canvas')[0];
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext('2d');
const imgUrl = (type, section, value, value2) => `${handlebarsData.websiteRoot}/img/guild-emblems/${type}_${value}${value2 ? ("_" + value2) : ""}_T${section}_U.PNG`; const imgUrl = (type, section, value, value2) =>
`${handlebarsData.websiteRoot}/img/guild-emblems/${type}_${value}${value2 ? '_' + value2 : ''}_T${section}_U.PNG`;
const $images = $("<div>").addClass("images").appendTo($emblem); const $images = $('<div>').addClass('images').appendTo($emblem);
const bgUpper = $("<img>").attr("src", imgUrl("Background", "U", emblem.background)).appendTo($images)[0]; const bgUpper = $('<img>').attr('src', imgUrl('Background', 'U', emblem.background)).appendTo($images)[0];
const bgLower = $("<img>").attr("src", imgUrl("Background", "L", emblem.background)).appendTo($images)[0]; const bgLower = $('<img>').attr('src', imgUrl('Background', 'L', emblem.background)).appendTo($images)[0];
const iconUpper = $("<img>").attr("src", imgUrl("Emblem", "U", emblem.icon, emblem.iconColor)).appendTo($images)[0]; const iconUpper = $('<img>').attr('src', imgUrl('Emblem', 'U', emblem.icon, emblem.iconColor)).appendTo($images)[0];
const iconLower = $("<img>").attr("src", imgUrl("Emblem", "L", emblem.icon, emblem.iconColor)).appendTo($images)[0]; const iconLower = $('<img>').attr('src', imgUrl('Emblem', 'L', emblem.icon, emblem.iconColor)).appendTo($images)[0];
const borderUpper = $("<img>").attr("src", imgUrl("Border", "U", emblem.border, emblem.borderColor)).appendTo($images)[0]; const borderUpper = $('<img>').attr('src', imgUrl('Border', 'U', emblem.border, emblem.borderColor)).appendTo($images)[0];
const borderLower = $("<img>").attr("src", imgUrl("Border", "L", emblem.border, emblem.borderColor)).appendTo($images)[0]; const borderLower = $('<img>').attr('src', imgUrl('Border', 'L', emblem.border, emblem.borderColor)).appendTo($images)[0];
const drawEmblemLayer = (ctx, layer) => { const drawEmblemLayer = (ctx, layer) => {
const [upper, lower] = layer; const [upper, lower] = layer;
@ -61,18 +64,21 @@ function createGuildEmblem(emblem, el) {
function createArenaEmblem(teamSize, emblem, el) { function createArenaEmblem(teamSize, emblem, el) {
const $emblem = $(el); const $emblem = $(el);
const canvas = $emblem.find("canvas")[0]; const canvas = $emblem.find('canvas')[0];
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext('2d');
const imgUrl = (teamSize, type, value) => `${handlebarsData.websiteRoot}/img/arena-banners/PVP-Banner${teamSize ? ("-" + teamSize) : ""}${type ? ("-" + type) : ""}${value ? ("-" + value) : ""}.PNG`; const imgUrl = (teamSize, type, value) =>
`${handlebarsData.websiteRoot}/img/arena-banners/PVP-Banner${teamSize ? '-' + teamSize : ''}${type ? '-' + type : ''}${
value ? '-' + value : ''
}.PNG`;
const $images = $("<div>").addClass("images").appendTo($emblem); const $images = $('<div>').addClass('images').appendTo($emblem);
const banner = $("<img>").attr("src", imgUrl(teamSize)).appendTo($images)[0]; const banner = $('<img>').attr('src', imgUrl(teamSize)).appendTo($images)[0];
const bannerCrop = $("<img>").attr("src", imgUrl(teamSize, "Crop")).appendTo($images)[0]; const bannerCrop = $('<img>').attr('src', imgUrl(teamSize, 'Crop')).appendTo($images)[0];
const border = $("<img>").attr("src", imgUrl(teamSize, "Border", emblem.border)).appendTo($images)[0]; const border = $('<img>').attr('src', imgUrl(teamSize, 'Border', emblem.border)).appendTo($images)[0];
const icon = $("<img>").attr("src", imgUrl(undefined, "Emblem", emblem.icon)).appendTo($images)[0]; const icon = $('<img>').attr('src', imgUrl(undefined, 'Emblem', emblem.icon)).appendTo($images)[0];
const colorToHex = (color) => "#" + parseInt(color).toString(16).substring(2); const colorToHex = (color) => '#' + parseInt(color).toString(16).substring(2);
waitForEmblemImages($emblem).then(() => { waitForEmblemImages($emblem).then(() => {
const srcH = 224; const srcH = 224;
@ -87,8 +93,8 @@ function createArenaEmblem(teamSize, emblem, el) {
}); });
} }
const tintCanvas = document.createElement("canvas"); const tintCanvas = document.createElement('canvas');
const tintContext = tintCanvas.getContext("2d"); const tintContext = tintCanvas.getContext('2d');
function tintImage(image, color, opacity = 1.0) { function tintImage(image, color, opacity = 1.0) {
const ctx = tintContext; const ctx = tintContext;
@ -100,12 +106,12 @@ function tintImage(image, color, opacity = 1.0) {
// Multiply with a rectangle of the specified color // Multiply with a rectangle of the specified color
ctx.fillStyle = color; ctx.fillStyle = color;
ctx.globalCompositeOperation = "multiply"; ctx.globalCompositeOperation = 'multiply';
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
// Finally, fix masking issues and globalAlpha // Finally, fix masking issues and globalAlpha
ctx.globalAlpha = opacity; ctx.globalAlpha = opacity;
ctx.globalCompositeOperation = "destination-in"; ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(image, 0, 0); ctx.drawImage(image, 0, 0);
return ctx.canvas; return ctx.canvas;

View file

@ -1,3 +1,6 @@
window.parent.postMessage({ window.parent.postMessage(
url: window.location.pathname.replace(handlebarsData.websiteRoot, ""), {
}, "*"); url: window.location.pathname.replace(handlebarsData.websiteRoot, ''),
},
'*',
);

10203
static/js/viewer.min.js vendored

File diff suppressed because one or more lines are too long