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 } from "../../Types/Types"; import { IAdmin } from "../../Admin/Interface"; import { IItemManager } from "./Interface"; import { getLogger } from "log4js"; import { IUserManager } from "../User/Interface"; import { ICharacterManager } from "../Character/Interface"; import { IPubSub } from "../PubSub/Interface"; import { IRaidManager } from "../Raid/Interface"; 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}) => { const data = await this.raidManager.getRaidData(raid) this.pubsub.publish(""+raid.id, data) await this.notifyRaids() } notifyRaids = async () => { 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):Promise => { return await this.admin.knex('items').select('*').where('itemname','=',name).first() } fetchItem = async (name:string):Promise => { const res = await fetch('https://classic.wowhead.com/item='+name+'&xml'); const txt = await res.text(); const r = await parser.parseStringPromise(txt); try{ return { 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]._, } }catch (e){ console.log(name) throw 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.enu('tier', _Tiers).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) => Promise.all( kv[1].map(i => this.fetchItem(i) .then(item => this.admin .knex('items') .insert({ tier: kv[0], ...item }).catch(() => {}) ) ) )) ) } } }