You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RaidManager.ts 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. import { Inject, Injectable } from "../../Injector/ServiceDecorator";
  2. import { RaidManagerIfc, RaidManagerFeatureIfc } from "./RPCInterface";
  3. import { FrontworkComponent } from "../../Types/FrontworkComponent";
  4. import { TableDefiniton, Signup, Raid, Character, RaidData, Spec, SRToken, Item, User } from "../../Types/Types";
  5. import { IAdmin } from "../../Admin/Interface";
  6. import { IRaidManager } from "./Interface";
  7. import { IUserManager } from "../User/Interface";
  8. import { ICharacterManager } from "../Character/Interface";
  9. import { _Tiers } from "../../Types/Items";
  10. import { IItemManager } from "../Item/Interface";
  11. import { ItemManager } from "../Item/ItemManager";
  12. import { IPubSub } from "../PubSub/Interface";
  13. @Injectable(IRaidManager)
  14. export class RaidManager
  15. implements FrontworkComponent<RaidManagerIfc, RaidManagerFeatureIfc>, IRaidManager {
  16. name = "RaidManager" as "RaidManager";
  17. @Inject(IAdmin)
  18. private admin: IAdmin
  19. @Inject(IUserManager)
  20. private userManager: IUserManager
  21. @Inject(ICharacterManager)
  22. private characterManager: ICharacterManager
  23. @Inject(ItemManager)
  24. private itemManager: IItemManager
  25. @Inject(IPubSub)
  26. private pubsub: IPubSub<any>
  27. exportRPCs = () => [
  28. this.getRaids,
  29. this.getRaidData,
  30. this.getPastRaids,
  31. this.getArchiveRaid
  32. ]
  33. exportRPCFeatures() {
  34. return [{
  35. name: 'manageRaid' as 'manageRaid',
  36. exportRPCs: () => [
  37. this.createRaid,
  38. this.addSignup,
  39. this.removeSignup,
  40. this.archiveRaid,
  41. this.setBenched,
  42. this.startRaid,
  43. this.adminUnsign
  44. ]
  45. }, {
  46. name: 'signup' as 'signup',
  47. exportRPCs: () => [
  48. this.getSignups,
  49. this.sign,
  50. this.unsign
  51. ]
  52. }]
  53. }
  54. getTableDefinitions(): TableDefiniton[] {
  55. return [
  56. {
  57. name: 'raids',
  58. tableBuilder: (table) => {
  59. table.increments('id').primary()
  60. table.dateTime('start').notNullable()
  61. table.string('description').notNullable()
  62. table.string('title').notNullable()
  63. table.integer('size').defaultTo(40)
  64. table.string('tier').defaultTo('null')
  65. }
  66. }, {
  67. name: 'archive',
  68. tableBuilder: (table) => {
  69. table.integer('id').primary()
  70. table.json('raiddata').notNullable()
  71. }
  72. }, {
  73. name: 'signups',
  74. tableBuilder: (table) => {
  75. table.increments('id').primary()
  76. table.unique(['raidid', 'characterid'])
  77. table.integer('raidid')
  78. table.foreign('raidid').references('id').inTable('raids').onDelete('CASCADE')
  79. table.integer('characterid')
  80. table.foreign('characterid').references('id').inTable('characters').onDelete('CASCADE')
  81. table.boolean('benched').defaultTo('false')
  82. table.boolean('late')
  83. }
  84. }
  85. ]
  86. }
  87. notifyRaid = async (raid:Raid | {id:number}) => {
  88. const data = await this.getRaidData(<Raid>raid)
  89. this.pubsub.publish(""+raid.id, data)
  90. await this.notifyRaids()
  91. }
  92. notifyRaids = async () => {
  93. this.pubsub.publish('raids', undefined)
  94. }
  95. createRaid = async (raid: Raid): Promise<Raid> => {
  96. const ids: number[] = await this.admin
  97. .knex('raids')
  98. .insert(raid)
  99. await this.notifyRaid({ id: ids[0] })
  100. return await this.admin.knex('raids').where({ id: ids[0] }).first()
  101. }
  102. addSignup = async (signup: Signup) => {
  103. const ids: number[] = await this.admin
  104. .knex('signups')
  105. .insert(signup)
  106. return await this.admin.knex('signups').where({ id: ids[0] }).first()
  107. }
  108. removeSignup = async (signup: Signup) => await this.admin
  109. .knex('signups')
  110. .where({
  111. raid_id: signup.raidid,
  112. character_id: signup.characterid
  113. })
  114. .del()
  115. getRaids = async (): Promise<Raid[]> => {
  116. const subQuery = this.admin
  117. .knex('signups')
  118. .count('*')
  119. .where({
  120. raidid: this.admin.knex.ref('raids.id'),
  121. benched: false,
  122. late: false
  123. })
  124. .as('signupcount')
  125. return await this.admin.knex('raids')
  126. .select('*', subQuery)
  127. .orderBy('start', 'asc')
  128. }
  129. startRaid = async (raid: Raid): Promise<RaidData> => {
  130. const archived = await this.archiveRaid(raid)
  131. const giveCurrency = async (b: Character) => {
  132. const usr = await this.characterManager.getUserOfCharacter(b)
  133. await this.userManager.incrementCurrency(usr, raid.tier, 1)
  134. }
  135. await Promise.all([
  136. ...archived.participants.bench.map(giveCurrency),
  137. ...Object.values(archived.participants).flat().flatMap((b:Signup & Character & Spec) => giveCurrency(b))
  138. ])
  139. await this.notifyRaids()
  140. return archived
  141. }
  142. archiveRaid = async (raid: Raid): Promise<RaidData> => {
  143. const raidData = await this.getRaidData(raid)
  144. //const tx = await this.admin.knex.transaction()
  145. await this.admin.knex('archive')
  146. //.transacting(tx)
  147. .insert({
  148. id: raidData.id,
  149. raiddata: JSON.stringify(raidData)
  150. })
  151. await Promise.all(
  152. Object.values(raidData.participants).flat().flatMap((p: (Signup & Character & Spec)) =>
  153. this.admin
  154. .knex(raid.tier + 'tokens')
  155. //.transacting(tx)
  156. .where({
  157. characterid: p.characterid,
  158. signupid: null
  159. })
  160. .del()
  161. ))
  162. await this.admin.knex('raids')
  163. //.transacting(tx)
  164. .where('id', '=', raid.id)
  165. .del()
  166. //await tx.commit()
  167. const row = await this.admin.knex('archive')
  168. .select('*')
  169. .where({
  170. id: raidData.id,
  171. })
  172. .first()
  173. return JSON.parse(row.raiddata)
  174. }
  175. getArchiveRaid = async (id: number): Promise<RaidData> => {
  176. const data = await this.admin.knex('archive').select('raiddata').where({
  177. id: id
  178. }).first()
  179. return JSON.parse(data.raiddata)
  180. }
  181. getPastRaids = async (limit: number): Promise<RaidData[]> => {
  182. const raids = await this.admin.knex('archive')
  183. .select('*')
  184. .orderBy('id', 'desc')
  185. .limit(limit)
  186. return raids.map(raid => JSON.parse(raid.raiddata))
  187. }
  188. getRaidData = async (raid: Raid): Promise<RaidData> => {
  189. const raiddata = {
  190. participants: {
  191. Druid: <(Signup & Character & Spec)[]>[],
  192. Hunter: <(Signup & Character & Spec)[]>[],
  193. Mage: <(Signup & Character & Spec)[]>[],
  194. Paladin: <(Signup & Character & Spec)[]>[],
  195. Priest: <(Signup & Character & Spec)[]>[],
  196. Rogue: <(Signup & Character & Spec)[]>[],
  197. Shaman: <(Signup & Character & Spec)[]>[],
  198. Warlock: <(Signup & Character & Spec)[]>[],
  199. Warrior: <(Signup & Character & Spec)[]>[],
  200. late: <(Signup & Character & Spec)[]>[],
  201. bench: <(Signup & Character & Spec)[]>[],
  202. },
  203. tokens: {},
  204. healers: <(Signup & Character & Spec)[]>[],
  205. tanks: <(Signup & Character & Spec)[]>[]
  206. }
  207. //const tx = await this.admin.knex.transaction()
  208. const subQuery = this.admin
  209. .knex('signups')
  210. .count('*')
  211. .where({
  212. raidid: this.admin.knex.ref('raids.id'),
  213. benched: false,
  214. late: false
  215. })
  216. .as('signupcount')
  217. const raidInDb: Raid = await this.admin.knex('raids')
  218. .select('*', subQuery)
  219. //.transacting(tx)
  220. .where('id', '=', raid.id)
  221. .first()
  222. const characterData: (Signup & Character & Spec)[] = await this.admin
  223. .knex('signups as s')
  224. //.transacting(tx)
  225. .select('s.id as id', 'charactername', 'class', 'specid', 'specname', 'race', 'userid', 'benched', 'late', 'raidid', 'characterid', 'specid')
  226. .join('raids as r', 's.raidid', '=', 'r.id')
  227. .join('characters as c', 's.characterid', '=', 'c.id')
  228. .join('users as u', 'c.userid', '=', 'u.id')
  229. .join('specs as sp', 'specid', '=', 'sp.id')
  230. .where('r.id', '=', raid.id)
  231. characterData.forEach(data => {
  232. if (data.benched) {
  233. raiddata.participants.bench.push(data)
  234. return
  235. }
  236. if (data.late) {
  237. raiddata.participants.late.push(data)
  238. return
  239. }
  240. raiddata.participants[data.class].push(data)
  241. })
  242. const tokenData: (Character & SRToken & Item)[] = await this.admin
  243. .knex('signups as s')
  244. //.transacting(tx)
  245. .select('*', 's.id as id')
  246. .join('raids as r', 's.raidid', '=', 'r.id')
  247. .join('characters as c', 's.characterid', '=', 'c.id')
  248. .join(raidInDb.tier + 'tokens as t', 't.characterid', '=', 'c.id')
  249. .join('items as i', 'i.itemname', '=', 't.itemname')
  250. .where({
  251. 'r.id': raid.id,
  252. })
  253. .andWhere(function () {
  254. this.whereNotNull('t.signupid')
  255. })
  256. //await tx.commit()
  257. tokenData.forEach(data => {
  258. if (!raiddata.tokens[data.itemname])
  259. raiddata.tokens[data.itemname] = []
  260. raiddata.tokens[data.itemname].push(data)
  261. })
  262. raiddata.tanks = Object.values(raiddata.participants).flatMap(
  263. (tanks: any[]) => tanks.filter((p: any) =>
  264. !p.benched
  265. && !p.late
  266. && (p.specname === "Protection"
  267. || p.specname === "Feral (Tank)"))
  268. )
  269. raiddata.healers = Object.values(raiddata.participants).flatMap(
  270. (healers: any[]) => healers.filter((p: any) =>
  271. !p.benched
  272. && !p.late
  273. && (p.specname === "Holy"
  274. || p.specname === "Discipline"
  275. || p.specname === "Restoration"))
  276. )
  277. return {
  278. ...raidInDb,
  279. ...raiddata
  280. }
  281. }
  282. getSignups = async (raid: Raid): Promise<(Signup & Character & Spec & User)[]> => await this.admin
  283. .knex('signups as si')
  284. .join('characters as c', 'c.id', '=', 'characterid')
  285. .join('specs as s', 's.id', '=', 'specid')
  286. .join('users as u', 'u.id', '=', 'userid')
  287. .select('*', 'si.id as id')
  288. .where('raidid', '=', raid.id!)
  289. sign = async (usertoken: string, character: Character, raid: Raid, late: boolean) => {
  290. const maybeUserRecord = this.userManager.getUserRecordByToken(usertoken)
  291. if (!maybeUserRecord || maybeUserRecord.user.id != character.userid) {
  292. throw new Error("Bad Usertoken")
  293. }
  294. //const tx = await this.admin.knex.transaction()
  295. const exists = await this.admin
  296. .knex('signups')
  297. //.transacting(tx)
  298. .select('*')
  299. .where({
  300. raidid: raid.id!,
  301. characterid: character.id!,
  302. })
  303. .first()
  304. if (!exists) {
  305. await this.admin
  306. .knex('signups')
  307. //.transacting(tx)
  308. .insert({
  309. raidid: raid.id!,
  310. characterid: character.id!,
  311. late: late,
  312. benched: false,
  313. })
  314. } else {
  315. await this.admin
  316. .knex('signups')
  317. //.transacting(tx)
  318. .where({
  319. id: exists.id
  320. })
  321. .update({
  322. raidid: raid.id!,
  323. characterid: character.id!,
  324. late: late,
  325. benched: false,
  326. })
  327. }
  328. //await tx.commit()
  329. await this.notifyRaid(raid)
  330. return await this.admin
  331. .knex('signups')
  332. .select('*')
  333. .where({
  334. raidid: raid.id!,
  335. characterid: character.id!,
  336. })
  337. .first()
  338. }
  339. unsign = async (usertoken: string, character: Character, raid: Raid) => {
  340. const maybeUserRecord = this.userManager.getUserRecordByToken(usertoken)
  341. if (!maybeUserRecord || maybeUserRecord.user.id != character.userid) {
  342. throw new Error("Bad Usertoken")
  343. }
  344. return await this.adminUnsign(character, raid)
  345. }
  346. adminUnsign = async (character: Character, raid: Raid) => {
  347. const user = await this.characterManager.getUserOfCharacter(character)
  348. const signup = await this.admin.knex('signups as si')
  349. .where({
  350. "si.raidid": raid.id!,
  351. "si.characterid": character.id!,
  352. }).first()
  353. const tokens = await this.itemManager.getTokens(character, [raid.tier], true)
  354. //check if token has to be deleted
  355. if (tokens) {
  356. Promise.all(
  357. tokens.map(async token => {
  358. await this.userManager.incrementCurrency(user, raid.tier, 1)
  359. const prio = await this.itemManager.calculatePriorities(token.itemname, character)
  360. if (token.level <= prio + 1) {
  361. await this.admin
  362. .knex(raid.tier + 'tokens')
  363. .where({
  364. characterid: character.id,
  365. itemname: token.itemname
  366. }).del()
  367. } else {
  368. await this.admin
  369. .knex(raid.tier + 'tokens')
  370. .where({
  371. characterid: character.id,
  372. itemname: token.itemname
  373. }).update({
  374. signupid: null,
  375. level: token.level - 1
  376. })
  377. }
  378. })
  379. )
  380. }
  381. await this.admin.knex('signups')
  382. .where({
  383. raidid: raid.id!,
  384. characterid: character.id!,
  385. })
  386. .del()
  387. await this.notifyRaid(raid)
  388. }
  389. setBenched = async (signup: Signup): Promise<void> => {
  390. await this.admin.knex('signups')
  391. .where({
  392. raidid: signup.raidid,
  393. characterid: signup.characterid
  394. })
  395. .update(signup)
  396. await this.notifyRaid({ id: signup.raidid })
  397. }
  398. }