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 17KB

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