-
Notifications
You must be signed in to change notification settings - Fork 0
/
TODO
771 lines (755 loc) · 71.1 KB
/
TODO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
// using TODO+ extension for vscode
TODO:
BUGS FROM 2020/01/04 Grand BR:
Setup:
!obr addteams Arbusion Loveleft | SHIROHAMYMOMMY VIVACE | Yaong "Monk Gyatso" | heyronii raikouhou | jordanlr7 suffix | Chal Aus | Astellis "Milo Milkshake" | pewdekz viraelia
https://osu.ppy.sh/community/matches/57483295
!obr addlobby 57483295
TODO:
☐ Create a test for the osu scanner @created(20-01-05 01:18)
☐ Ensure results are not being generated for the entire match history @created(20-01-05 01:20)
☐ Feed the scanner API results one at a time @created(20-01-05 01:18)
{
results1: {
match1: {
endTime: null
}
},
results2: {
match1: {
endTime: 12345678qq
},
match2: {
endTime: null
}
}
}
Bugs:
✔ Duplicate leaderboard gets posted as soon as the 2nd match starts @created(20-01-04 21:26) @done(20-01-19 00:45)
Can lazily fix this by just checking if the same VM results were already delivered
✔ And it's reposting the results from every match @created(20-01-04 21:28) @done(20-01-19 00:45)
☐ Duplicate leaderboard gets posted when each match begins @created(20-01-04 21:26)
☐ fix heart icon
☐ scuffed team numbers (adding teams after game starts breaks team numbers)
☐ don't show teams on leaderboards with a score of 0
☐ add !removeteam command @started(20-01-19 00:45)
☐ URGENT: Fix if a team does not submit any scores, they win the match (and possibly if submits but scores 0) @created(20-02-15 14:43)
Improvement ideas:
☐ Post a message when a match begins @created(20-01-04 21:42)
maybe? -> prevMatch.endTime != null && latestMatch.endTime == null && !prevMatch.aborted
✔ Sort leaderboard by current match scores, not by total scored @created(20-01-04 22:02) @done(20-02-01 16:14)
☐ Show the rank-ordered leaderboard when the game ends @created(20-01-04 22:05)
☐ Increase API limit to improve speed of results being shown @created(20-01-04 22:24)
✘ Include mods with map string @created(20-01-04 22:15) @cancelled(20-01-04 22:54)
Memory Issues:
✔ Completely rework the nested data structures @created(19-12-28 22:05) @done(20-01-01 15:01)
- See: LobbyRepository.findMultiplayerEntitiesForLobby
- Get one big data blog from a simple query - this should be enough to get all the data we need
- Then use some searches to pick out the data needed
- Do this instead of nesting the data like lobby.matches.lobby.matches
G1: 600 MB
G2: 850 MB
G3: 1600 MB
pg -> parseBigInt -> emit
✔ Fix OOM - FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory @created(19-12-28 19:33) @done(20-01-01 15:01)
☐ Fix "Failed to end game..." @created(19-11-16 14:25)
☐ Fix mp results not being delivered if we add a 2nd game with the same properties as the first (same lobby) @created(19-11-16 14:25)
☐ Add - Load game watcher data from database when we restart the app @created(19-11-16 14:25)
Common Build/Runtime Errors:
- Error: Chromium revision is not downloaded. Run "npm install" or "yarn install"
> npm install puppeteer --unsafe-perm=true --allow-root
Leaderboard:
☐ Deliver an initial leaderboard after !startgame is used @created(20-01-01 15:02)
✔ Create message reportable if match was aborted @created(19-11-03 23:03) @done(19-11-12 18:19)
✔ Add TeamEliminated game event @created(19-11-02 23:30) @done(19-11-03 23:04)
✔ Fix bug - Two leaderboards are being incorrectly generated for each match for the Thaibuy vs Bubbleman match - 54567825 @created(19-11-02 00:42) @done(19-11-02 16:43)
✔ Fix bug - Previous position and "changed" should be undefined for all teams for the first leaderboard generated for a game @created(19-11-02 00:36) @done(19-11-02 16:43)
Because there is no previous position, since this is the first match.
✔ Do not include any reportables after the final leaderboard (the first-occurring leaderboard where only one team remains alive) @created(19-11-02 16:51) @done(19-11-02 17:15)
☐ Do not add to team's total score if GameSettings.countFailedScores == false and if the player's score is a failed score @created(19-11-01 16:35)
☐ convert Leaderboard to class + create abstract Leaderboard class + rename leaderboard to BattleRoyaleLeaderboard @created(19-10-27 20:11)
☐ Build a reportables deliverer to deliver the leaderboard, events and messages to Discord @created(19-11-01 18:05)
☐ Write tests @created(19-11-01 18:07)
☐ Filter out already-reported reportables before delivering to Discord @created(19-11-01 18:07)
☐ Write tests @created(19-11-01 18:07)
☐ Handle game winner @created(19-11-01 22:46)
✔ Write game event for game winner @created(19-11-01 22:46) @done(19-11-02 23:31)
✔ Handle team winning a game - call Game.endGame, etc. @created(19-11-01 22:46) @done(19-11-03 15:12)
☐ Deliver message to GameMessageTarget (using the EventDispatcher) when endGame is called @created(19-11-03 15:12)
✘ Merge CalculatedTeamScore with LeaderboardBuilder.TeamScoresMap @created(19-10-30 00:53) @cancelled(19-11-01 10:59)
✔ Add method in LeaderboardBuilder named "buildLeaderboards" which calls buildLeaderboard to build a leaderboard for many reportables @created(19-10-30 13:49) @done(19-11-01 17:03)
✔ Rename buildLeaderboard to buildLatestLeaderboard @created(19-10-30 13:50) @done(19-11-01 12:21)
✔ Include the leaderboard reportables in the output of the MultiplayerResultsReporter.getItemsToBeReported method @created(19-10-30 15:11) @done(19-11-01 12:21)
✔ Position should be locked if the team was eliminated @created(19-10-27 20:11) @done(19-10-31 22:20)
✔ Do not add to team's total score if the team *currently* has 0 lives remaining (i.e. before *this* leaderboard calculation) @done(19-10-30 00:48)
✔ Change position from index 1-based to 0-based @created(19-10-29 19:40) @done(19-10-30 00:44)
✔ Fix `Error: Multiplayer 54078930 is not currently being watched` when using `!endgame` command @created(19-08-30 07:43) @done(19-08-30 23:29)
✔ Fix `!obr removelobby` command not unwatching the lobby in the osu lobby scanner @created(19-08-30 23:29) @done(19-08-30 23:52)
☐ Remove all the comments starting with "GL:" from when we implemented the GameLobby entity @created(19-08-30 07:48)
✔ Completely rework adding/removing lobbies from games @created(19-08-28 20:27) @started(19-08-28 20:20) @done(19-08-30 07:41) @lasted(1d11h21m46s)
Storing add/remove values on a many-to-many GamesLobby table: https://github.com/typeorm/typeorm/issues/1224
☐ Remove GameStatus.UNKNOWN
Using "UNKNOWN" feels lazy. If there's an error preventing a known-status being applied, we should just throw an error instead of resorting to an "UNKNOWN" status. Also having a game status as "UNKNOWN" means that status has to be handled in some way eventually, so why not just handle it that way to start with?
☐ Explore the osu! API - we need to know if submitting a map when you're in a multi will display the map in your "recent" played maps. This is mainly for MING MODE where we need to know - if it's displayed in your recent plays with an F (if failed in a multi). Does it only show F if you didn't recover in the multi, or will it show some letter (S,A,B,C,D) when you recover before the end of the map.
☐ Take a look at the new osu! API
✔ Use test Discord Commando database depending on env context @started(19-08-26 21:40) @done(19-08-27 08:11) @lasted(10h31m25s)
Terminology:
Virtual Lobby:
A group of lobbies added to a game
Virtual Match:
A superset of matches played across multiple lobbies added to a game, where each match has played the same beatmap the same nth number of times in the lobby.
Completed Virtual Match:
A virtual match containing a match for all lobbies added to a game (i.e. all lobbies have finished playing the same beatmap the same nth number of times).
Livestreaming:
☐ Setup vscode LiveShare for readonly access so stream viewers can jump around the code @created(19-10-06 21:51)
☐ Need some way to make .env files inaccessible before we can allow this @created(19-10-06 21:51)
Bugs:
Critical:
☐ Problem: Error "integer NaN" being thrown in SQL query during mp entity saving (probably during match insertions) @created(19-10-22 00:17)
Recreate: !creategame !addlobby 5.. !addlobby 4.. !startgame
☐ Problem: !startgame called without first adding teams, causing error: @created(19-10-17 23:51)
[2019-10-17T23:32:52.712] [ERROR] default - Method unexpected-error: buildVirtualMatchReportGroupsForGame MultiplayerResultsProcessor TypeError: Cannot read property 'teamOsuUsers' of null
at Function.calculateTeamScoresForVirtualMatch (C:\C\projects\OsuBrBot2\src\multiplayer\team-score-calculator.ts:80:35)
☐ Fix 1: Deny !startgame command if no teams added @created(19-10-17 23:56)
☐ Fix 2: Check for undefined teamsOsuUsers in TeamScoreCalculator (do this anyway) @created(19-10-17 23:56)
☐ We should also deny the !addteam command if a game has already started, but the code should be flexible enough to support future scenarios where teams can be added after a game has been started, and the results are re-calculated to include the new teams. @created(19-10-17 23:54)
Important:
☐ !creategame should change the target game of the requesting user to that game. @created(19-09-19 13:00)
☐ If a match from the osu API has the same start time and the same beatmap ID (in the same lobby), the system is treating this as the same map (but it should be like BM1#1 and BM1#2). @created(19-10-31 21:25)
In reality this should never be an issue, since maps cannot start at the same time in the same lobby. However, we should find a way to include the match end time in this comparison. If the end time is different, treat it as a different match.
Optimize:
☐ Lobby scanner should start scanning immediately when a lobby is added. Right now, it waits the interval time before doing the first scan. @created(19-09-19 13:13)
Security:
☐ The !addteam command could be abused if a user spams the command with a bunch of osu usernames over and over again, because the addteam process involves validating the osu usernames with the osu API, meaning this is an effective DoS attack. @created(19-09-07 17:42)
☐ We need some kind of rate limiter for the actions the users can do @created(19-09-07 17:43)
☐ To add a rate limiter, we need to record where in the code API requests are originating from, and if the user triggers some process which involves one of these API requests, we add that to the rate limiter. @created(19-09-07 17:43)
☐ The goal should be to share the API load between users, maybe using some kind of back-off approach, where actions triggered by the spamming user get slower and slower for each additional spam command sent. @created(19-09-07 17:48)
Functionality:
Game in Progress:
Things to Handle:
☐ All players of a team do not submit a score @created(19-09-02 13:36)
☐ Option 1: Team loses a life @created(19-09-02 13:37)
✘ Option 2: Team does not lose a life and we treat it as if they "skipped" this map @created(19-09-02 13:37) @cancelled(19-09-02 13:37)
Bad because it could be abused as a way to not lose lives.
☐ Option 3: Team loses a life and receives warning that if they fail to submit a score on the next map, they will be immediately eliminated. @created(19-09-02 13:38)
☐ Option 4: @created(19-09-02 13:39)
Osu Lobby Watcher / Osu API:
✔ Rebuild osu-lobby-watcher.ts using listener and EventEmitter @created(19-08-27 12:09) @done(19-09-24 22:29)
https://github.com/onury/tasktimer
https://blog.x5ff.xyz/blog/typescript-interval-iot/
https://github.com/sindresorhus/emittery
https://www.npmjs.com/package/async-eventemitter
☐ Persist osu watcher lobbies being watched after app exits @created(19-08-30 22:45)
☐ Load osu lobby watchers from database for all active games when the app starts @created(19-09-03 11:15)
☐ Add a routine for when a closed game is re-actived, it should load the watcher from the database into the osu lobby watcher @created(19-09-24 22:27)
☐ Deny adding a lobby and send a warning to the user if the maximum allowed number of lobbies being scanned is reached @created(19-09-04 06:54)
☐ We can accomplish this by adding a method to IOsuApiFetcher like isMaxJobsReached() - which would compare the current number of lobbies being scanned with Bottleneck.limiter.maxConcurrent @created(19-09-04 06:58)
☐ If a user adds the bot from source code, using their own osu API key, the limit should be kept at 60 max requests per 60 seconds. The Bottleneck limiter should be able to dynamically set this depending on the user's own account API limit. @created(19-09-04 06:58)
Error Messages:
☐ When adding a lobby before creating a game, the error message returned should replace "user ID X" with [at]DiscordUsername" instead (e.g. No games have been created by user ID 2. -> No games have been created by [at]CeilingWaffle
☐ Response<T> should be of type AddLobbyFailureReport and contain a discordUserId property
☐ When adding an invalid bancho mp id (e.g. 1234) the error message should be better. Right now it's: banchoMultiplayerId: "1234" is bad because: [object Object]
☐ If an error is thrown at any stage during the osu lobby scanner process, an error message should be delivered to the Discord game target channels, so that if something does go wrong, it doesn't silently fail @created(19-10-03 18:57)
Server:
☐ Ensure app does not crash, or if it does, restart it in a reliable way
Don't worry about uncaught exceptions like "connection closed" errors - just use foreverjs to restart the app in the event of failure.
☐ Handle internet disconnections by auto-reconnecting @created(19-09-02 10:01)
eg1: https://i.imgur.com/v4GDoRf.png
eg2: https://i.imgur.com/9A5uA5A.png
Social:
Notable osu people showing interest:
ThePooN, RyuK, Musty98
Testing:
Priorities:
- Coverage (maximize the number of situations tested + maximize "peace of mind")
- Readability (ability to see "at a glance" what a test is testing)
- Maintainability (how easy is it to modify a test if something like a method signature changes)
- Performance (minimize the time required to complete the tests)
☐ Test multiplayer-results-processor with postgres instead of sqlite - to test for potential DB-specific errors @created(19-10-03 19:43)
☐ Unit test EventDispatcher class @created(19-10-21 05:02)
☐ Write tests for creating and reading game-matches-reported @created(19-10-03 14:08)
☐ Write tests for starting game (and test behaviour of OsuLobbyScannerService) @created(19-09-29 23:46)
☐ Remove fake of IOsuLobbyScanner and instead somehow inject the ApiMultiplayer object into the return of FakeOsuApiFetcher.fetchMultiplayerResults() @created(19-09-23 16:12)
This is so we can include testing the real OsuLobbyScanner in the lobby-remove tests instead of copying code from the real implemention to the fake.
☐ Improve the Osu API Fetcher test (mock) class @created(19-09-14 23:27)
☐ Potential issue with the validation methods where they may incorrectly fail or succeed with the validation but the mocked class is just assuming they always succeed @created(19-09-14 23:27)
☐ One solution is just to unit test every line within the validation method, but if we add more lines in future we need to remember the unit test those aswell @created(19-09-14 23:28)
☐ Another solution is to mock the Nodesu API itself, instead of mocking the OsuAPIFetcher class @created(19-09-14 23:29)
☐ Write unit tests for ColorPicker class @created(19-09-11 13:45)
☐ Write tests for validating osu users (mock osu API?) @created(19-09-14 02:30)
☐ Assert that an invalid user generates a banchoOsuUserIdIsInvalidFailure from the TeamController (controller calling TeamService) @created(19-09-14 02:30)
☐ Write tests for adding users to teams @created(19-09-13 20:14)
☐ Assert that a TeamUser cannot exist consisting of the same Team and OsuUser (i.e. TeamUser should be unique) @created(19-09-13 20:14)
☐ Assert that multiple teams containing the same OsuUsers cannot exist @created(19-09-13 20:14)
☐ Assert that teams added with a group containing osu user ids listed in descending order are still added correctly (tests the alphaSort function) @created(19-09-13 20:16)
☐ Assert that a command containing duplicate username/user-id groups does not create multiple teams of the duplicate, but should still silently succeed, ignoring any duplicates @created(19-09-13 22:38)
☐ Assert that the color is unique for each GameTeam in a specific game @created(19-09-14 02:35)
☐ log4js - Write log to a separate file when running tests @created(19-09-08 09:52)
☐ log4js - Disable logging when running mocha tests in parallel, or log to a separate file for each test @created(19-09-08 09:52)
☐ Create test for testing the osu lobby scanner timer @created(19-09-06 10:07)
✘ Test that a watcher was disposed correctly while the timer is currently running @created(19-09-06 10:07) @cancelled(19-09-06 10:15)
Solved this by just checking if watcher is defined as the first action in the scan() method, and don't continue with the scan if it's undefined.
✔ Speed up test execution times @created(19-08-30 23:52) @done(19-09-08 09:53)
✔ Can we run each group of tests in parallel by using a separate sqlite database for each group? @created(19-08-30 23:52) @done(19-09-08 09:53)
✔ Using an in-memory sqlite database solved this. Tests went from 2 minute exeuction times down to 15 seconds @created(19-09-08 09:53) @done(19-09-08 09:54)
✔ Finish writing commented-out tests in lobby-create.test.ts @started(19-08-18 20:12) @done(19-08-18 21:25) @lasted(1h13m39s)
✔ Run tests individually @done(19-08-18 22:41)
✔ Write tests for asserting lobby status after lobby-removal @started(19-08-20 05:22) @done(19-08-20 05:44) @lasted(22m21s)
☐ Finish writing tests for asserting error messages received when sending remove-lobby requests
☐ Write test for: Create a new user and add that user to a ref of a game, then assert that the user is a ref of that game
☐ Write test for LobbyService: Add 2 lobbies to a game, then remove 2 lobbies, then assert that game.lobbies.length = 0 @created(19-08-28 13:51)
☐ Create test suite for updating a game @created(19-08-31 13:58)
☐ Test user permission allowed/denied update @created(19-08-31 13:58)
✔ Write more tests for testing ending games @done(19-08-27 02:54)
☐ We have an issue where some tests are responsible for too much
e.g. The end-game test is creating a game, changing the status, and ending the game. So now the test for "end game" will fail if the code fails in the GameControler under "create game".
Solution 1 - Instead of calling controller methods, just directly modify the state of the database. The only disadvantage I can see to this is that there will be less test-coverage-redundancy, but this should be fine if we've tested every possible scenario in its own test. This way when a specific function fails, it will only fail the corresponding test.
☐ Replace assert.equal with assert.strictEqual
I feel like using strictEqual would be less susceptible to some rare instances of tests passing when they shouldn't. I have no idea if this is actually true though. Needs research.
Database:
☐ Write typeorm migrations - but only once we go to production @created(19-08-27 08:08)
Optimize:
☐ Consider adding "use strict" to the top of all source files @created(19-08-30 23:18)
☐ Improve startup time for `npm run test`
☐ Fix having to restart the app multiple times waiting for database connection
☐ Handle soft/hard lobby deletes depending on whether or not matches exist for the given lobby.
e.g. if the user adds the lobby, then immediately removes it, this is okay to hard delete.
☐ Too many match results being downloaded from the osu API.
Need some way of limiting the maximum number of matches downloaded per multiplayer (right now we're using 500kb/s for 150+ matches in a single multi when polling every 1s)
Refactor:
☐ Rename DiscordMessageBuilder -> DiscordCommandMessageBuilder and all similar classes @created(19-11-12 19:40)
☐ Replace object in method args with (args: {...}) @created(19-10-17 15:53)
to simplify the Document This comments, and reduce the character count in the method signatures
☐ For next big version @created(19-10-08 15:30)
☐ Use io-ts with Either
☐ Use CQS/CQRS pattern (see nestjs/cqrs)
☐ Use NestJS
☐ In class MultiplayerResultsProcessor @created(19-10-06 21:00)
☐ Extract everything responsible for building LobbyBeatmapStatusMessage objects into its own builder class @created(19-10-06 21:00)
☐ Extract everything not "processing multiplayer results" to somewhere-else @created(19-10-06 21:01)
✔ Building the messages @created(19-10-06 21:22) @done(19-10-08 04:00)
✔ Saving multiplayer entities @created(19-10-06 21:23) @done(19-10-08 04:00)
✔ Building the beatmap lobby groups @created(19-10-06 21:23) @done(19-10-08 04:00)
☐ Building event/leaderboard reports @created(19-10-06 21:23)
☐ Clean up the imports in every ts file @created(19-09-24 19:52)
✔ Get ConnectionManager instance from iocContainer instead of singleton getInstance() method @created(19-09-23 23:05) @done(19-09-24 15:33)
✘ Replace with inversify provider @created(19-09-24 00:18) @cancelled(19-09-24 15:34)
See: https://github.com/inversify/InversifyJS/blob/master/wiki/provider_injection.md
and: https://stackoverflow.com/questions/40880447/how-to-inject-an-asynchronous-dependency-in-inversify
✔ Replace all uses of "NodesuApiFetcher.getInstance()" with a singleton instance of IOsuApiFetcher resolved from the inversify DIC @created(19-09-11 15:13) @done(19-09-14 22:41)
✘ Look into using LazyServiceIdentifier instead of lazyInject @created(19-09-09 23:15) @cancelled(19-09-24 15:34)
The reason for this is so we can move the injections into constructor arguments. Then we'd have the option to overide these upon class initialization.
☐ Move out files from src/discord/.. into their respective domain folders @created(19-09-07 18:26)
✔ Swap inversify back to typical @injectable() decorators on classes instead of using autoProvide with inversify.entities.ts file @created(19-09-02 09:58) @done(19-09-24 15:34)
This is just so we have one less dependency, and we can do things like mapping interfaces to class implementations. And create singleton instances of classes.
☐ Replace strings with Symbols in locale files e.g. en.json @created(19-08-28 19:41)
☐ Replace the default commando "catch-all" error-handler.
Replace it with our own specific one with messages formatted the same as the ErrorDiscordMessageBuilder. Consider making ErrorDiscordMessageBuilder handle something less specific than just Response objects, or create another ErrorBuilder abstraction layer.
☐ Cleanup the failure methods.
Can we abstract and extend these? Keep them more consistent; less method signature copy pasting.
✔ Replace lolex with a spy in lobby-create.test. @done(19-09-23 15:45)
Using lolex is just a temp solution for now. The problem is if we use another timer anywhere else within the LobbyController.create method chain, it will replace all those timers with the lolex mock.
✔ Replaced with FakeOsuLobbyScanner implemention of IOsuLobbyScanner @created(19-09-23 15:45) @done(19-09-23 15:45)
☐ Inject IOsuApiFetcher instead of getting directly from the DIC
[12:00] bossqone: then you're hiding dependencies of your class, because you're passing whole container, not individual dependencies
[12:07] bossqone: no problem at all, it will work anyway :) It's just tip to make your code more testable (e.g. you want to test that class. Now you need to construct whole DI container, bind correct instances, etc.., instead of passing mocked/correct instance directly to your class via constructor)
@created(19-09-15 00:17)
Inversify/ioc/dic/injectables dump:
// @lazyInject(TYPES.GameService) private gameService: GameService;
// @lazyInject(TYPES.Permissions) private permissions: Permissions;
// @lazyInject(TYPES.RequesterFactory) private requesterFactory: RequesterFactory;
// private gameService: GameService = iocContainer.get<GameService>(TYPES.GameService);
// private permissions: Permissions = iocContainer.get<Permissions>(TYPES.Permissions);
// private requesterFactory: RequesterFactory = iocContainer.get<RequesterFactory>(TYPES.RequesterFactory);
// @inject(new LazyServiceIdentifer(() => TYPES.GameService)) protected gameService: GameService,
// @inject(new LazyServiceIdentifer(() => TYPES.Permissions)) protected permissions: Permissions,
// @inject(new LazyServiceIdentifer(() => TYPES.RequesterFactory)) protected requesterFactory: RequesterFactory
// @inject(TYPES.GameService) protected gameService: GameService,
// @inject(TYPES.Permissions) protected permissions: Permissions,
// @inject(TYPES.RequesterFactory) protected requesterFactory: RequesterFactory
Logging:
☐ Create more logger levels to make it easier to filter certain events @created(19-09-05 08:17)
Like if we didn't care about classes being instantiated, all the instantiation Logger events could be turned off.
Or if we just wanted to see osu API events, we could just enable those.
☐ Can we create a separate log file for each game? @created(19-09-12 13:54)
To make it easier to find and analyze the events of indivudual games.
☐ Replace all calls to Log.methodFailure(etc) with objects like {methodName: ..., className: ...} @created(19-11-12 20:26)
Pre-launch checklist:
☐ Replace all uses of "!obr" with "!mpp" @created(19-09-25 23:38)
☐ Some way of keeping dependencies secure and up-to-date
Something like: https://dependabot.com/javascript/
☐ Contact peppy and see if we can get the API ratelimit increased from 60 max within 60 seconds to 1200 within 60 seconds. @created(19-09-04 06:57)
☐ Set typeorm logging settings to log to file only: @created(19-09-08 11:15)
☐ Queries with a long execution time @created(19-09-08 11:15)
☐ Query errors @created(19-09-08 11:15)
https://github.com/typeorm/typeorm/blob/master/docs/logging.md
☐ Set log4js to log only warnings and errors to file @created(19-09-08 11:16)
☐ Move sqlite to postgres? Performance may be better (e.g. concurrency) @created(19-09-08 11:22)
☐ Prevent the app from launching multiple instances @created(19-09-10 13:05)
Domain/Website:
✔ osumpp.xyz domain registered with Porkbun @created(19-09-20 01:06) @done(19-09-20 01:06)
Documentation:
☐ Explain how BR scoring works; how a winner is determined @created(19-10-30 14:07)
Design:
What we have access to - maps, mods(SD), modes - team vs, ffa, TAG4, osu API
✔ We need a name for this thing @done(19-09-24 15:36)
✘ Last Man Standing = LMS, so maybe lmsu -> lemonsu (good lord this is terrible; need more coffee) @cancelled(19-09-24 15:36)
✔ Multiplayer Plus / osu!MP+ / osu!MPP Bot @done(19-09-24 15:36)
☐ Would be cool to run a single Discord server where everyone goes to create matches @created(19-09-10 13:45)
☐ And the server runs some official matches where users can sign up and earn prestige
☐ With higher prestige getting higher roles, shown higher in the server list
☐ The bot should auto-create/schedule games and auto-select a random map pool
e.g. DigitalHypno's tournament pool spreadsheet - https://docs.google.com/spreadsheets/d/1Pm1ihvelNLk5hyAxDir5o8UgPuqE9CEsJ00jBv3OLs4/edit#gid=1437326481
-- Average player rank should determine pool difficulty
☐ Users can create their own games, but no rewards will be granted (since this could be abused for farming prestige)
☐ Twitch bot that streamer players can add to their chat to send updates after each map with the latest results, who's in the lead, etc. @created(19-10-25 21:55)
☐ Streamer should authenticate their Twitch account before the bot gets added to their chat @created(19-10-25 21:56)
☐ Bot should have a !removeme command so streamers can remove the bot from their chat @created(19-10-25 21:57)
Auto-Scheduled Tournaments:
☐ Users should sign up with their osu account up to 1 week before the tournament is scheduled to begin @created(19-09-26 13:15)
☐ Users should "check in" 1 hour before the tournament is scheduled to begin @created(19-09-26 13:15)
Progression/Level-up Server System:
Encourage people to use the bot more.
☐ Prestige Badges
☐ The more games you start, the more you level up.
☐ As you level up, you unlock more modes, e.g. like winner deciding who gets host.
☐ [1:18] Dionysaw: You unlock maps that you can play in lobbies
Mini-Games:
☐ Voting for the winner (betting)
☐ Guess the roll
☐ osu! trivia quiz
Game Modes:
☐ Team handicaps
☐ Option to ignore score bonuses/reductions in score calculations (e.g. EZ 50% -> 100%, HD 106% -> 100%)
☐ Team Vs / Sudden Death idea
☐ Multiple lobby support for 16x16 teams. Could in theory have any nxn sizes, but maybe we want to limit it to 16x16 to make the chat experience better.
☐ randomized team vs player arrangements @created(19-09-25 23:27)
☐ team vs across multiple lobbies should work - if one lobby ends the map, the bot observes that and ends all the other lobbies @created(19-09-25 23:27)
☐ Battle Royale / Last-Man-Standing, either solo or teams
☐ MING MODE - Whoever can pass with the lowest acc wins. Can't fail at any point.
☐ "True Elimination" Mode - Add option for kicking the losing team/player from the lobby within some short time after losing all their lives.
e.g. "CeilingWaffle doesn't feel so good..." *kicked*
☐ Randomly request a roll of 50 or above to save themselves (to gain a life)
☐ The winner gets host
☐ The winner gets to the decide who gets kicked,
☐ or maybe gets to choose one of the bottom 2 players to be kicked
☐ or make it - everyone votes to decide who gets kicked
☐ or the winner decides the target player, and the lobby votes yay or nay on whether or not they actually get kicked.
☐ If you SS, you gain a life, or temporarily gain some kind of forbidden power. Like you choose the next map.
☐ If you're on 0 lives, you need to win the next map, otherwise you're eliminated. "One final chance" mode.
☐ Riwaka: One player can force a mod on the entire lobby
Would also be cool if one player could force a mod on a specific player
Emoji Icons:
🕹 Game
🏠 Lobby
👪 Team
🙋 User/Ref
Adding Lobbies:
✔ Currently, the lobby service is trying to add a new Lobby with the same banchoMultiplayerId rather than create a new Lobby-Game relationship. To fix this, add some code in the lobby service to check for the existing banchoMultiplayerId Lobby, and push the target-Game onto the lobby.games array. @started(19-08-18 17:00) @done(19-08-18 17:31) @lasted(31m15s)
✔ Make bancho multiplayer ID unique to lobby. No two lobbies should exist with the same mp ID. @done(19-08-18 19:49)
☐ We need some way to support adding a lobby after a game has already started. @created(19-09-07 18:57)
e.g. if it's a game involving hundreds of players, and we want to pause and continue the next day/next week, the original lobby would be closed and re-created later - need to support that lobby re-creation step.
User Permissions:
https://github.com/tensult/role-acl
✔ Ensure that the user *removing a lobby* is a game ref or game creator @done(19-08-27 00:46)
✔ Ensure that the user *adding a lobby* is a game ref or game creator @done(19-08-27 00:46)
✔ Need some kind of game-permissions abstraction. e.g. user1.for(lobby).isPermittedTo(LobbyUpdateAction) - does a library already exist for this sort of thing? @done(19-08-27 00:46)
☐ Allow Discord roles to be used for app-roles. Use both Discord-roles and DB-user-roles.
☐ Create a more friendly action-specific "permission denied for action/resource" message @created(19-09-17 22:27)
See permissions.ts - buildPermittedResult() method
User-Game-Roles:
✘ After the game is created, add an entry in UserGameRoles using game id and requester-user id @cancelled(19-08-23 19:32)
✘ OLD IDEA -------- add the game without a relation to the UserGameRole, then create a new UserGameRole using gameCreator.userId + newGame.gameId + role === "game-creator" (this should update the game afterwards with a reference to the UserGameRole) @cancelled(19-08-23 19:32)
✘ Should we store a redundant reference to the game creator on the game entity? Possibly bad because now we have multiple references to the creator - what if we access it from the wrong entity while working on this in a few months time? @cancelled(19-08-23 19:32)
Discord:
Announcement Messages:
☐ Some way to send announcement messages via Discord - e.g. "Experiencing problem." "Down time for maintenance expected from x-y time on day d" @created(19-09-25 07:51)
☐ Send auto-announcement on git push/CI -- with changelog changes @created(19-09-25 07:51)
Bot Permissions Required:
☐ Delete messages (e.g. user types yes to confirm destructive command - we need to delete that message) @created(19-08-30 23:06)
☐ Create text channels (to create game-specific channels) @created(19-08-30 23:07)
Misc:
☐ Allow users to customize what is displayed on the leaderboard (e.g. disable player map ranks from being shown) @created(19-09-22 21:08)
☐ Figure out how to handle the situation where the target-channel of the GameMessageTarget has been deleted. Where do messages get sent to? Maybe DM the original author?
Use authorId as the message-target to do this.
☐ End game and shut-down watchers if the game target-channel is deleted. Listen for the "Channel Delete" event on the Discord.js Client:
Channel Delete event: https://discord.js.org/#/docs/main/stable/class/Client?scrollTo=e-channelDelete
All Discord events: https://discord.js.org/#/docs/main/stable/typedef/WSEventType
☐ Also if the server is deleted. Is there an event for that? Send some kind of "server deleted, so we're shutting down the game" message to the original game author.
✔ Get user confirmation before performing destructive commands @created(19-08-26 21:53) @done(19-08-26 23:17)
e.g. !obr endgame -> "Are you sure you want to end game 5? Enter yes to confirm."
☐ Delete the user's `yes` confirmation message @created(19-08-30 23:05)
☐ Send game-event messages to a specific game channel, and send @ replies to the user in the channel where they wrote the command. @created(19-08-28 08:22)
e.g. User types !obr creategame.
Bot creates channel #obr-game-5
Bot replies in same channel "@user, The game has been created and channel #obr-game-5 was opened".
Bot emits message in #obr-game-5 with a game-created card containing the game properties.
Bot emits message with match results in #obr-game-5 when a lobby-match finishes.
Only admin, game-creator, and game ref are allowed to write messages in channel #obr-game-5.
Commands:
Discord:
✘ `!obr setchannel #<discordTextChannelName>` @created(19-09-01 23:50) @cancelled(19-09-10 19:27)
Sets the channel where match results and game-related messages will be delivered to.
General:
✘ `!obr undo` @created(19-09-01 22:36) @cancelled(19-09-10 19:27)
Undoes the last command of the user. We won't need to confirm command actions with "yes" after this is implemented.
If this command is fired multiple times, it keeps undoing the command preceeding the previous undone one.
✘ `!obr redo` @created(19-09-01 22:36) @cancelled(19-09-10 19:27)
Redoes the last command of the user.
✔ `!obr targetgame <gameId>` @created(19-09-01 22:37) @done(19-09-10 19:27)
Sets the gameId as the target of the user's commands. If not used, the user's most recent game is the target.
Game:
✔ `!obr creategame` @done(19-09-01 22:56)
✘ Refuse `!obr creategame` command from a game-channel. @cancelled(19-08-22 20:14)
This *should* be OK to allow, but could be weird in situations where one game has an initial-channel as the game-channel of another game. Could be problematic with channel deletions. So just disallow this for now to be safe.
☐ Warn the user if they use `!obr creategame` if the last game they created is still active @created(19-08-31 08:05)
"Looks like the last game you created is still active (game id x). Are you sure you want to start a new game? Reply with "yes" if so. Otherwise do nothing and this message will expire in t seconds."
☐ Alias `!obr creategame` -> `!obr newgame` @created(19-08-31 08:05)
✔ `!obr endgame <gameId?>` DESTRUCTIVE @created(19-09-01 22:48) @done(19-09-01 22:56)
☐ Post final game results (if any) @created(19-09-10 13:37)
✔ `!obr editgame <prop> <propValue>` @created(19-08-31 23:41) @started(19-09-01 23:11) @done(19-09-02 12:36) @lasted(13h25m58s)
☐ `!obr startgame <gameId?>` @created(19-09-01 22:57)
Validation:
☐ Do not start game if the game has no registered teams @created(19-09-24 22:15)
☐ Do not start game if the game has no lobby added @created(19-09-24 22:15)
☐ `!obr gameinfo <gameId?>` @created(19-08-31 23:41)
See types - https://discord.js.org/#/docs/commando/master/typedef/ArgumentInfo
Use a union type to check: If integer, assume gameId. If string, assume propKey.
Include description "use !obr targetgame to specify another game".
☐ `!obr listgames <active/closed/(default:all)?>` @created(19-09-01 22:38)
List the games created within this Discord server.
☐ `!obr update` @created(19-10-30 14:09)
Forces a lobby-results check on all active lobbies for the user's target game.
If not all lobbies have completed the next unreported virtual match, display a message like "Waiting for lobby x to complete beatmap y (number n)".
Lobby:
☐ `!obr addlobby <banchoMpId>` @created(19-09-01 22:58)
✔ When using command !obr addlobby 54078930 6, on a game which already has that lobby added, return some error message. @started(19-08-19 00:30) @done(19-08-19 05:13) @lasted(4h43m45s)
✔ Don't actually starting watching for results until `!obr startgame` is used, unless the lobby was added to an already-started game @created(19-09-02 13:57) @done(19-09-27 22:58)
☐ We should be able to add a lobby to an already-started game and just have the score calculator calculate scores for registered players in the new lobby during the score-calculation at the end of each match @created(19-09-02 13:58)
✔ `!obr removelobby <banchoMpId>` @created(19-09-01 23:05) @done(19-09-01 23:05)
✔ Add a manual remove-lobby command. Include description like "this will not close the actual lobby; it will only tell the bot to stop scanning the lobby for future results." @started(19-08-19 00:30) @done(19-08-19 05:13) @lasted(4h43m45s)
User Roles:
☐ `!obr addref @<discordUser>, @<discordUser>, ...` @created(19-08-31 23:41)
☐ `!obr removeref @<discordUser>, @<discordUser>, ...` @created(19-08-31 23:41)
☐ `!obr listrefs` @created(19-08-31 23:41)
Teams:
✔ `!obr addteam <ouid1> <ouid2> ...` @created(19-08-31 23:41) @started(19-09-07 16:48) @done(19-09-22 13:37) @lasted(2w20h49m1s)
Adds a team of osu users. See targetgame command.
✔ Allow multiline like `!obr addteams\n<ouid1> <ouid2>\n<ouid3> <ouid4>\n...` @created(19-08-31 14:04) @done(19-09-20 13:42)
☐ If match results are processed before any teams are added @created(19-09-07 16:51)
✘ osu users from the results are created and added to a new team of 1 @cancelled(19-09-20 13:43)
✘ But then if a team is created with 2+ osu users, and one of those osu users is in the team of 1, delete the team of 1 and create a new team with the 2+ osu users @cancelled(19-09-20 13:43)
☐ Do not create any teams. Create the osu users only. @created(19-09-20 13:43)
☐ How do we handle database integrity if we delete a team? @created(19-09-19 00:34)
✔ In TeamResponseFactory - Make subject GameTeam instead of Team @created(19-09-19 00:34) @done(19-09-19 00:35)
✔ In AddTeamDiscordMessageBuilder - Replace "Team ID x" with "Team Number y (color)" @created(19-09-19 00:34) @done(19-09-19 00:35)
✔ In AddTeamDiscordMessageBuilder - Replace command example with -- remove team using !obr removeteam <teamNumber> (instead of <teamId>) @created(19-09-19 00:34) @done(19-09-19 00:35)
☐ `!obr removeteam <ouid1> <ouid2> ...` @created(19-08-31 23:41)
☐ `!obr clearteams` DESTRUCTIVE @created(19-08-31 23:41)
Matches:
☐ `!obr undoresult <mapId?/mapName?>` @created(19-09-02 13:40)
Undoes the last match result for this game (i.e. when a map should be replayed).
Match scores are marked as skipped, and the preceeding lost team life gets restored.
If the game ended, it is restored to active.
If this command is fired multiple times, it keeps undoing the result preceeding the previous undone one.
☐ mapName @created(19-09-02 13:49)
☐ Clever search to target the result of a specific map. @created(20-03-23 23:10)
☐ Some minimum distance time @created(19-09-02 13:50)
So if a result comes in immediately before they use the command, it doesn't undo that immediate recent result - it should target the result prior to that one instead.
☐ `!obr redoresult <mapId?/mapName?>` @created(19-09-02 13:43)
In case they want to undo the undo.
☐ `!obr undoabort <matchId>` @created(19-09-22 13:40)
Leaderboard:
☐ `!obr emoji` or `!obr icons` @created(19-09-06 08:19)
Lists every "team performance icon" and explains what each one represents (e.g. 🥄 = Team ended in last place)
Scores:
☐ `!obr addscore <matchId> <osuUserId> <score>` @created(19-09-22 13:40)
☐ Validate: @created(19-09-22 13:43)
☐ user is permitted @created(19-09-22 13:44)
☐ valid match ID @created(19-09-22 13:43)
☐ osu user exists in team in game. @created(19-09-22 13:43)
☐ score gte 0 @created(19-09-22 13:43)
☐ Bot should recalculate entire game results after every addscore command, and output 1 report with the current leaderboard. @created(19-09-22 13:43)
☐ `!obr removescore <matchId> <osuUserId>` @created(19-09-22 13:40)
☐ Validate: @created(19-09-22 13:43)
☐ user is permitted @created(19-09-22 13:44)
☐ valid match ID @created(19-09-22 13:43)
☐ osu user exists in team in game. @created(19-09-22 13:43)
☐ Bot should recalculate entire game results after every removescore command, and output 1 report with the current leaderboard. @created(19-09-22 13:43)
☐ `!obr listscores <matchId>` @created(19-09-22 13:45)
Lobby Management:
Game Management:
☐ Add command `!obr game <gameId> <prop> <newPropValue>` @created(19-08-31 13:59)
Server Management:
✘ Add discord command `!obr set-channels-category "OBR Games"` @cancelled(19-08-22 20:14)
This should create a new Discord-category (container of text-channels) where all future-created game-channels will be created in for this server.
Multiplayer Matches:
☐ Use lobby.startingMapNumber to skip some map results
osu-lobby-watcher.ts -> refreshAndEmitMultiplayerResults()
☐ Handle match aborts @started(19-11-04 08:24)
☐ Send discord message if it looks like the last match was aborted.
☐ Use "!obr include <matchId/mapNumber>" to essentially "undo" the abort, and thereby choose to include scores from the aborted match. This is incase the system mistakingly analyzed the match as aborted. @created(19-09-04 15:10)
☐ Implementing match aborts @created(19-11-04 08:16)
☐ Add user permissions to abort/undo-abort match @created(19-11-04 08:16)
☐ Aborting a match @created(19-11-04 08:18)
☐ Add discord command
☐ Add controller method
☐ Add MatchService method
☐ Write match abort test
(controller -> service -> response -> load match from DB -> assert aborted status)
☐ Modify VirtualMatchCreator to handle aborted matches as if they don't exist
☐ Modify scores calculator to ignore scores for aborted matches
☐ Use the OsuLobbyScanner to send an "aborted match" event using the EventDispatcher.
☐ A Discord message should be sent to the game channel.
☐ Modify the spreadsheet example to include an aborted match
☐ Undoing a match abort @created(19-11-04 08:20)
☐ Add discord command
☐ Add controller method
☐ Add MatchService method
☐ Write undo-match-abort test
(controller -> service -> response -> load match from DB -> assert not-aborted status)
✔ Process match results @created(19-09-03 11:19) @started(19-09-03 11:19) @done(19-10-03 11:19)
☐ Add OsuUser entity @created(19-09-03 19:37)
☐ Add TeamScore entity @created(19-09-03 19:37)
☐ Add MultiplayerMatch entity @created(19-09-03 19:38)
☐ A team can continue submitting scores after eliminated, but they won't be counted for anything important - only included with the report to be displayed in the match results message. @created(19-09-04 11:03)
☐ Team rank/leaderboard @created(19-09-04 10:08)
Rank 1...n listed from top to bottom in order of
-- time when eliminated (team listed further towards the top if their time eliminated is more recent)
-- current lives remaining (higher lives => higher rank)
-- cumulative number of positions achieved (e.g. Team A has positioned higher than Team B on average during this game => list Team A above Team B)
-- total of all team scores achieved during this game
^ (algorithm: proceed to continue checking the next condition only if the current condition is equal)
☐ How should we handle a match consisting of only 1 player? @created(19-09-19 17:45)
-- Solution 1: Calculate match results like normal, but no lives are lost. i.e. the game continues forever until another player joins.
Tournament Client Notes:
--
App Design:
Displaying info about the game state in OBS:
Links:
-- BR Clip: https://clips.twitch.tv/BoringSingleTildeTriHard
-- Number of scenes needed in OBS: https://docs.google.com/spreadsheets/d/1rPxYmPPBAcL4kxhcIbevoca_3wkXkN-4CEdIrAR4TWE/edit#gid=0
-- obs-websockets - https://theomynomy.s-ul.eu/aBlcIPhu
-- IPC
-- https://github.com/ppy/osu/blob/master/osu.Game.Tournament/IPC/FileBasedIPC.cs
-- https://github.com/NotAdam/osu-ipc/blob/master/osu!meme/osu/Interprocess.cs
-- tourney client setup: https://osu.ppy.sh/help/wiki/osu!tourney/Setup/
☐ MVP Requirements @created(19-09-10 19:31)
☐ Show lives remaining per team
☐ Show leadboard position per team
☐ Show team scores
TO do this, we need to memory scan the tournament client osu! instances
(The IPC text file generated is limited to only using scores from 2 teams, so we need to memory scan each of the osu!.exe processes to get scores for each of the player windows)
☐ We need scores from each of the tournament client windows
☐ Need to output these to a text file, or transmit over TCP, or IPC, or websockets
☐ The local bot generates team-scores from memory scanning:
9:38 TheOmyNomy: I just output files based on team size.
9:38 TheOmyNomy: If it's a team size of 3, there will be a file for slots-1-2-3.txt and slots-4-5-6.txt.
☐ Needs to know the team size - this can just be entered manually by the streamer for now.
☐ Ideally, OBS should auto-change scenes based on the number of teams remaining. If a team gets eliminated, they are also eliminated from the overlay. See https://github.com/Palakis/obs-websocket
☐ The BR bot should move players around in slots in the lobby, so the team with the most lives remaining is always in the top slots.
☐ The BR bot should be transmitting lives-remaining and leaderboard positions, and team-player compositions, and slot positions to an OBS browser source, and to a local bot listening for those events.
Are we sure we can transmit slot positions for each player, and can we do this in realmtime to keep it as in-sync as possible?
☐ The local bot can also listen for the BR-bot websocket events,
☐ and have a real-time record of which osu players are in which slots, @created(19-09-10 21:41)
✘ which players are in which teams, @created(19-09-10 21:41) @cancelled(19-09-10 21:42)
☐ and then reads the osu-clients to get player scores, @created(19-09-10 21:41)
✘ and then use that to generate team-score txt files for each team. @created(19-09-10 21:41) @cancelled(19-09-10 21:42)
☐ for each group of slots, not for each team @created(19-09-10 21:42)
☐ The local bot has: team scores, and which client the scores were calculated from,
☐ The BR bot has team lives remaining, and which players are in which lobby slots.
☐ The stream would need to have a new scene for each combination of team-size and teams-remaining situation
(assumption is that teams are always of equal sizes, e.g. not 1v2v3 or 1v15)
☐ If players ina team swap slots with players in another team, the team-score and lives-remaining should also "swap" those positions, but this is depoendent on the local memory reading bot being able to correlate the osu-client position with the player username (i.e. we need to read the player's name from the tournament client memory).
☐ To show the lives remaining in each OBS scene in the correct position for the team corresponding to the client-slots, the BR bot will generate a unique URL
☐ Restrictions for players to abide by:
☐ Players in teams always need to be in slots next to each other
☐ As teams are elminated, any teams in slots below the eliminated team should move up, so there should be no gaps in slots between players, and only the bottom slots should be empty
☐ Showing team lives in OBS scenes: @created(19-09-10 22:03)
https://waffle.bot/game/1/lobby/1/team-lives/slots/1/2
-- gets the number of lives remaining for the team positioned in slots 1 and 2 in lobby 1 of game 1
-- browser source can then be added in a static-position in a scene in OBS, and the number of lives will stay in-sync with whatever team is in slots 1 and 2.
-- websockets client running on this URL
-- e.g. when a player moves from slot 3 to slot 1, an in-memory DB (e.g. redis) records the new lives of the team of that player in that slot, and the app sends a websockets message to the client which updates the number of lives displayed for that slot-group.
Match Result Reports:
Processing Match Results:
☐ Entity changes @created(19-09-07 10:20)
State changes over time: https://drive.google.com/file/d/1MXJR-XSlzcyyxUxMVRZd83pwLTxoGAvP/view?usp=sharing
DB Diagram: https://dbdiagram.io/d/5d1657f5ced98361d6dc2742
✔ Remove TeamScore @done(19-09-07 15:03)
Calculate team scores from player scores instead
✔ Remove GameTeamScore @done(19-09-07 15:05)
✔ Add TeamOsuUser (with addedBy) @done(19-09-07 15:21)
So we can know who added the user to the team
✔ Add Realm (with realmType string as union of "discord_realm" | "web_realm") @done(19-09-07 16:10)
-- This is to avoid privacy issues, by making it possible to filter results in reports like "show all team scores", where if the same team was also created in another realm, we only include team scores belonging to games created in the realm where the command was sent from.
-- See CommunicationClientType, but avoid copying these strings to the db in case we rename the types in the codebase later.
☐ Use an _exhaustiveCheck to map the types to a specific entity (e.g. GameStatus) and retrieve them from a function like "getDatabaseRealmType(from: CommunicationClientType): Entity" @created(19-09-07 10:58) @created(19-09-07 10:58)
☐ This should also be done for any "status" types. @created(19-09-07 10:58)
☐ Maybe create a TypeToDatabaseEntityMapper class. @created(19-09-07 10:57)
☐ Load those database type-entities from the database during app initialization (populate TypeToDatabaseValueMapper static properties with references to the loaded type-entities) @created(19-09-07 10:59)
This way, we only query the database once at the start of the app.
☐ And then update all entity relationships to reference the SpecificTypeEntity. @created(19-09-07 10:57)
✘ Consider adding a TeamUserGameTeam entity @cancelled(19-09-07 10:43)
-- To accomodate the scenario where we want the user to be able to apply an optional adjustment to any scores set by a user for a specific team in a specific game (e.g. hadicap score by reducing it by 50%).
-- I think we could just add this later without needing to change any code? We could add in some procedure during the score-calculation/report-creation process to query this table for any adjustments to be made, before reporting the score.
☐ Testing osu multiplayer match results API object transformed into a report object @created(19-09-05 08:25)
☐ arrange: ApiMultiplayer object
☐ act: do the transforming
☐ assert: MatchResultsReport object
☐ Input to output steps (API to Report) @created(19-09-05 11:38)
✔ input: ApiMultiplayer @done(19-09-12 20:08)
✔ find Game using ApiMultiplayer.multiplayerId @done(19-09-12 20:08)
✔ find/create OsuUser[] using ApiMultiplayer.ApiMatch.ApiPlayerScore.osuUserId @done(19-09-12 20:08)
☐ find Team using OsuUser[] and Game
TeamRepository.findTeamOfOsuUserInGame(osuUser, game)
☐ create PlayerScore[] from ApiMultiplayer.ApiMatch.ApiPlayerScore
☐ create TeamScore[] from PlayerScore[] and Game (game rules) and ApiMultiplayer.ApiMatch (e.g. match aborted)
☐ Determine events based on the state of the entities
entity state -> action -- e.g. lowest TeamScore.score -> Team loses a life
☐ Do actions based on events (e.g. event game winner -> GameService.endGame())
☐ build the leaderboard from entities and events
☐ output: MatchResultsReport
☐ ...use some kind of templating engine to generate the discord message
☐ General idea @created(19-09-06 11:02)
☐ Takes API Multiplayer object
☐ -> UserService
✔ create/find/update User entities @done(19-09-12 20:08)
☐ Update osu username if changed @created(19-09-12 20:08)
☐ -> TeamService
☐ create/find Team entities from users + MP Object
☐ -> ScoringService -> TeamScoreCalculator
☐ calculate team scores
☐ create TeamScore entities from Teams + calulcated scores + MP Object
☐ -> (this)
☐ save the created/updated entities
☐ -> GameEventsBuilder to build GameEvent objects *from stored entities*
e.g. like "⭐ Team X won the match!" and "🛑 Match aborted! No lives were lost."
"from entities" is important - so we can recreate them from the database store in future if needed
☐ -> GameLeaderboardBuilder to build GameTeamLine objects
-- Should NOT include any formatting/design/emoji - only Types and data
-- Independent (I) vs Dependent (D) on other line
(I) leaderboard position lost/gained/same
(I) team number / team name
(I) team performance type (determined from GameEvents)
(I) lives remaining
(I) lives max
(I) team score
(I) players scores
(I) players map rank achieved
☐ -> GameReportBuilder to build GameReport objects from all the entities/objects
Reporting Leaderboards on Multiple-Lobby Games:
☐ Implement some kind of time limit for the maximum amount of time a game will wait to allow a lobby to complete a beatmap @created(19-10-14 01:02)
If a lobby fails to submit a score for a virtual-beatmap within the time frame, assume all teams present in that lobby scored 0, then calculate results like normal.
✘ Solution 1: @created(19-09-22 13:33) @cancelled(19-09-22 13:33)
Don't allow multiple-lobby games, and just deliver a report whenever a lobby map finishes.
✘ Solution 2: @created(19-09-22 13:33) @cancelled(19-09-22 13:33)
Assume the same maps are being played in the same order in all lobbies, and deliver a report whenever the last lobby finishes the map.
(This will be enforcable when the bot can create and manage lobbies [i.e. not in v1 when one of the users creates the lobbies and we just "observe" the lobbies])
☐ Solution 3: @created(19-09-22 13:33)
- Wait until all lobbies finish a map, then deliver a results-report for that map.
- e.g.
- lobby L1 finishes map A at time T,
- lobby L2 finishes map B at time T+1
- (bot does not deliver a report)
- bot sends a status message saying "Lobby L1 finished map A. Waiting on results from lobbies B,..."
- lobby L2 finishes map A at time T+2
- map A has now been completed by all lobbies --> bot delivers report on results for map A
- lobby L1 finishes map B at time T+3
- map B has now been completed by all lobbies--> bot delivers report on results for map B
- If the same map is played two times in the same lobby, wait to report results for that map until all other lobbies finish the map a second time.
Leaderboard Image Design:
- Overlapping circular avatars for each team member
team captain is the outter layer, with other team member avatars hidden below
- Leaderboard icon in one of the corners, indicating that the image is a leaderboard e.g. https://i.imgur.com/8YNraXF.png
Examples
- https://giantbomb1.cbsistatic.com/uploads/scale_medium/0/1992/1677549-steamleaderboard.jpg
Discord Message Design:
Test message design with: https://cog-creators.github.io/discord-embed-sandbox/
☐ Example 1 @created(19-09-04 10:55)
Can each player get their own colour? This would help players locate themselves as the leaderboard shifts around.
e.g. https://i.imgur.com/9Fhz8yJ.png
Game messages shown as soon as they happen, or in one big batch if we're catching up to the latest match (e.g. if a lobby was added after some maps have been played)...
🦞 Lobby 1 completed BM1#1 15 minutes ago
🦞 Lobby 2 completed BM1#1 14 minutes ago
✅ All lobbies have completed BM1#1 (Winner: Team X, Loser: Team Y, Eliminated: Team Y)
🦞 Lobby 1 completed BM2#1 12 minutes ago
🦞 Lobby 1 completed BM2#2 11 minutes ago
✅ All lobbies have completed BM2#2 (Tied: Team X and Team Y [no lives lost])
🦞 Lobby 1 completed BM3#1 39 seconds ago
⌛ Waiting on BM1#1, BM1#2, BM2#2 from lobby(ies) 2 (, 3, and 4),
Shown after all lobbies complete the same map...
--- Beatmap 123456 #1 finished in lobbies 1,2,3 ( Galneyrus = Raise My Sword [AAAAAAAAAA] ) ---
| ⭐ Team X won the match! |
| 🌟 Team X is on a winning streak of N! |
| 💥 Team Y lost a life! |
| 💀 Team Y was eliminated! |
---------------------------------------------------
Alive (4 maps remain)
⬆ 01. Team 1 |🌟| 🤎🤎🤍 | Score: 123,456
player1: 123,456 (SS)
player2: 123,456 (S)
⬇ 02. Team 3 |⬛| 🤎🤎🤍 | Score: 111,111👔 | p5 [S: 123,456], player6 [A: 123,456]
03. Team 2 |⬛| 🤎🤍🤍 | Score: 111,111👔 | player7 [B: 123,456], playerlongername8 [C: 123,456]
Eliminated
⬇ 04. Team 4 |💀| 🤍🤍🤍 | Score: 100,000 | player3 [D], player4 [B]
05. Team 5 |⬛| 🤍🤍🤍 | Score: 0 | player9 [🚫], player10 [F]
☐ Emoji indicators @created(19-09-04 09:44)
☐ Match
☐ 5⭐ in map title line (5 star map)
☐ Match Summary (may include more than one, listed in order of which is to be written first)
☐ ⭐ Team X won the match!
☐ 🌟 Team X is on a winning streak of N! []
☐ 💥 Team X lost a life!
☐ 💀 Team X was eliminated!
✘ Team Y is now in the accuracy average lead @cancelled(19-09-22 21:02) []
☐ 🛑 Match aborted! No lives were lost. (Undo with !undoabort <matchId>)
☐ 👔 Bottom tied! Teams X,Y(,..N-1) tied! No lives were lost.
If bottom teams tied (i.e. no loser can be determined)
☐ 🏆 Game over! The winner is Team X!
☐ 🚫 Player P from Team X quit
☐ Team X now in leaderboard position N (do not output)
☐ Team X gained/lost N leaderboard positions (do not output) []
☐ Team X scored N (do not output)
☐ Player P scored N (do not output)
☐ Player P achieved rank SS/S/A... (do not output)
These events will be processed to determine the leaderboard output
☐ Team Line
☐ Team Performance Icon (in order of priority)
✘ 🏆 Tournament winner @cancelled(19-09-15 13:21)
☐ 🥇 1st place (game over)
☐ 🥈 2nd place (game over)
☐ 🥉 3rd place (game over)
✘ 🏅 4th place (game over) @cancelled(19-09-19 19:14)
☐ 💀 Skull if team was eliminated this match
☐ 💥 Collision if team lost a life
✘ 🤩 Perfect Score (all team members SS) @cancelled(19-09-04 15:36)
☐ 🌟 Team is on a 1st-place winning streak (won gte 2 times in a row)
☐ ⭐ Team won the match!
✘ 🌜 Team had their 1st-place streak ended by another team @cancelled(19-09-04 15:37)
^ Probably best to leave this one out to avoid confusion
✘ 😭 Team score was exactly 0 @cancelled(19-09-09 08:33)
✘ 😅 Team scored 2nd-last and the score was only 1% more than the last team's score @cancelled(19-09-04 15:36)
✘ ♿ Team score was less than 5% of the highest team score @cancelled(19-09-04 15:36)
☐ 🥔 Player X is a potato
☐ 🥛 Player X is milk
☐ Default empty space
☐ Lives
☐ 🤎 One full-heart for each life remaining
-- If maxLives > 5, show "x lives" text instead of hearts.
☐ 🤍 One empty-heart for each life lost
☐ Leaderboard position
☐ ⬆📈☝🏻 - Gained position
☐ ⬇📉👇🏿 - Lost position
☐ ⏹↕️⬜✋🏽 - Same position
Or just display an empty space for same position
☐ Player
☐ [S][A][B][C][D][❌] Player map rank achieved (listed next to player name)
☐ [❌] Listed next to player if they failed the map (regardless of whether or not the game is counting failed scores)
☐ 🚫 Player left lobby / did not submit map result
☐ Score
☐ 👔 Tied with at least one other team
Footer Text Memes:
☐ Azer @created(19-09-04 11:55)
<WinningTeam> isn't so great? Are you kidding me? When was the last time you saw a player with such aim ability and movement with a tablet? Alex puts the game on another level, and we will be blessed if we ever see a player with his skill and passion for the game again. Cookiezi breaks records. Rafis breaks records. <WinningTeam> breaks the rules. You can keep your statistics. I prefer the magic.
Archive: