implement loot system feedback

This commit is contained in:
peter
2020-02-06 23:37:17 +01:00
parent b987974b9e
commit 5ace7357a4
22 changed files with 382 additions and 199 deletions
+7
View File
@@ -102,6 +102,13 @@ implements TableDefinitionExporter, IAdmin {
private startWebsocket(){ private startWebsocket(){
this.rpcServer = new RPCServer(20000, [ this.rpcServer = new RPCServer(20000, [
...this.frontworkComponents, ...this.frontworkComponents,
{
name: "debug",
exportRPCs: () => [{
name: 'dumpDb',
call: async (table) => await this.knex(table).select('*')
}]
}
], { ], {
visibility: '0.0.0.0' visibility: '0.0.0.0'
}) })
+2 -2
View File
@@ -1,9 +1,9 @@
import { Item, Character, SRToken, SRPriority, Spec } from "../../Types/Types" import { Item, Character, SRToken, SRPriority, Spec, Signup } from "../../Types/Types"
export class IItemManager{ export class IItemManager{
getItems: () => Promise<Item[]> getItems: () => Promise<Item[]>
fetchItem: (name:string) => Promise<Item> fetchItem: (name:string) => Promise<Item>
buyToken: (usertoken: string, charactername:string, itemname:string) => Promise<(SRToken & Character & Item) | void> buyToken: (usertoken: string, charactername:string, itemname:string, signup:Signup) => Promise<(SRToken & Character & Item) | void>
setPriority: (itemname:string, priority: any) => Promise<void> setPriority: (itemname:string, priority: any) => Promise<void>
calculatePriorities: (itemname: string, character:Character) => Promise<number> calculatePriorities: (itemname: string, character:Character) => Promise<number>
deletePriority: (priority:SRPriority) => Promise<void> deletePriority: (priority:SRPriority) => Promise<void>
+96 -49
View File
@@ -1,9 +1,9 @@
import { T1, T2 } from "../../Types/Items"; import { T1, T2, allItems, _Tiers } from "../../Types/Items";
import { Inject, Injectable } from "../../Injector/ServiceDecorator"; import { Inject, Injectable } from "../../Injector/ServiceDecorator";
import { ItemManagerFeatureIfc, ItemManagerIfc } from "./RPCInterface"; import { ItemManagerFeatureIfc, ItemManagerIfc } from "./RPCInterface";
import { FrontworkComponent } from "../../Types/FrontworkComponent"; import { FrontworkComponent } from "../../Types/FrontworkComponent";
import { TableDefinitionExporter } from "../../Types/Interfaces"; import { TableDefinitionExporter } from "../../Types/Interfaces";
import { TableDefiniton, Item, User, Character, SRToken, SRPriority, Spec } from "../../Types/Types"; import { TableDefiniton, Item, User, Character, SRToken, SRPriority, Spec, Signup } from "../../Types/Types";
import { IAdmin } from "../../Admin/Interface"; import { IAdmin } from "../../Admin/Interface";
import { IItemManager } from "./Interface"; import { IItemManager } from "./Interface";
import { getLogger } from "log4js"; import { getLogger } from "log4js";
@@ -87,19 +87,21 @@ implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefin
{ {
name: 'tokens', name: 'tokens',
tableBuilder: (table) => { tableBuilder: (table) => {
table.primary(['characterid', 'itemid']) table.primary(['characterid', 'itemname'])
table.integer("characterid") table.integer("characterid")
table.foreign("characterid").references("id").inTable('characters') table.foreign("characterid").references("id").inTable('characters')
table.integer("itemid") table.string("itemname")
table.foreign("itemid").references("id").inTable('items') 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) table.integer("level").defaultTo(1)
} }
},{ },{
name: 'priorities', name: 'priorities',
tableBuilder: (table) => { tableBuilder: (table) => {
table.increments('id').primary() table.increments('id').primary()
table.integer('itemid') table.string('itemname')
table.foreign('itemid').references('id').inTable('items') table.foreign('itemname').references('itemname').inTable('items')
table.string('race').nullable() table.string('race').nullable()
table.integer('specid').nullable() table.integer('specid').nullable()
table.foreign('specid').references('id').inTable('specs') table.foreign('specid').references('id').inTable('specs')
@@ -109,17 +111,17 @@ implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefin
},{ },{
name: 'items', name: 'items',
tableBuilder: (table) => { tableBuilder: (table) => {
table.increments("id").primary() table.string('itemname').unique().notNullable().primary()
table.string('itemname').unique().notNullable()
table.string('iconname').notNullable() table.string('iconname').notNullable()
table.string('url').notNullable() table.string('url').notNullable()
table.string('quality').defaultTo('Epic').notNullable() table.string('quality').defaultTo('Epic').notNullable()
table.boolean('hidden').defaultTo(false).notNullable() table.boolean('hidden').defaultTo(false).notNullable()
table.enu('tier', _Tiers).notNullable()
} }
}] }]
} }
buyToken = async (usertoken: string, charactername:string, itemname:string): Promise<(SRToken & Character & Item) | void> => { buyToken = async (usertoken: string, charactername:string, itemname:string, signup: Signup): Promise<(SRToken & Character & Item) | void> => {
const record = this.userManager.getUserRecordByToken(usertoken) const record = this.userManager.getUserRecordByToken(usertoken)
const character = await this.character.getCharacterByName(charactername) const character = await this.character.getCharacterByName(charactername)
@@ -131,30 +133,62 @@ implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefin
const currency = await this.userManager.getCurrency(record.user) const currency = await this.userManager.getCurrency(record.user)
if(currency < 1) return if(currency < 1) return
const existingToken = await this.getToken(character, item) const shadowTokens = await this.getTokens(character, false)
const activeTokens = await this.getTokens(character, true)
await this.userManager.decrementCurrency(record.user, 1) await this.userManager.decrementCurrency(record.user, 1)
const modifier = await this.calculatePriorities(itemname, character)
if(!existingToken){ //tokens with deleted signups
const modifier = await this.calculatePriorities(itemname, character) if(shadowTokens.length > 0){
await this.admin.knex('tokens').insert({ //token for current item
characterid: character.id, const matchingtoken = shadowTokens.find(token => token.itemname === itemname)
itemid: item.id, if(matchingtoken){
level: 1+modifier //update signupid and increment level
})
}else{ console.log("delete shadow tokens");
await this.admin.knex('tokens').where({
characterid: character.id, await this.admin
itemid: item.id .knex('tokens')
}).increment('level') .update({
signupid: signup.id,
level: matchingtoken.level+1
})
.where({
characterid: character.id,
itemname: item.itemname
})
return await this.getToken(character, item)
}
} }
const matchingtoken = activeTokens.find(token => token.itemname === itemname)
if(matchingtoken){
//power up token
await this.admin
.knex('tokens')
.increment('level')
.where({
signupid: signup.id,
characterid: character.id,
itemname: item.itemname
})
}else{
await this.admin
.knex('tokens')
.insert({
characterid: character.id,
itemname: item.itemname,
level: 1+modifier,
signupid: signup.id
})
}
return await this.getToken(character, item) return await this.getToken(character, item)
} }
getAllPriorities = async() :Promise<(SRPriority & Spec & Item)[]> => this.admin getAllPriorities = async() :Promise<(SRPriority & Spec & Item)[]> => this.admin
.knex('priorities as p') .knex('priorities as p')
.join('items as i', 'p.itemid', '=', 'i.id') .join('items as i', 'p.itemname', '=', 'i.itemname')
.leftJoin('specs as s', 'p.specid', '=', 's.id') .leftJoin('specs as s', 'p.specid', '=', 's.id')
.select('*') .select('*')
@@ -170,24 +204,24 @@ implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefin
await this.admin await this.admin
.knex('priorities') .knex('priorities')
.insert(<SRPriority>{ .insert(<SRPriority>{
itemid: item.id, itemname: item.itemname,
...priority ...priority
}) })
} }
getPriorities = async(itemname:string) : Promise<SRPriority[]> => { getPriorities = async(itemname:string) : Promise<SRPriority[]> => {
const item = await this.getItem(itemname) return await this.admin
.knex('priorities as p')
return await this.admin.knex('priorities') .where('p.itemname', '=', itemname)
.where('itemid', '=', item.id)
.select('*') .select('*')
} }
calculatePriorities = async (itemname: string, character:Character):Promise<number>=> { calculatePriorities = async (itemname: string, character:Character):Promise<number>=> {
const rules : SRPriority[] = await this.admin.knex('priorities as p') const rules : SRPriority[] = await this.admin
.knex('priorities as p')
.select('*') .select('*')
.join('items as i', 'i.id', '=', 'p.itemid') .join('items as i', 'i.itemname', '=', 'p.itemname')
.where('itemname', '=', itemname) .where('p.itemname', '=', itemname)
return rules.map(rule => { return rules.map(rule => {
if(rule.specid && rule.race){ if(rule.specid && rule.race){
@@ -219,28 +253,37 @@ implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefin
.knex('tokens as t') .knex('tokens as t')
.select('*') .select('*')
.join('characters as c', 'c.id', '=', 't.characterid') .join('characters as c', 'c.id', '=', 't.characterid')
.join('items as i', 'i.id', '=', 't.itemid') .join('items as i', 'i.itemname', '=', 't.itemname')
.where({ .where({
characterid: character.id, characterid: character.id,
itemid: item.id "i.itemname": item.itemname
}) })
.first() .first()
} }
getTokens = async (character:Character) : Promise<(SRToken & Character & Item)[]> => { getTokens = async (character:Character, valid=true) : Promise<(SRToken & Character & Item)[]> => {
return await this.admin return await this.admin
.knex('tokens as t') .knex('tokens as t')
.select('*') .select('*')
.select('*')
.join('characters as c', 'c.id', '=', 't.characterid') .join('characters as c', 'c.id', '=', 't.characterid')
.join('items as i', 'i.id', '=', 't.itemid') .join('items as i', 'i.itemname', '=', 't.itemname')
.where({ .where({
characterid: character.id, characterid: character.id,
}) })
.andWhere(function(){
if(valid){
this.whereNotNull('t.signupid')
}else{
this.whereNull('t.signupid')
}
})
} }
countItems = async() :Promise<number> => { countItems = async() :Promise<number> => {
const count = await this.admin.knex('items').count('*'); const count = await this.admin
.knex('items')
.count('*');
return <number>count[0]['count(*)'] return <number>count[0]['count(*)']
} }
@@ -249,20 +292,24 @@ implements FrontworkComponent<ItemManagerIfc, ItemManagerFeatureIfc>, TableDefin
if(this.initialized) return if(this.initialized) return
this.initialized = true this.initialized = true
const allItems = [...T1, ...T2] const itemTiers = allItems
const countCache = await this.countItems() const countCache = await this.countItems()
getLogger('ItemManager').debug('Checking items, got: ',countCache, 'expected: ', allItems.length)
if(countCache != allItems.length){ if(countCache != Object.values(itemTiers).flat().length){
const items:Item[] = await Promise.all(allItems.map((i) => this.fetchItem(i))) await Promise.all(
try{ Object.entries(itemTiers)
await this.admin .map((kv) => Promise.all(
.knex('items') kv[1].map(i => this.fetchItem(i)
.insert(items) .then(item => this.admin
}catch(e){ .knex('items')
getLogger('ItemManager').debug("Skipping item insertion") .insert({
} tier: kv[0],
...item
})
))
))
)
} }
} }
} }
+2 -2
View File
@@ -1,11 +1,11 @@
import { Raid, Signup, Character, RaidData } from "../../Types/Types" import { Raid, Signup, Character, RaidData, User, Spec } from "../../Types/Types"
export class IRaidManager{ export class IRaidManager{
getRaids: () => Promise<Raid[]> getRaids: () => Promise<Raid[]>
createRaid: (raid:Raid) => Promise<any> createRaid: (raid:Raid) => Promise<any>
addSignup: (signup: Signup) => Promise<any> addSignup: (signup: Signup) => Promise<any>
removeSignup: (signup: Signup) => Promise<any> removeSignup: (signup: Signup) => Promise<any>
getSignups: (raid:Raid) => Promise<Signup[]> getSignups: (raid:Raid) => Promise<(Signup & Character & Spec & User)[]>
sign: (userToken: string, character:Character, raid:Raid, late:boolean) => Promise<any> sign: (userToken: string, character:Character, raid:Raid, late:boolean) => Promise<any>
unsign: (userToken: string, character:Character, raid:Raid,) => Promise<any> unsign: (userToken: string, character:Character, raid:Raid,) => Promise<any>
archiveRaid: (raid:Raid) => Promise<RaidData> archiveRaid: (raid:Raid) => Promise<RaidData>
+45 -22
View File
@@ -1,11 +1,12 @@
import { Inject, Injectable } from "../../Injector/ServiceDecorator"; import { Inject, Injectable } from "../../Injector/ServiceDecorator";
import { RaidManagerIfc, RaidManagerFeatureIfc } from "./RPCInterface"; import { RaidManagerIfc, RaidManagerFeatureIfc } from "./RPCInterface";
import { FrontworkComponent } from "../../Types/FrontworkComponent"; import { FrontworkComponent } from "../../Types/FrontworkComponent";
import { TableDefiniton, Signup, Raid, Character, RaidData, Spec, SRToken, Item } from "../../Types/Types"; import { TableDefiniton, Signup, Raid, Character, RaidData, Spec, SRToken, Item, User } from "../../Types/Types";
import { IAdmin } from "../../Admin/Interface"; import { IAdmin } from "../../Admin/Interface";
import { IRaidManager } from "./Interface"; import { IRaidManager } from "./Interface";
import { IUserManager } from "../User/Interface"; import { IUserManager } from "../User/Interface";
import { ICharacterManager } from "../Character/Interface"; import { ICharacterManager } from "../Character/Interface";
import { _Tiers } from "../../Types/Items";
@Injectable(IRaidManager) @Injectable(IRaidManager)
export class RaidManager export class RaidManager
@@ -59,6 +60,7 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManag
table.string('description').notNullable() table.string('description').notNullable()
table.string('title').notNullable() table.string('title').notNullable()
table.integer('size').defaultTo(40) table.integer('size').defaultTo(40)
table.enu('tier', _Tiers).defaultTo(null as any)
} }
},{ },{
name: 'archive', name: 'archive',
@@ -69,7 +71,8 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManag
},{ },{
name: 'signups', name: 'signups',
tableBuilder: (table) => { tableBuilder: (table) => {
table.primary(['raidid', 'characterid']) table.increments('id').primary()
table.unique(['raidid', 'characterid'])
table.integer('raidid') table.integer('raidid')
table.foreign('raidid').references('id').inTable('raids').onDelete('CASCADE') table.foreign('raidid').references('id').inTable('raids').onDelete('CASCADE')
table.integer('characterid') table.integer('characterid')
@@ -95,7 +98,7 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManag
raid_id: signup.raidid, raid_id: signup.raidid,
character_id: signup.characterid character_id: signup.characterid
}) })
.delete() .del()
getRaids = async () : Promise<Raid[]> => { getRaids = async () : Promise<Raid[]> => {
@@ -116,7 +119,6 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManag
startRaid = async (raid:Raid) : Promise<RaidData> => { startRaid = async (raid:Raid) : Promise<RaidData> => {
const archived = await this.archiveRaid(raid) const archived = await this.archiveRaid(raid)
delete archived.participants.late delete archived.participants.late
const giveCurrency = async (b: Character) => { const giveCurrency = async (b: Character) => {
@@ -140,6 +142,15 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManag
raiddata: JSON.stringify(raidData) raiddata: JSON.stringify(raidData)
}) })
await Promise.all(
Object.values(raidData.participants).flat().flatMap((signup) => this.admin
.knex('tokens')
.where({
characterid: signup.characterid,
signupid: null
})
.del()
))
await this.admin.knex('raids') await this.admin.knex('raids')
.where('id', '=', raid.id) .where('id', '=', raid.id)
@@ -175,17 +186,17 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManag
getRaidData = async (raid:Raid) : Promise<RaidData> => { getRaidData = async (raid:Raid) : Promise<RaidData> => {
const ret = { const ret = {
participants:{ participants:{
Druid: <(Character&Spec)[]>[], Druid: <(Signup&Character&Spec)[]>[],
Hunter: <(Character&Spec)[]>[], Hunter: <(Signup&Character&Spec)[]>[],
Mage: <(Character&Spec)[]>[], Mage: <(Signup&Character&Spec)[]>[],
Paladin: <(Character&Spec)[]>[], Paladin: <(Signup&Character&Spec)[]>[],
Priest: <(Character&Spec)[]>[], Priest: <(Signup&Character&Spec)[]>[],
Rogue: <(Character&Spec)[]>[], Rogue: <(Signup&Character&Spec)[]>[],
Shaman: <(Character&Spec)[]>[], Shaman: <(Signup&Character&Spec)[]>[],
Warlock: <(Character&Spec)[]>[], Warlock: <(Signup&Character&Spec)[]>[],
Warrior: <(Character&Spec)[]>[], Warrior: <(Signup&Character&Spec)[]>[],
late: <(Character&Spec)[]>[], late: <(Signup&Character&Spec)[]>[],
bench: <(Character&Spec)[]>[], bench: <(Signup&Character&Spec)[]>[],
}, },
tokens:{} tokens:{}
} }
@@ -205,9 +216,9 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManag
.where('id','=',raid.id) .where('id','=',raid.id)
.first() .first()
const characterData: (Character & Spec & Signup)[] = await this.admin const characterData: (Signup & Character & Spec)[] = await this.admin
.knex('signups as s') .knex('signups as s')
.select('characterid as id', 'charactername', 'class', 'specname', 'race', 'userid', 'benched', 'late', 'raidid', 'characterid') .select('s.id as id', 'charactername', 'class', 'specname', 'race', 'userid', 'benched', 'late', 'raidid', 'characterid')
.join('raids as r', 's.raidid','=','r.id') .join('raids as r', 's.raidid','=','r.id')
.join('characters as c', 's.characterid','=','c.id') .join('characters as c', 's.characterid','=','c.id')
.join('users as u', 'c.userid','=','u.id') .join('users as u', 'c.userid','=','u.id')
@@ -228,12 +239,15 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManag
const tokenData: (Character & SRToken & Item)[] = await this.admin const tokenData: (Character & SRToken & Item)[] = await this.admin
.knex('signups as s') .knex('signups as s')
.select('*') .select('*', 's.id as id')
.join('raids as r', 's.raidid','=','r.id') .join('raids as r', 's.raidid','=','r.id')
.where('r.id','=',raid.id) .where('r.id','=',raid.id)
.andWhere(function(){
this.whereNotNull('t.signupid')
})
.join('characters as c', 's.characterid','=','c.id') .join('characters as c', 's.characterid','=','c.id')
.join('tokens as t', 't.characterid','=','c.id') .join('tokens as t', 't.characterid','=','c.id')
.join('items as i', 'i.id','=','t.itemid') .join('items as i', 'i.itemname','=','t.itemname')
tokenData.forEach(data => { tokenData.forEach(data => {
if(!ret.tokens[data.itemname]) if(!ret.tokens[data.itemname])
@@ -246,12 +260,12 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManag
} }
} }
getSignups = async (raid:Raid) : Promise<Signup[]> => await this.admin getSignups = async (raid:Raid) : Promise<(Signup & Character & Spec & User)[]> => await this.admin
.knex('signups') .knex('signups as si')
.join('characters as c', 'c.id', '=', 'characterid') .join('characters as c', 'c.id', '=', 'characterid')
.join('specs as s', 's.id', '=', 'specid') .join('specs as s', 's.id', '=', 'specid')
.join('users as u', 'u.id', '=', 'userid') .join('users as u', 'u.id', '=', 'userid')
.select('*') .select('*','si.id as id')
.where('raidid', '=', raid.id!) .where('raidid', '=', raid.id!)
sign = async (usertoken:string, character:Character, raid:Raid, late:boolean) => { sign = async (usertoken:string, character:Character, raid:Raid, late:boolean) => {
@@ -286,6 +300,15 @@ implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManag
late: late late: late
}) })
} }
return await this.admin
.knex('signups')
.select('*')
.where({
raidid: raid.id!,
characterid: character.id!,
})
.first()
} }
unsign = async (usertoken:string, character:Character, raid:Raid) => { unsign = async (usertoken:string, character:Character, raid:Raid) => {
+1 -2
View File
@@ -118,14 +118,13 @@ implements FrontworkComponent<UserManagerIfc, UserManagerFeatureIfc>, IUserManag
initialize = async () => { initialize = async () => {
this.exporters = [this.guild, this.item, this.raid, this.character] this.exporters = [this.guild, this.item, this.raid, this.character]
//set up permissions //set up permissions
getLogger('UserManager').debug('inserting permissions') getLogger('UserManager').debug('setting up permissions')
await Promise.all( await Promise.all(
[this, ...this.exporters].flatMap(exp => exp.exportRPCFeatures().map(async (feature) => { [this, ...this.exporters].flatMap(exp => exp.exportRPCFeatures().map(async (feature) => {
try{ try{
await this.admin.knex.insert({ rpcname: feature.name }).into('rpcpermissions') await this.admin.knex.insert({ rpcname: feature.name }).into('rpcpermissions')
}catch(e){ }catch(e){
getLogger('UserManager').debug(feature.name);
} }
}))) })))
+16
View File
@@ -1,3 +1,6 @@
export type Tiers = "MC" | 'BWL' | 'ZG' | 'AQ20' | 'AQ40' | 'Naxx'
export const _Tiers = ["MC", 'BWL', 'ZG', 'AQ20', 'AQ40', 'Naxx']
export const T1 = [ export const T1 = [
"Robe of Volatile Power", "Robe of Volatile Power",
"Salamander Scale Pants", "Salamander Scale Pants",
@@ -133,3 +136,16 @@ export const T2:string[] = [
"Interlaced Shadow Jerkin", "Interlaced Shadow Jerkin",
"Ringo's Blizzard Boots" "Ringo's Blizzard Boots"
] ]
export type AllItems = {
[tier in Tiers] : string[]
}
export const allItems : AllItems = {
MC: T1,
BWL: T2,
ZG:[],
AQ20: [],
AQ40: [],
Naxx: []
}
+10 -6
View File
@@ -6,6 +6,7 @@ import { CharacterManagerIfc, CharacterManagerFeatureIfc } from "../Components/C
import { ItemManagerFeatureIfc, ItemManagerIfc } from "../Components/Item/RPCInterface"; import { ItemManagerFeatureIfc, ItemManagerIfc } from "../Components/Item/RPCInterface";
import { GuildManagerFeatureIfc, GuildManagerIfc } from "../Components/Guild/RPCInterface"; import { GuildManagerFeatureIfc, GuildManagerIfc } from "../Components/Guild/RPCInterface";
import { ShoutboxIfc } from "../Components/Shoutbox/RPCInterface"; import { ShoutboxIfc } from "../Components/Shoutbox/RPCInterface";
import { Tiers } from "./Items";
export type FrontcraftIfc = RaidManagerIfc export type FrontcraftIfc = RaidManagerIfc
& UserManagerIfc & UserManagerIfc
@@ -49,10 +50,10 @@ export type RPCPermission = {
export type RaidData = Raid & { export type RaidData = Raid & {
participants: { participants: {
[clazz in Class] : (Character & Spec)[] [clazz in Class] : (Signup & Character & Spec)[]
} & { } & {
late: (Character & Spec)[] late: (Signup & Character & Spec)[]
bench: (Character & Spec)[] bench: (Signup & Character & Spec)[]
} }
tokens: { tokens: {
[itemname in string]: (Character & SRToken & Item)[] [itemname in string]: (Character & SRToken & Item)[]
@@ -60,8 +61,9 @@ export type RaidData = Raid & {
} }
export type SRToken = { export type SRToken = {
signupid?:number
characterid: number, characterid: number,
itemid: number, itemname: string,
level: number level: number
} }
@@ -69,18 +71,18 @@ export type SRPriority = {
id?:number id?:number
race?:Race race?:Race
specid?:number, specid?:number,
itemid?:number, itemname?:string,
description?:string, description?:string,
modifier:number modifier:number
} }
export type Item = { export type Item = {
id?:number
itemname:string itemname:string
iconname:string iconname:string
url:string url:string
quality:string quality:string
hidden:boolean hidden:boolean
tier: Tiers
} }
export type User = { export type User = {
@@ -98,9 +100,11 @@ export type Raid = {
start: string start: string
signupcount?: number signupcount?: number
size: number size: number
tier: Tiers
} }
export type Signup = { export type Signup = {
id?:number
raidid: number raidid: number
characterid: number characterid: number
benched: boolean benched: boolean
@@ -1,13 +1,25 @@
<nb-card <nb-card
class = "col-12 col-xl-9" class = "col-12 col-xl-9"
status="control"> status="control">
<nb-card-header [ngStyle]="{'color': color}" style="text-transform: capitalize;"> <nb-card-header style="text-transform: capitalize;">
{{char.charactername}} <h4>
<a *ngIf="link === 'character'" [ngStyle]="{'color': color}" [routerLink]="'/frontcraft/character/'+char.charactername">
{{char.charactername}}
</a>
</h4>
<h3>
<span [ngStyle]="{'color': color}" *ngIf="link !== 'character'">
{{char.charactername}}
</span>
</h3>
</nb-card-header> </nb-card-header>
<nb-card-body> <nb-card-body>
{{char.race}}<br /> {{char.race}}<br />
{{char.specname}} {{char.class}}<br /> {{char.specname}} {{char.class}}<br />
Owned by <a [routerLink]="'/frontcraft/user/'+char.username"> {{char.username}} ({{char.rank}})</a> <span *ngIf="link === 'owner'">
Owned by <a [routerLink]="'/frontcraft/user/'+char.username"> {{char.username}} ({{char.rank}})</a>
</span>
<br/><br /> <br/><br />
<span *ngFor="let token of tokens"> <span *ngFor="let token of tokens">
[ {{token.level}} ] [ {{token.level}} ]
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, Input } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { ApiService as ApiService } from '../../services/login-api'; import { ApiService as ApiService } from '../../services/login-api';
import { Spec, User, Character } from '../../../../../../backend/Types/Types'; import { Spec, User, Character } from '../../../../../../backend/Types/Types';
@@ -10,6 +10,9 @@ import { getClassColor } from '../../../../../../backend/Types/PlayerSpecs';
}) })
export class FrontcraftCharacterComponent implements OnInit{ export class FrontcraftCharacterComponent implements OnInit{
@Input() name?: string
@Input() link?: "owner" | "character" = 'owner'
char : (Character & User & Spec) = {} as any char : (Character & User & Spec) = {} as any
color : string color : string
tokens tokens
@@ -20,7 +23,9 @@ export class FrontcraftCharacterComponent implements OnInit{
){} ){}
async ngOnInit(){ async ngOnInit(){
const param = this.route.snapshot.paramMap.get('name'); const param = this.name || this.route.snapshot.paramMap.get('name');
if(!param) return
this.api.get('CharacterManager') this.api.get('CharacterManager')
.getCharacterByName(param) .getCharacterByName(param)
.then((char) => { .then((char) => {
@@ -6,9 +6,6 @@
</nb-card-body> </nb-card-body>
</nb-card> </nb-card>
<nb-card class="col-12 col-xl-9"> <nb-card class="col-12 col-xl-9">
<nb-card-body> <nb-card-body>
<input type="text" nbInput [(ngModel)]="search" (change)="changeSearch()" placeholder="Search" fullWidth="true" /> <input type="text" nbInput [(ngModel)]="search" (change)="changeSearch()" placeholder="Search" fullWidth="true" />
@@ -32,11 +32,12 @@ import { FrontcraftRaidComponent } from './raid/raid.component';
import { FrontcraftArchiveComponent } from './raid/archive.component'; import { FrontcraftArchiveComponent } from './raid/archive.component';
import { NbEvaIconsModule } from '@nebular/eva-icons'; import { NbEvaIconsModule } from '@nebular/eva-icons';
import { FrontcraftCharacerpickerComponent } from './raid/characterpicker.component'; import { FrontcraftCharacerpickerComponent } from './raid/characterpicker.component';
import { FrontcraftShopComponent, FrontcraftBuyTokenComponent } from './shop/shop.component'; import { FrontcraftShopComponent } from './shop/shop.component';
import { FrontcraftRulesComponent } from './rules/rules.component'; import { FrontcraftRulesComponent } from './rules/rules.component';
import { FrontcraftItemSelectComponent } from './shop/itemselector.component'; import { FrontcraftItemSelectComponent } from './shop/itemselector.component';
import { NgxEchartsModule } from 'ngx-echarts'; import { NgxEchartsModule } from 'ngx-echarts';
import { FrontcraftCharactersComponent } from './characters/characters.component'; import { FrontcraftCharactersComponent } from './characters/characters.component';
import { FrontcraftBuyTokenComponent } from './shop/buytoken.component';
@NgModule({ @NgModule({
@@ -67,14 +67,15 @@
</div> </div>
</nb-card-body> </nb-card-body>
</nb-card> </nb-card>
</ng-container> </ng-container>
</div> </div>
</nb-tab> </nb-tab>
<nb-tab tabTitle="Shop">
<shop (onSelect)="itemSelect($event)"></shop>
</nb-tab>
<nb-tab tabTitle="Reserves"> <nb-tab tabTitle="Reserves">
<nb-list> <nb-list>
<nb-list-item *ngFor="let item of raid.tokens | keyvalue"> <nb-list-item *ngFor="let item of raid.tokens | keyvalue">
<a [ngStyle]="{'color':item.value[0].quality=='Epic'?'#a335ee':'#ff8000'}" <a [ngStyle]="{'color':item.value[0].quality=='Epic'?'#a335ee':'#ff8000'}"
target="_blank" target="_blank"
[href]="item.value[0].url"> [href]="item.value[0].url">
@@ -5,6 +5,7 @@ import { RaidData, Raid, Signup } from '../../../../../../backend/Types/Types';
import { NbWindowService, NbToastrService, NbDialogService } from '@nebular/theme'; import { NbWindowService, NbToastrService, NbDialogService } from '@nebular/theme';
import { FrontcraftCharacerpickerComponent } from './characterpicker.component'; import { FrontcraftCharacerpickerComponent } from './characterpicker.component';
import { getClassColor } from '../../../../../../backend/Types/PlayerSpecs'; import { getClassColor } from '../../../../../../backend/Types/PlayerSpecs';
import { FrontcraftBuyTokenComponent } from '../shop/buytoken.component';
@Component({ @Component({
selector: 'raid', selector: 'raid',
@@ -52,6 +53,15 @@ export class FrontcraftRaidComponent implements OnInit{
this.refresh() this.refresh()
} }
itemSelect = async(item) => {
this.dialogService.open(FrontcraftBuyTokenComponent, {
context: {
item: item,
signup: this.mySignup
}
}).onClose.subscribe(() => this.refresh())
}
signup = async () => { signup = async () => {
const signupFeature = this.api.get('signup') const signupFeature = this.api.get('signup')
if(!signupFeature) return if(!signupFeature) return
@@ -60,7 +70,7 @@ export class FrontcraftRaidComponent implements OnInit{
closeOnBackdropClick: true, closeOnBackdropClick: true,
closeOnEsc: true, closeOnEsc: true,
context: { context: {
'raid': this.raid, raid: this.raid,
} }
}).onClose.subscribe(()=>{ }).onClose.subscribe(()=>{
this.refresh() this.refresh()
@@ -83,7 +93,7 @@ export class FrontcraftRaidComponent implements OnInit{
const signupFeature = this.api.get('signup') const signupFeature = this.api.get('signup')
if(!signupFeature) return if(!signupFeature) return
await signupFeature.unsign(this.api.getAuth().token.value, this.mySignup, this.raid) await signupFeature.unsign(this.api.getAuth().token.value, <any>{id: this.mySignup.characterid, userid: this.mySignup.userid}, this.raid)
this.toast.show('Success', 'Unsigned', { status: 'success' }) this.toast.show('Success', 'Unsigned', { status: 'success' })
this.refresh() this.refresh()
} }
@@ -103,6 +113,7 @@ export class FrontcraftRaidComponent implements OnInit{
const user = this.api.getCurrentUser() const user = this.api.getCurrentUser()
const matchingSignup = Object.values(raiddata.participants).flat().find(char => user && char.userid === user.id!) const matchingSignup = Object.values(raiddata.participants).flat().find(char => user && char.userid === user.id!)
if(matchingSignup){ if(matchingSignup){
this.isSignedup = true this.isSignedup = true
this.mySignup = matchingSignup this.mySignup = matchingSignup
this.mySignup.status = matchingSignup['benched']?'Bench':matchingSignup['late']?'Late':'Attending' this.mySignup.status = matchingSignup['benched']?'Bench':matchingSignup['late']?'Late':'Attending'
@@ -4,7 +4,7 @@
<nb-tab tabTitle="Info"> <nb-tab tabTitle="Info">
Smart text here Smart text here
</nb-tab> </nb-tab>
<nb-tab *ngIf="managePriorities" tabTitle="priorities"> <nb-tab tabTitle="priorities">
<nb-list> <nb-list>
<nb-list-item *ngFor="let item of rules | keyvalue"> <nb-list-item *ngFor="let item of rules | keyvalue">
<a [ngStyle]="{'color':item.value[0].quality=='Epic'?'#a335ee':'#ff8000'}" <a [ngStyle]="{'color':item.value[0].quality=='Epic'?'#a335ee':'#ff8000'}"
@@ -0,0 +1,104 @@
import { Component, OnInit, ContentChild, AfterContentInit, ViewChild, AfterViewInit, Input, Output, EventEmitter } from '@angular/core';
import { ApiService as ApiService } from '../../services/login-api';
import { FrontcraftItemSelectComponent } from './itemselector.component';
import { NbWindowService, NbWindowRef, NbToastrService, NbDialogService, NbDialogRef } from '@nebular/theme';
import { Item, Character, SRToken, Signup } from '../../../../../../backend/Types/Types';
import { getClassColor } from '../../../../../../backend/Types/PlayerSpecs';
import { _Tiers, allItems, Tiers } from '../../../../../../backend/Types/Items';
@Component({
selector: 'buyToken',
template: `
<nb-card class="col-12 col-xl-9">
<nb-card-header>
Buy {{item.itemname}}
</nb-card-header>
<nb-card-body>
<p>
You currently have {{currency}} softreserve currency
</p>
<div *ngIf="currency>0">
<div *ngFor="let kv of modifier | keyvalue">
<button
(click)="buyToken(kv.key)"
[disabled]="currency<=0"
nbButton
outline
status="success"
size="tiny">
buy
</button>
{{kv.key}} <span *ngIf="kv.value>0">(<b>+{{kv.value}}</b> from priorities)</span>
</div>
<div *ngFor="let token of ownedtokens">
<button
(click)="buyToken(token.charactername)"
nbButton
outline
status="success"
size="tiny">
buy
</button>
{{token.charactername}} [ {{token.level}} ] => [ {{token.level+1}} ]
</div>
</div>
</nb-card-body>
</nb-card>
`,
})
export class FrontcraftBuyTokenComponent implements OnInit{
@Input() item : Item
@Input() signup : Signup
@Input() tier: Tiers
characters: Character[]
modifier = {}
currency: number = 0
ownedtokens: SRToken[] = []
constructor(
private toastr: NbToastrService,
protected dialogRef: NbDialogRef<FrontcraftBuyTokenComponent>,
private api : ApiService
){}
ngOnInit(): void {
const usr = this.api.getCurrentUser()
this.api.get('CharacterManager')
.getCharactersOfUser(usr.username)
.then(chars => {
chars.forEach(char => {
char['color'] = getClassColor(char.class)
this.api.get('ItemManager').getToken(char, this.item).then(token => {
if(token) this.ownedtokens.push(token)
else{
this.api.get('ItemManager').calculatePriorities(this.item.itemname, char).then(modifier => {
this.modifier[char.charactername] = modifier
})
}
})
})
this.characters = chars
})
this.api.get('UserManager').getUser(usr.username).then(u => {
if(!u) return
this.currency = u.currency
})
}
buyToken = async (charactername:string) => {
const src = this.api.get('ItemManager')
const token = await src.buyToken(this.api.getAuth().token.value, charactername, this.item.itemname, this.signup)
if(token){
this.toastr.show(token.characterid+' now has a token for '+token.itemname+' of level '+token.level, 'Yay', {status: 'success'})
}else{
this.toastr.show('Error (something went wrong)', 'Oh no', {status: 'danger'})
}
this.dialogRef.close()
}
}
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
import { ApiService } from '../../services/login-api'; import { ApiService } from '../../services/login-api';
import { Item } from '../../../../../../backend/Types/Types'; import { Item } from '../../../../../../backend/Types/Types';
@@ -10,10 +10,11 @@ export class FrontcraftItemSelectComponent implements OnInit{
selected: Item selected: Item
search: string search: string
items: any[] allItems:Item[] = []
displayedItems: any[] displayedItems: any[]
callbacks = [] @Input() items: string[]
@Output() onSelect = new EventEmitter<Item>();
constructor( constructor(
private api: ApiService, private api: ApiService,
@@ -21,25 +22,23 @@ export class FrontcraftItemSelectComponent implements OnInit{
} }
async ngOnInit(){ async ngOnInit(){
this.api.get('ItemManager').getItems().then(items => { Promise.all(this.items.map(itemname =>
this.items = items this.api.get('ItemManager').getItem(itemname)
this.displayedItems = items )).then(items => {
this.allItems = items
this.displayedItems = this.allItems
}) })
} }
changeSearch(){ changeSearch(){
if(!this.search || this.search == "") if(!this.search || this.search == "")
this.displayedItems = this.items this.displayedItems = this.allItems
else else
this.displayedItems = this.items.filter(it => it.itemname.toLowerCase().includes(this.search.toLowerCase())) this.displayedItems = this.allItems.filter(it => it.itemname.toLowerCase().includes(this.search.toLowerCase()))
} }
select(item: Item){ select(item: Item){
this.selected = item this.onSelect.emit(item)
this.callbacks.forEach(cb => cb(item))
} }
onselect(callback:Function){
this.callbacks.push(callback)
}
} }
@@ -1,13 +1,7 @@
<h3>
{{tier}} items
</h3>
<nb-card class="col-12 col-xl-9"> <itemselect
<nb-card-body> [items]="allItems[tier]"
<nb-tabset> (onSelect)="onSelect.emit($event)"></itemselect>
<nb-tab tabTitle="Items">
<itemselect></itemselect>
</nb-tab>
<nb-tab tabTitle="About">
</nb-tab>
</nb-tabset>
</nb-card-body>
</nb-card>
@@ -1,43 +1,28 @@
import { Component, OnInit, ContentChild, AfterContentInit, ViewChild, AfterViewInit } from '@angular/core'; import { Component, OnInit, ContentChild, AfterContentInit, ViewChild, AfterViewInit, Input, Output, EventEmitter } from '@angular/core';
import { ApiService as ApiService } from '../../services/login-api'; import { ApiService as ApiService } from '../../services/login-api';
import { FrontcraftItemSelectComponent } from './itemselector.component'; import { FrontcraftItemSelectComponent } from './itemselector.component';
import { NbWindowService, NbWindowRef, NbToastrService, NbDialogService, NbDialogRef } from '@nebular/theme'; import { NbWindowService, NbWindowRef, NbToastrService, NbDialogService, NbDialogRef } from '@nebular/theme';
import { Item, Character, SRToken } from '../../../../../../backend/Types/Types'; import { Item, Character, SRToken, Signup } from '../../../../../../backend/Types/Types';
import { getClassColor } from '../../../../../../backend/Types/PlayerSpecs'; import { getClassColor } from '../../../../../../backend/Types/PlayerSpecs';
import { _Tiers, allItems } from '../../../../../../backend/Types/Items';
@Component({ @Component({
selector: 'shop', selector: 'shop',
templateUrl: './shop.component.html', templateUrl: './shop.component.html',
}) })
export class FrontcraftShopComponent implements AfterViewInit{ export class FrontcraftShopComponent{
@ViewChild(FrontcraftItemSelectComponent, {static: false}) @Input() character: Character
itemselect !: FrontcraftItemSelectComponent @Input() signup: Signup
@Input() tier = _Tiers[0]
@Output() onSelect = new EventEmitter<Item>()
allItems = allItems
constructor( constructor(
private api: ApiService, private api: ApiService,
private dialogService : NbDialogService private dialogService : NbDialogService
){}
){
window['shop'] = this
}
buy = (item) => {
this.dialogService.open(FrontcraftBuyTokenComponent, {
closeOnBackdropClick: true,
closeOnEsc: true,
context: {
item: item
}
}).onClose.subscribe(()=>{
});
}
ngAfterViewInit(){
this.itemselect.onselect(this.buy)
}
} }
@Component({ @Component({
@@ -7,32 +7,8 @@ accent="info">
</nb-card-header> </nb-card-header>
<nb-card-body> <nb-card-body>
{{user.currency}} {{user.currency}}
<nb-card <ng-container *ngFor="let char of characters">
accent="control" <character [name]="char.charactername" [link]="'character'"></character>
*ngFor="let char of characters"> </ng-container>
<nb-card-header>
<h4>
<a [ngStyle]="{'color': char.color}" style="text-transform: capitalize;" [routerLink]="'/frontcraft/character/'+char.charactername">
{{char.charactername}}
</a>
</h4>
</nb-card-header>
<nb-card-body>
{{char.race}}<br />
{{char.specname}} {{char.class}}
<br />
<br />
<span *ngFor="let token of char.tokens">
[ {{token.level}} ]
<a [ngStyle]="{'color':token.quality=='Epic'?'#a335ee':'#ff8000'}"
target="_blank"
[href]="token.url">
<img style="min-width: 25px; width: 2.25vw; max-width: 50px"
[src]="'https://wow.zamimg.com/images/wow/icons/large/'+token.iconname+'.jpg'" />
{{token.itemname}}
</a><br />
</span>
</nb-card-body>
</nb-card>
</nb-card-body> </nb-card-body>
</nb-card> </nb-card>
@@ -134,14 +134,6 @@ export class ApiService{
} }
} }
function str2arraybuf(str:string): ArrayBuffer {
return new Buffer(str)
}
function buf2hex(buffer: ArrayBuffer) {
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}
export async function hash(value:string) : Promise<string>{ export async function hash(value:string) : Promise<string>{
return saltedHash(value, "") return saltedHash(value, "")
} }
+22 -12
View File
@@ -3,7 +3,7 @@ import { FrontworkAdmin } from "../src/backend/Admin/Admin";
import { T1 } from "../src/backend/Types/Items"; import { T1 } from "../src/backend/Types/Items";
import { RPCSocket } from "rpclibrary"; import { RPCSocket } from "rpclibrary";
import { FrontcraftIfc, Auth, User, FrontcraftFeatureIfc, Raid, Character, Rank, Class, Race, SRPriority, Spec } from "../src/backend/Types/Types"; import { FrontcraftIfc, Auth, User, FrontcraftFeatureIfc, Raid, Character, Rank, Class, Race, SRPriority, Spec, Signup } from "../src/backend/Types/Types";
import { SpecT } from "../src/backend/Types/PlayerSpecs"; import { SpecT } from "../src/backend/Types/PlayerSpecs";
@@ -107,7 +107,7 @@ describe('Frontcraft', () => {
client : RPCSocket & FrontcraftIfc, client : RPCSocket & FrontcraftIfc,
adminClient : RPCSocket & FrontcraftFeatureIfc, adminClient : RPCSocket & FrontcraftFeatureIfc,
raids: Raid[] = [], raids: Raid[] = [],
users : {[username in string] : { account: User, character: Character, auth: Auth, item?:string }} = {} users : {[username in string] : { account: User, character: Character, auth: Auth, signup?:Signup, item?:string }} = {}
const createAccount = (user: User) => { const createAccount = (user: User) => {
return client.UserManager.createUser(user) return client.UserManager.createUser(user)
@@ -217,9 +217,12 @@ describe('Frontcraft', () => {
adminClient.signup.sign(user.auth.token.value, user.character, raids[0], false).catch(done) adminClient.signup.sign(user.auth.token.value, user.character, raids[0], false).catch(done)
)).then(x => { )).then(x => {
adminClient.signup.getSignups(raids[0]).then(s => { adminClient.signup.getSignups(raids[0]).then(s => {
if(s.length == testAccounts.length) if(s.length == testAccounts.length){
s.forEach(sign => {
users[sign.username].signup = sign
})
done() done()
else{ }else{
done("Unexpected number of signups: "+s.length) done("Unexpected number of signups: "+s.length)
} }
}) })
@@ -467,7 +470,7 @@ describe('Frontcraft', () => {
const itemname = T1[0]//T1[Math.floor(T1.length*Math.random())] const itemname = T1[0]//T1[Math.floor(T1.length*Math.random())]
const modifier = await client.ItemManager.calculatePriorities(itemname, user.character) const modifier = await client.ItemManager.calculatePriorities(itemname, user.character)
const token = await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname) const token = await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!)
users[user.account.username].item = itemname users[user.account.username].item = itemname
if(!token) return false if(!token) return false
@@ -480,7 +483,7 @@ describe('Frontcraft', () => {
it('not buy token without currency', (done)=>{ it('not buy token without currency', (done)=>{
const user = Object.values(users)[0] const user = Object.values(users)[0]
const itemname = T1[0] const itemname = T1[0]
client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname).then(token => { client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!).then(token => {
if(!token) if(!token)
done() done()
else { else {
@@ -497,12 +500,18 @@ describe('Frontcraft', () => {
const item = await client.ItemManager.getItem(itemname) const item = await client.ItemManager.getItem(itemname)
const before = await client.ItemManager.getToken(user.character, item) const before = await client.ItemManager.getToken(user.character, item)
if(!before || before.level !== 7) return if(!before || before.level !== 7) {
console.log("expected level to be 7", before);
return
}
await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname) await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!)
const after = await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname) const after = await client.ItemManager.buyToken(user.auth.token.value, user.character.charactername, itemname, user.signup!)
if(!after || after.level !== 9) return if(!after || after.level !== 9) {
console.log("expected level to be 9", after);
return
}
done() done()
}) })
}) })
@@ -510,11 +519,12 @@ describe('Frontcraft', () => {
it('should buy more tokens', (done) => { it('should buy more tokens', (done) => {
Promise.all(Object.values(users).map(async (user) => { Promise.all(Object.values(users).map(async (user) => {
await adminClient.softreserveCurrency.incrementCurrency(user.account, 1) await adminClient.softreserveCurrency.incrementCurrency(user.account, 1)
await client.ItemManager return await client.ItemManager
.buyToken( .buyToken(
user.auth.token.value, user.auth.token.value,
user.character.charactername, user.character.charactername,
T1[Math.floor(T1.length*Math.random())] T1[Math.floor(T1.length*Math.random())],
user.signup!
) )
})).then(_ => { })).then(_ => {
done() done()