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.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  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. })
  137. .as('signupcount')
  138. const countBenches = this.admin
  139. .knex('signups')
  140. .count('*')
  141. .where({
  142. raidid: this.admin.knex.ref('raids.id'),
  143. benched: true,
  144. late: false
  145. })
  146. .as('benchcount')
  147. return await this.admin.knex('raids')
  148. .select('*', countSignups, countBenches)
  149. .orderBy('start', 'asc')
  150. }
  151. startRaid = async (raid: Raid): Promise<RaidData> => {
  152. const data = await this.getRaidData(raid)
  153. data.participants.absent.map(p => this.itemManager.decayTokensOfCharacter(raid.tier, p, 1))
  154. await this.itemManager.decayTokens(data.tier)
  155. const archived = await this.archiveRaid(raid)
  156. const giveCurrency = async (b: Character) => {
  157. const usr = await this.characterManager.getUserOfCharacter(b)
  158. await this.userManager.incrementCurrency(usr, raid.tier, 1)
  159. }
  160. await Promise.all([
  161. ...archived.participants.bench.map(giveCurrency),
  162. ...Object.values(archived.participants).flat().map((b: Signup & Character & Spec) => giveCurrency(b))
  163. ])
  164. await this.notifyRaids()
  165. return archived
  166. }
  167. archiveRaid = async (raid: Raid): Promise<RaidData> => {
  168. const raidData = await this.getRaidData(raid)
  169. const tx = await this.admin.knex.transaction()
  170. await this.admin.knex('archive')
  171. .transacting(tx)
  172. .insert({
  173. id: raidData.id,
  174. raiddata: JSON.stringify(raidData)
  175. })
  176. await Promise.all(
  177. Object.values(raidData.participants).flat().flatMap((p: (Signup & Character & Spec)) =>
  178. this.admin
  179. .knex(raid.tier + 'tokens')
  180. .transacting(tx)
  181. .where({
  182. characterid: p.characterid,
  183. signupid: null
  184. })
  185. .del()
  186. ))
  187. await this.admin.knex('raids')
  188. .transacting(tx)
  189. .where('id', '=', raid.id)
  190. .del()
  191. await tx.commit()
  192. const row = await this.admin.knex('archive')
  193. .select('*')
  194. .where({
  195. id: raidData.id,
  196. })
  197. .first()
  198. try {
  199. return JSON.parse(row.raiddata)
  200. } catch (e) {
  201. getLogger('RaidManager#archiveRaid').error(row.id + " could not get parsed")
  202. return {} as RaidData
  203. }
  204. }
  205. getArchiveRaid = async (id: number): Promise<RaidData> => {
  206. const data = await this.admin
  207. .knex('archive')
  208. .select('raiddata')
  209. .where({
  210. id: id
  211. })
  212. .first()
  213. try {
  214. return JSON.parse(data.raiddata)
  215. } catch (e) {
  216. getLogger('RaidManager#getArchiveRaid').error(id + " could not get parsed")
  217. return {} as RaidData
  218. }
  219. }
  220. getPastRaids = async (limit: number): Promise<RaidData[]> => {
  221. const raids = await this.admin.knex('archive')
  222. .select('*')
  223. .orderBy('id', 'desc')
  224. .limit(limit)
  225. return raids.map(raid => JSON.parse(raid.raiddata))
  226. }
  227. getRaidData = async (raid: Raid): Promise<RaidData> => {
  228. const raiddata = {
  229. participants: {
  230. Druid: <(Signup & Character & Spec)[]>[],
  231. Hunter: <(Signup & Character & Spec)[]>[],
  232. Mage: <(Signup & Character & Spec)[]>[],
  233. Paladin: <(Signup & Character & Spec)[]>[],
  234. Priest: <(Signup & Character & Spec)[]>[],
  235. Rogue: <(Signup & Character & Spec)[]>[],
  236. Shaman: <(Signup & Character & Spec)[]>[],
  237. Warlock: <(Signup & Character & Spec)[]>[],
  238. Warrior: <(Signup & Character & Spec)[]>[],
  239. late: <(Signup & Character & Spec)[]>[],
  240. bench: <(Signup & Character & Spec)[]>[],
  241. absent: <(Signup & Character & Spec)[]>[]
  242. },
  243. tokens: {},
  244. healers: <(Signup & Character & Spec)[]>[],
  245. tanks: <(Signup & Character & Spec)[]>[]
  246. }
  247. const subQuery = this.admin
  248. .knex('signups')
  249. .count('*')
  250. .where({
  251. raidid: this.admin.knex.ref('raids.id'),
  252. benched: false,
  253. late: false,
  254. absent: false
  255. })
  256. .as('signupcount')
  257. const raidInDb: Raid = await this.admin.knex('raids')
  258. .select('*', subQuery)
  259. .where('id', '=', raid.id)
  260. .first()
  261. const characterData: (Signup & Character & Spec)[] = await this.admin
  262. .knex('signups as s')
  263. .select('s.id as id', 'charactername', 'rank', 'class', 'specid', 'specname', 'race', 'userid', 'benched', 'late', 'raidid', 'characterid', 'specid', 'memo', 'timestamp', 'absent')
  264. .join('raids as r', 's.raidid', '=', 'r.id')
  265. .join('characters as c', 's.characterid', '=', 'c.id')
  266. .join('users as u', 'c.userid', '=', 'u.id')
  267. .join('specs as sp', 'specid', '=', 'sp.id')
  268. .where('r.id', '=', raid.id)
  269. characterData.forEach(data => {
  270. if(data.absent){
  271. raiddata.participants.absent.push(data)
  272. return
  273. }
  274. if (data.benched) {
  275. raiddata.participants.bench.push(data)
  276. return
  277. }
  278. if (data.late) {
  279. raiddata.participants.late.push(data)
  280. return
  281. }
  282. raiddata.participants[data.class].push(data)
  283. })
  284. const tokenData: (Character & SRToken & Item)[] = await this.admin
  285. .knex('signups as s')
  286. .select('*', 's.id as id')
  287. .join('raids as r', 's.raidid', '=', 'r.id')
  288. .join('characters as c', 's.characterid', '=', 'c.id')
  289. .join('users as u', 'u.id', '=', 'c.userid')
  290. .join(raidInDb.tier + 'tokens as t', 't.characterid', '=', 'c.id')
  291. .join('items as i', 'i.itemname', '=', 't.itemname')
  292. .where({
  293. 'r.id': raid.id,
  294. })
  295. .andWhere(function () {
  296. //this.whereNotIn('u.rank', ['Trial', 'Guest'])
  297. this.whereNotNull('t.signupid')
  298. })
  299. tokenData.forEach(data => {
  300. if (!raiddata.tokens[data.itemname])
  301. raiddata.tokens[data.itemname] = []
  302. raiddata.tokens[data.itemname].push(data)
  303. })
  304. raiddata.tanks = Object.values(raiddata.participants).flatMap(
  305. (tanks: any[]) => tanks.filter((p: any) =>
  306. !p.benched
  307. && !p.absent
  308. && !p.late
  309. && (p.specname === "Protection"
  310. || p.specname === "Feral (Tank)"))
  311. )
  312. raiddata.healers = Object.values(raiddata.participants).flatMap(
  313. (healers: any[]) => healers.filter((p: any) =>
  314. !p.benched
  315. && !p.absent
  316. && !p.late
  317. && (p.specname === "Holy"
  318. || p.specname === "Discipline"
  319. || p.specname === "Restoration"))
  320. )
  321. return {
  322. ...raidInDb,
  323. ...raiddata
  324. }
  325. }
  326. getSignups = async (raid: Raid): Promise<(Signup & Character & Spec & User)[]> => await this.admin
  327. .knex('signups as si')
  328. .join('characters as c', 'c.id', '=', 'characterid')
  329. .join('specs as s', 's.id', '=', 'specid')
  330. .join('users as u', 'u.id', '=', 'userid')
  331. .select('*', 'si.id as id')
  332. .where('raidid', '=', raid.id!)
  333. sign = async (usertoken: string, character: Character, raid: Raid, late: boolean, absent:boolean, memo?: string) => {
  334. const maybeUserRecord = this.userManager.getUserRecordByToken(usertoken)
  335. if (!maybeUserRecord || maybeUserRecord.user.id != character.userid) {
  336. throw new Error("Bad Usertoken")
  337. }
  338. if (memo) memo = memo.substring(0, MAX_MEMO_LENGTH)
  339. const exists = await this.admin
  340. .knex('signups')
  341. .select('*')
  342. .where({
  343. raidid: raid.id!,
  344. characterid: character.id!,
  345. })
  346. .first()
  347. if (!exists) {
  348. await this.admin
  349. .knex('signups')
  350. .insert({
  351. raidid: raid.id!,
  352. characterid: character.id!,
  353. late: late,
  354. absent: absent,
  355. benched: false,
  356. memo: memo
  357. })
  358. } else {
  359. await this.admin
  360. .knex('signups')
  361. .where({
  362. id: exists.id
  363. })
  364. .update({
  365. raidid: raid.id!,
  366. characterid: character.id!,
  367. late: late,
  368. absent: absent,
  369. benched: false,
  370. memo: memo
  371. })
  372. }
  373. await this.notifyRaid(raid)
  374. return await this.admin
  375. .knex('signups')
  376. .select('*')
  377. .where({
  378. raidid: raid.id!,
  379. characterid: character.id!,
  380. })
  381. .first()
  382. }
  383. cancelRaid = async (raid: Raid) => {
  384. const data = await this.getRaidData(raid)
  385. const participants = Object.values(data.participants).flat()
  386. await Promise.all(
  387. participants.map(async p => await this.adminUnsign({...p, id: p.characterid}, raid))
  388. )
  389. await this.admin.knex('raids').where({id: raid.id}).del()
  390. await this.notifyRaids()
  391. }
  392. unsign = async (usertoken: string, character: Character, raid: Raid) => {
  393. const maybeUserRecord = this.userManager.getUserRecordByToken(usertoken)
  394. if (!maybeUserRecord || maybeUserRecord.user.id != character.userid) {
  395. throw new Error("Bad Usertoken")
  396. }
  397. return await this.adminUnsign(character, raid)
  398. }
  399. adminUnsign = async (character: Character, raid: Raid) => {
  400. const user = await this.characterManager.getUserOfCharacter(character)
  401. const tokens = await this.itemManager.getTokens(character, [raid.tier], true)
  402. //check if token has to be deleted
  403. if (tokens) {
  404. await Promise.all(
  405. tokens.map(async token => {
  406. await this.userManager.incrementCurrency(user, raid.tier, 1)
  407. const prio = await this.itemManager.calculatePriorities(token.itemname, character)
  408. if (token.level <= prio + 2) {
  409. await this.admin
  410. .knex(raid.tier + 'tokens')
  411. .where({
  412. characterid: character.id,
  413. itemname: token.itemname
  414. }).del()
  415. } else {
  416. await this.admin
  417. .knex(raid.tier + 'tokens')
  418. .where({
  419. characterid: character.id,
  420. itemname: token.itemname
  421. }).update({
  422. signupid: null,
  423. level: token.level - 2
  424. })
  425. }
  426. })
  427. )
  428. }
  429. await this.admin.knex('signups')
  430. .where({
  431. raidid: raid.id!,
  432. characterid: character.id!,
  433. })
  434. .del()
  435. await this.notifyRaid(raid)
  436. }
  437. updateSignup = async (signup: Signup): Promise<void> => {
  438. await this.admin.knex('signups')
  439. .where({
  440. raidid: signup.raidid,
  441. characterid: signup.characterid
  442. })
  443. .update(signup)
  444. await this.notifyRaid({ id: signup.raidid })
  445. }
  446. }