import { allItems, _Tiers, Tiers } from "../../Types/Items"; import { Inject, Injectable } from "../../Injector/ServiceDecorator"; import { ItemManagerFeatureIfc, ItemManagerIfc } from "./RPCInterface"; import { FrontworkComponent } from "../../Types/FrontworkComponent"; import { TableDefinitionExporter } from "../../Types/Interfaces"; import { TableDefiniton, Item, User, Character, SRToken, SRPriority, Spec, Signup, Raid, Stats } from "../../Types/Types"; import { IAdmin } from "../../Admin/Interface"; import { IItemManager } from "./Interface"; import { IUserManager } from "../User/Interface"; import { ICharacterManager } from "../Character/Interface"; import { IPubSub } from "../PubSub/Interface"; import { IRaidManager } from "../Raid/Interface"; import { getLogger } from "frontblock-generic/Types"; const fetch = require('node-fetch') const xml2js = require('xml2js'); const parser = new xml2js.Parser(/* options */); @Injectable(IItemManager) export class ItemManager implements FrontworkComponent, TableDefinitionExporter, IItemManager { name = "ItemManager" as "ItemManager"; @Inject(IAdmin) private admin: IAdmin @Inject(IUserManager) private userManager: IUserManager @Inject(ICharacterManager) private character: ICharacterManager @Inject(IPubSub) private pubsub: IPubSub @Inject(IRaidManager) private raidManager: IRaidManager exportRPCs = () => [ this.getItems, this.getItem, this.buyToken, this.calculatePriorities, this.getToken, this.getTokens, this.getAllPriorities ] exportRPCFeatures = () => [{ name: 'managePriorities' as 'managePriorities', exportRPCs: () => [ this.setPriority, this.deletePriority ], }, { name: 'reset' as 'reset', exportRPCs: () => [ this.wipeCurrencyAndItems ] }] notifyRaid = async (raid: Raid | { id: number }) => { await this.notifyRaids() const data = await this.raidManager.getRaidData(raid) await this.pubsub.publish("" + raid.id, data) } notifyRaids = async () => { await this.pubsub.publish('raids', undefined) } wipeCurrencyAndItems = async () => { await Promise.all([ this.userManager.wipeCurrency(), Promise.all(_Tiers.map(tier => this.admin.knex(tier + 'tokens').where(true).del())) ]) } getItems = async (): Promise => await this.admin.knex.select('*').from('items') getItem = async (name: string, tier?:Tiers): Promise => { let item = await this.admin.knex('items').select('*').where('itemname', '=', name).first() if(!item){ item = await this.fetchItem(name, tier) if (!item) return await this.admin .knex('items') .insert({ ...item, stats: JSON.stringify(item.stats) }) return this.getItem(name, tier) } item.stats = JSON.parse(item.stats) return item } fetchItem = async (name: string, tier?:Tiers): Promise => { const res = await fetch('https://classic.wowhead.com/item=' + name + '&xml'); const txt = await res.text(); const r = await parser.parseStringPromise(txt); try{ if(!r.wowhead.item || !r.wowhead.item[0]) return const j = JSON.parse('{'+r.wowhead.item[0].json[0]+"}") if(!tier){ if(j.sourcemore && Number.isInteger(j.sourcemore[0].t) && j.sourcemore[0].t > 0){ tier = _Tiers[j.sourcemore[0].t-1] }else{ tier = "MC" } } const stats = JSON.parse('{'+r.wowhead.item[0].jsonEquip[0]+"}") const item = { itemname: r.wowhead.item[0].name[0], iconname: r.wowhead.item[0].icon[0]._, url: r.wowhead.item[0].link[0], quality: r.wowhead.item[0].quality[0]._, tooltip: r.wowhead.item[0].htmlTooltip[0], stats: toStats(stats), tier: tier } return item }catch(e){ getLogger('ItemManager', 'error').error(name, e); } } getTableDefinitions(): TableDefiniton[] { return [ ...['null', ..._Tiers].map(tier => { return { name: tier + 'tokens', tableBuilder: (table) => { table.primary(['characterid', 'itemname']) table.integer("characterid") table.foreign("characterid").references("id").inTable('characters') table.string("itemname") table.foreign("itemname").references("itemname").inTable('items') table.string("signupid").nullable() table.foreign("signupid").references("id").inTable('signups').onDelete('SET NULL') table.integer("level").defaultTo(1) } } }), { name: 'priorities', tableBuilder: (table) => { table.increments('id').primary() table.string('itemname') table.foreign('itemname').references('itemname').inTable('items') table.string('race').nullable() table.integer('specid').nullable() table.foreign('specid').references('id').inTable('specs') table.integer('modifier') table.string('description').nullable() } }, { name: 'items', tableBuilder: (table) => { table.string('itemname').unique().notNullable().primary() table.string('iconname').notNullable() table.string('url').notNullable() table.string('quality').defaultTo('Epic').notNullable() table.boolean('hidden').defaultTo(false).notNullable() table.string('tooltip').notNullable() table.enu('tier', _Tiers).notNullable() table.json('stats').notNullable() } }] } buyToken = async (usertoken: string, charactername: string, itemname: string, signup: Signup): Promise<(SRToken & Character & Item) | undefined> => { const record = this.userManager.getUserRecordByToken(usertoken) const character = await this.character.getCharacterByName(charactername) if (!record || !character || record.user.username !== character.username) return const item = await this.getItem(itemname) if (!item) return const currency = await this.userManager.getCurrency(record.user, item.tier) if (currency < 1) return const streaks = await this.getTokens(character, [item.tier], false) const activeTokens = await this.getTokens(character, [item.tier], true) await this.userManager.decrementCurrency(record.user, item.tier, 1) const modifier = await this.calculatePriorities(itemname, character) const tx = await this.admin.knex.transaction() if (streaks.length > 0) { const myStreak = streaks.find(token => token.itemname === itemname) if (myStreak) { //getLogger('ItemManager').debug('update signupid and increment level') await this.admin .knex(item.tier + 'tokens') .transacting(tx) .where({ characterid: character.id, itemname: item.itemname }) .update({ signupid: signup.id, level: myStreak.level + 1 }) } //getLogger('ItemManager').debug('delete streaks') await Promise.all(streaks.map(async s => await this.admin .knex(item.tier + 'tokens') .transacting(tx) .where({ itemname: s.itemname, characterid: character.id, signupid: null }) .del() )) if (myStreak) { await tx.commit() await this.notifyRaid({ id: signup.raidid }) return await this.getToken(character, item) } } const matchingReserve = activeTokens.find(token => token.itemname === itemname) if (matchingReserve) { //getLogger('ItemManager').debug('upgrade reserve') await this.admin .knex(item.tier + 'tokens') .transacting(tx) .increment('level') .where({ signupid: signup.id, characterid: character.id, itemname: item.itemname }) } else { //getLogger('ItemManager').debug('new reserve') await this.admin .knex(item.tier + 'tokens') .transacting(tx) .insert({ characterid: character.id, itemname: item.itemname, level: 1 + modifier, signupid: signup.id }) } await tx.commit() await this.notifyRaid({ id: signup.raidid }) return await this.getToken(character, item) } getAllPriorities = async (): Promise<(SRPriority & Spec & Item)[]> => this.admin .knex('priorities as p') .join('items as i', 'p.itemname', '=', 'i.itemname') .leftJoin('specs as s', 'p.specid', '=', 's.id') .select('*') deletePriority = async (priority: SRPriority): Promise => { await this.admin.knex('priorities') .where(priority) .del() } setPriority = async (itemname: string, priority: SRPriority): Promise => { const item = await this.getItem(itemname) await this.admin .knex('priorities') .insert({ itemname: item!.itemname, ...priority }) } getPriorities = async (itemname: string): Promise => { return await this.admin .knex('priorities as p') .where('p.itemname', '=', itemname) .select('*') } calculatePriorities = async (itemname: string, character: Character): Promise => { const rules: SRPriority[] = await this.admin .knex('priorities as p') .select('*') .join('items as i', 'i.itemname', '=', 'p.itemname') .where('p.itemname', '=', itemname) return rules.map(rule => { if (rule.specid && rule.race) { if (rule.specid === character.specid && rule.race === character.race) return rule.modifier else return 0 } if (rule.specid) { if (rule.specid === character.specid) return rule.modifier else return 0 } if (rule.race) { if (rule.race === character.race) return rule.modifier else return 0 } return 0 }).reduce((prev, curr) => prev + curr, 0) } getToken = async (character: Character, item: Item, valid = true): Promise<(SRToken & Character & Item) | undefined> => { return await this.admin .knex(item.tier + 'tokens as t') .select('*') .join('characters as c', 'c.id', '=', 't.characterid') .join('items as i', 'i.itemname', '=', 't.itemname') .where({ characterid: character.id, "i.itemname": item.itemname }) .andWhere(function () { if (valid) { this.whereNotNull('t.signupid') } else { this.whereNull('t.signupid') } }) .first() } getTokens = async (character: Character, tiers: Tiers[], valid = true): Promise<(SRToken & Character & Item)[]> => { const ret = await Promise.all(tiers.map(async tier => { return await this.admin .knex(tier + 'tokens as t') .select('*') .join('characters as c', 'c.id', '=', 't.characterid') .join('items as i', 'i.itemname', '=', 't.itemname') .where({ characterid: character.id, }) .andWhere(function () { if (valid) { this.whereNotNull('t.signupid') } else { this.whereNull('t.signupid') } }) })) return ret.flat() } countItems = async (): Promise => { const count = await this.admin .knex('items') .count('*'); return count[0]['count(*)'] } private initialized = false initialize = async () => { if (this.initialized) return this.initialized = true const itemTiers = allItems const countCache = await this.countItems() if (countCache != Object.values(itemTiers).flat().length) { await Promise.all( Object.entries(itemTiers) .map((kv: [string, string[]]) => Promise.all( kv[1].map(i => this.getItem(i, kv[0] as Tiers) ) )) ) } } } function toStats(obj:any){ if(!obj) obj = {} return { stamina: obj.sta || 0, agility: obj.agi || 0, strength: obj.str || 0, intellect: obj.int || 0, spirit: obj.spi || 0, attackpower: obj.atkpwr || 0, meleehit: obj.mlehitpct || 0, meleecrit: obj.mlecritstrkpct || 0, rangeattackpower: obj.rgdatkpwr || 0, rangehit: obj.rgdhitpct || 0, rangecrit: obj.rgdcritstrkpct || 0, spellpower: obj.splpwr || 0, spellhit: obj.splhitpct || 0, spellcrit: obj.splcritstrkpct || 0, heal: obj.splheal || 0, frostspellpower: obj.frosplpwr || 0, firespellpower: obj.firsplpwr || 0, naturespellpower: obj.natsplpwr || 0, shadowspellpower: obj.shasplpwr || 0, arcanespellpower: obj.arcsplpwr || 0, fireresist: obj.firres || 0, arcaneresist: obj.arcres || 0, frostresist: obj.frores || 0, natureresist: obj.natres || 0, shadowresist: obj.shares || 0, armor: obj.armor || 0, defense: obj.def || 0, block: obj.blockpct || 0, blockvalue: obj.blockamount || 0, dodge: obj.dodgepct || 0, parry: obj.parrypct || 0, } as Stats }