LibrePlanet: Conference/2022/Transcripts/Empowering-community-oriented-play-with-TRBot

From LibrePlanet
Jump to: navigation, search

Empowering community-oriented play with TRBot

Original event link: https://media.libreplanet.org/u/libreplanet/m/empowering-community-oriented-play-with-trbot/

This talk was given on March 19, 2022 from 13:35 to 14:20 EDT.

This transcript is in SupRip (.srt) format.

Transcript

1
00:00:00,000 --> 00:00:05,000
* Music *

2
00:00:08,000 --> 00:00:09,000
Great, thank you Michael.

3
00:00:10,000 --> 00:00:14,000
Hello everybody, umm, hope you've had a nice break.

4
00:00:14,100 --> 00:00:17,901
Smooth ride - we are back. I'm Amin Bandeli.

5
00:00:18,001 --> 00:00:21,001
I'm the room monitor for this talk.

6
00:00:21,002 --> 00:00:24,500
My IRC nick is just "bandeli"

7
00:00:24,501 --> 00:00:29,800
so if you do have any questions, please try to mention or highlight me on IRC

8
00:00:29,800 --> 00:00:32,600
so that I can see them easier and relay them here.

9
00:00:33,300 --> 00:00:39,900
Umm, so...this talk is titled "Empowering community-oriented play with TRBot",

10
00:00:39,900 --> 00:00:45,900
and will be presented, uh, presented by Thomas Deeb.
Uhh, Thomas is a professional software engineer,

11
00:00:45,900 --> 00:00:53,000
passionate about software freedom. He has worked in game development for over 6 years and chose to transition

12
00:00:53,000 --> 00:01:01,000
...umm, sorry, chose to release his debut indie title as free software during his transition to computing freedom.

13
00:01:01,000 --> 00:01:08,300
Umm, which started in 2019. Thomas has made contributions to many other free software projects,

14
00:01:08,300 --> 00:01:15,500
including Cinnamon, Nemo, MonoGame, and RetroArch.
Outside software development, Thomas' interests include

15
00:01:15,500 --> 00:01:24,200
yoga, hiking, and composing chiptune music. So umm, I'm very excited for this. Thomas, please take it away.

16
00:01:25,000 --> 00:01:27,800
Thank you Amin, really excited to be here.

17
00:01:28,000 --> 00:01:34,999
So this talk is "Empowering community-oriented play with TRBot". In the middle there is TRBot's logo by the awesome David Revoy.

18
00:01:35,600 --> 00:01:42,700
So here's an overview of how the talk is gonna go. First I'm gonna describe what is TRBot. Motivation and history of the project.

19
00:01:42,700 --> 00:01:50,100
The technical workings at a higher level. The community impact of TRBot. Future aspirations I have for TRBot,

20
00:01:50,100 --> 00:01:52,700
and then we'll leave some time at the end for some Q and A.

21
00:01:52,800 --> 00:01:58,500
Just a short disclaimer: I'll be referring to proprietary games and platforms that have had some sort of significance

22
00:01:58,500 --> 00:02:03,700
in TRBot's development and use. I'm not going to be endorsing or encouraging the use of these platforms.

23
00:02:04,500 --> 00:02:12,100
So, what is TRBot? So, in essence TRBot is software that can play games through text. There's an animation here showing me

24
00:02:12,100 --> 00:02:20,500
playing my own game, Maze Burrow, using the terminal. Typing inputs like up, down...and it's controlling the game. So, how does this work?

25
00:02:20,500 --> 00:02:28,300
TRBot has only two requirements. One is a source of text - in this case, the terminal - and then a virtual game controller, which is running in the background. And I'll be

26
00:02:28,300 --> 00:02:35,700
covering the virtual game controller in more detail later. But the important thing to note here is the text source can also be a chat platform,

27
00:02:35,700 --> 00:02:42,600
for example, IRC, and therefore anyone can control the game and this is the essence of what allows collaborative play.

28
00:02:42,700 --> 00:02:50,100
So, motivation for the project. In late 2016, I was on Twitch, and I was searching for The Legend of Zelda: Ocarina of Time streams

29
00:02:50,100 --> 00:02:58,800
- an old 1998 video game for the Nintendo 64 - and I found a stream called TwitchPlaysPaperMario. They were playing the game through chat.

30
00:02:58,800 --> 00:03:08,000
And before this, I only had exposure to this sort of concept in 2014 with TwitchPlaysPokemon. But in comparison, The Legend of Zelda: Ocarina of Time

31
00:03:08,000 --> 00:03:14,250
is an infinitely more complex game. There's 3D movement, there's like many sorts of maneuvers and timing involved.

32
00:03:14,250 --> 00:03:22,600
And I was just amazed, outstanded, like how did they do this, you know? The input syntax seemed really robust and allowed for great precision.

33
00:03:22,600 --> 00:03:28,750
So over time, I playe-interacted then joined the stream and played more games with them.

34
00:03:28,750 --> 00:03:34,100
And then in 2017, TwitchPlaysPaperMario rebranded itself to TwitchPlays_Everything.

35
00:03:34,100 --> 00:03:43,850
And uh, TRBot got its start around here. There was a rerun schedule that TwitchPlays_Everything hosted for past runs. And these were not short by any means;

36
00:03:43,850 --> 00:03:51,600
they were over 100 hours long! So it's something people would put on in the background on a separate machine or maybe a separate monitor.

37
00:03:51,600 --> 00:03:59,000
And I wrote a little chatbot for this to...uhh - for something to freshen up the pace, you know, like taking in commands,

38
00:03:59,000 --> 00:04:09,300
like IRC commands, to show the rerun schedule, which was hosted on an external wiki. Or, just like bet credits - just something for them to do.

39
00:04:10,800 --> 00:04:19,500
And then, in 2018 TwitchPlays_Everything was unfortunately slowing down. And at this point I was thinking, you know, what if it dies? I didn't want it to die;

40
00:04:19,500 --> 00:04:26,100
I loved the collaborative play. And I loved the community. And there were other problems we found with the stream.

41
00:04:26,100 --> 00:04:36,550
So, the maintainer had some code problems with their solution. It was written in Python, so over time we found that sometimes the inputs would be inaccurate,

42
00:04:36,550 --> 00:04:48,000
especially for more complex games. Multiplayer support was kinda like hardcoded in. There had to be big changes to the codebase just to add multiplayer.

43
00:04:48,000 --> 00:05:03,800
And yeah, late 2018 TwitchPlays_Everything unfortunately retired. And so I'm thinking: what now? I really loved the collaborative play. What can I do to help revive this?

44
00:05:03,800 --> 00:05:16,500
So I looked around: there are streams on Twitch and such, but everyone had their own sort of solution that was custom to their stream and it was like all proprietary and they wouldn't share it.

45
00:05:16,500 --> 00:05:29,800
So I looked at existing free software bots on code repositories. Most of them were catered towards the TwitchPlaysPokemon-style of play. So, just one input at a time - very simple - nothing that

46
00:05:29,800 --> 00:05:40,400
could play like Zelda: Ocarina of Time, for instance, in any reasonable capacity. And some of them were also just very specific to certain setups and cumbersome to use.

47
00:05:40,400 --> 00:05:51,300
So I thought, you know, I had my own little bot for the rerun schedule. Why not create my own collaborative play bot? And TwitchPlays_Everything was very nice and

48
00:05:51,300 --> 00:06:01,400
provided their Python version of the parser, and I converted that to C# for my own bot. And if you see here in the screenshot, March 4, 2019,

49
00:06:01,400 --> 00:06:11,600
was the commit added to convert the parser. And also the idea behind this was that anyone else can use their own bot-can use this bot instead of having to write their own stack,

50
00:06:11,600 --> 00:06:16,500
and overall it would just be great for expanding collaborative play.

51
00:06:16,500 --> 00:06:27,030
So, some history of TRBot's releases. So on June 6, 2019, TRBot 1.0 was released. This was a fairly barebones release.

52
00:06:27,030 --> 00:06:36,050
It had collaborative play available, but it was only available through Twitch. It had some sort of console infrastructure,

53
00:06:36,050 --> 00:06:44,250
but though all these values were hardcoded, you had to change the source code and recompile TRBot to really do much of anything.

54
00:06:44,250 --> 00:06:56,150
And, the data was stored in text files. This was slow and not very flexible. And, ultimately, this is around when I started my free software journey,

55
00:06:56,150 --> 00:07:06,230
and I didn't even realize that at this point, TRBot was not free software. So, on January 4, 2020, I added the GNU Affero General Public License version 3,

56
00:07:06,230 --> 00:07:16,550
that officially made TRBot free software. And why specifically the AGPL? So, TRBot is designed for community-oriented play, and typically this happens over a network.

57
00:07:16,550 --> 00:07:27,000
And so, adding the AGPL license would give end-users freedom if they're running on a service, say some platform like IRC.

58
00:07:27,000 --> 00:07:38,200
And also, it would help spread TRBot and knowledge of its existence, really. A lot of this goes on behind the scenes, like when you're playing on a collaborative stream.

59
00:07:38,200 --> 00:07:47,100
Like you don't really know the tech stack behind the software. So they can look at the software, they can host it themselves, and they can ultimately help improve it.

60
00:07:47,100 --> 00:07:56,000
And then in April 8, 2020, around this time I was dual-booting on my current machine. I was looking to switch over completely to GNU/Linux.

61
00:07:56,000 --> 00:08:04,000
And this is the 1.5 release, and it officially added GNU/Linux support. More on how that was achieved later.

62
00:08:04,000 --> 00:08:18,500
And then, fastforward to December 8, 2020: TRBot 2.0 was released. This was the biggest release in TRBot's history. It consists of a full rewrite, a full refactor of the entire codebase.

63
00:08:18,500 --> 00:08:31,500
It's more modular; there's a SQLite database for data instead of text files; there's a whole permissions system for hosts to help manage their streams; features like periodic inputs, and so on.

64
00:08:31,500 --> 00:08:44,350
And just some more features since version 2.0. Support for free platforms like websocket and IRC. In particular, the websocket is interesting because you can have, say, a website.

65
00:08:44,350 --> 00:08:53,780
You can display your own stream, on your website. And you can have like a chatbox that you write and connect TRBot to that chatbox,

66
00:08:53,780 --> 00:09:01,500
which I'd be hoping it goes through the websocket, and then people can play on a website. This is just one of the many use-cases for websocket support.

67
00:09:01,500 --> 00:09:12,750
Next is custom code. This one is huge. So TRBot has a concept of commands, like IRC commands, and routines, which are pieces of code that run every now and then in the background.

68
00:09:12,750 --> 00:09:25,500
And so, right now, hosts can write C# code, and TRBot can compile it on the fly and run it as if it was part of the main code by adding information to the database.

69
00:09:25,500 --> 00:09:36,050
And this allows hosts to dynamically adjust the stream. Like they can change this code, they can add/remove code. And one example of this was I

70
00:09:36,050 --> 00:09:49,500
used a custom routine to start up RetroArch, a free software emulator. And every hour, it would switch games. So that's just one use-case for this; there are infinite possibilities here.

71
00:09:49,500 --> 00:10:00,100
Next up is the event dispatcher. This is more catered towards developers of external applications. So, TRBot sends many different events while it's running.

72
00:10:00,100 --> 00:10:12,900
So say for example, someone types in an IRC channel. TRBot will receive an event and dispatch one over a WebSocket. Who said what and more information about it.

73
00:10:12,900 --> 00:10:23,950
Whenever it parses an input, it'll send an input event. For instance, this can be used to create like a GUI application to display like all the inputs going on.

74
00:10:23,950 --> 00:10:33,100
Especially useful for TRBot's democracy mode, which, instead of having players type whatever and it executes it right away, it tallies up the most popular input

75
00:10:33,100 --> 00:10:42,400
and executes that after a time period - say, 5-10 seconds. It's configurable in the database by the host. And so, the event dispatcher would allow someone to write

76
00:10:42,400 --> 00:10:48,300
a GUI application that displays all the democracy votes and the most popular democracy input for instance.

77
00:10:48,300 --> 00:10:58,000
And then continuous integration - this is not particularly relevant to the codebase itself, but thanks to Codeberg's Woodpecker CI integration,

78
00:10:58,000 --> 00:11:12,600
it's possible to run tests and compile TRBot after each commit. This helps greatly instead of me having to test things manually. I can see like readily if there's a problem and address it.

79
00:11:12,600 --> 00:11:21,500
And here's just the project structure for TRBot. There are many projects. TRBot.Build, TRBot.Commands...a lot of these are libraries,

80
00:11:21,500 --> 00:11:30,300
but some of them are applications. So for instance, TRBot.Main in the middle, which is the application that most people will be using if they download it from the repository,

81
00:11:30,300 --> 00:11:40,000
and at the bottom there's a TRBot data migration tool, which is used for 1.0 releases to migrate the data to the SQLite database in 2.0 releases.

82
00:11:40,000 --> 00:11:48,900
And what does this structure accomplish? It makes it very modular and extensible. TRBot.Main just glues together everything else to have it function.

83
00:11:48,900 --> 00:11:57,300
So it glues together the parser from TRBot.Parsing, and it glues together the data from TRBot.Data to achieve its functions.

84
00:11:57,300 --> 00:12:05,100
And instead of being a monolithic application - otherwise it'd be very difficult to make the TRBot data migration tool at the bottom there

85
00:12:05,100 --> 00:12:13,700
because that just uses the data and a few other projects here. Instead of having to import everything and kinda build on top of that -

86
00:12:13,700 --> 00:12:20,150
that would just be very inflexible and kinda awkward to work with from a code perspective.

87
00:12:20,150 --> 00:12:30,800
So, there's an animation before of me typing, but how exactly does it work? Here's the input syntax. This input syntax is mostly borrowed from the TwitchPlays_Everything

88
00:12:30,800 --> 00:12:40,100
channel's syntax but with some additions. It looks daunting, but it's actually fairly straightforward - I'll cover it one-by-one.

89
00:12:40,100 --> 00:12:50,800
So...first we have a controller port at the beginning, which starts with an ampersand followed by the number. So here, "&1" means execute this input on controller port 1.

90
00:12:50,800 --> 00:12:58,000
If you had two controllers, you can do "&2", execute it on controller port 2. And so on - this supports any number of controllers.

91
00:12:58,000 --> 00:13:08,400
Next is the modifier. There's hold and release. Hold right here is with the underscore symbol. That means hold the input until the end of the sequence,

92
00:13:08,400 --> 00:13:17,000
or until it sees a release input for the current input name. And third is the input name right here, it's currently "right".

93
00:13:17,000 --> 00:13:25,300
Umm...this could be anything, it could be "a", "b" - usually it's a description of the input, like the action in the game.

94
00:13:25,300 --> 00:13:32,200
And this is actually the only required part of the syntax, you can just put "right" - if it's configured correctly, it'll work.

95
00:13:32,200 --> 00:13:40,200
TRBot will use data from the database otherwise to fill in the other defaults. For example, the default duration is 200 milliseconds,

96
00:13:40,200 --> 00:13:46,400
the default controller port is based on the user. Next up is the analog or trigger percentage.

97
00:13:46,400 --> 00:13:52,650
This supports up to three decimal places for accuracy. Here there's "right 87.341%".

98
00:13:52,650 --> 00:13:59,800
If you imagine like an analog stick on a game controller, this means you would push the stick to the right almost all the way.

99
00:13:59,800 --> 00:14:08,500
Not quite there, but like most of the way there. This is used only for like analog or trigger inputs.

100
00:14:08,500 --> 00:14:17,500
And next is the duration, so here it says "500ms" - that means 500 milliseconds, which is half a second. This can be longer or shorter.

101
00:14:17,500 --> 00:14:21,500
This is how long to push the input for.

102
00:14:21,500 --> 00:14:34,300
After that is plus symbol, which means chain with the next input. This means this input this will be executed at the same exact time as the following input.

103
00:14:34,300 --> 00:14:40,600
If it was absent then it would be right 500 milliseconds - it would wait 500 milliseconds then execute the next input.

104
00:14:40,600 --> 00:14:45,700
But right now it's going to execute both at the same time because there's a plus. Let's move onto the next input there.

105
00:14:45,700 --> 00:14:53,000
You see there's ampersand 2, which means execute that on controller port 2. There's a minus symbol there, for the modifier, which is the release modifier.

106
00:14:53,000 --> 00:15:00,800
So this means release grab on controller port 2. And then the duration could also be specified in seconds, also with three decimal points of accuracy.

107
00:15:00,800 --> 00:15:08,600
The "3.742s" means 3742 milliseconds, so a little under 4 seconds.

108
00:15:08,600 --> 00:15:19,700
Umm, here are some examples of me playing SuperTux with TRBot. So, on the first example, you can see it's holding right, and then there's a hash symbol (#) there.

109
00:15:19,700 --> 00:15:28,700
So, by default, the hash symbol in TRBot is a blank input. What exactly does a blank input mean? That means it's an input that does nothing.

110
00:15:28,700 --> 00:15:36,400
So, it's gonna wait 300 milliseconds effectively. So it's gonna hold right, then wait 300 milliseconds, then it's gonna push "a" for 1 second.

111
00:15:36,400 --> 00:15:46,350
"a" in this game is jump. So you can see from the animation at the top, Tux is moving to the right, waiting a little bit, and then jumping.

112
00:15:46,350 --> 00:15:49,700
And then at the end, releases everything.

113
00:15:49,700 --> 00:15:56,500
And then at the bottom you can see there's a concept of macros and repetitive sequences. So...

114
00:15:56,500 --> 00:16:04,800
I'm adding a macro here called "jumplots", and the sequence pans out to "a100ms", which means push "a" for 100 milliseconds.

115
00:16:04,800 --> 00:16:11,800
And then wait 67 milliseconds. And then that's all in brackets and there's a times 10 afterwards, that means "repeat this 10 times".

116
00:16:11,800 --> 00:16:16,900
So, it's gonna basically jump 10 times. And you can see from the animation the character is jumping quite a bit.

117
00:16:16,900 --> 00:16:23,500
And since this stored in a macro, it's possible to just type "#jumplots" and it'll do this sequence.

118
00:16:23,500 --> 00:16:34,100
Macros can contain other macros - they can contain any number of sequences - as long as they fit the maximum input duration, which is specified in the database.

119
00:16:34,100 --> 00:16:43,600
And the reason for the maximum input duration is so like, sometimes players troll or if there's like a technical reason like processing too many inputs, and so on.

120
00:16:43,600 --> 00:16:51,300
And if you're curious about the 500 milliseconds at the start of this sequence - well, certain games, like SuperTux here, unfortunately do not take

121
00:16:51,300 --> 00:16:59,200
background input unless the window is focused, so here I'm just giving myself time to click on the window for TRBot does its thing.

122
00:16:59,200 --> 00:17:10,000
And expanding on macros is dynamic macros. So, I create a macro here called "press reps", and then I add parenthesis and then a star in the middle.

123
00:17:10,000 --> 00:17:20,050
So you can think of this as a function call in languages like C. Whatever I fill in there, in the star, is actually gonna replace what's in the sequence.

124
00:17:20,050 --> 00:17:24,600
So, the sequence here is a zero in angled brackets ("<0>"). That means argument 1.

125
00:17:24,600 --> 00:17:31,250
It does whatever that is for 34 milliseconds, and it waits 150 milliseconds, and then it repeats that 7 times.

126
00:17:31,250 --> 00:17:41,600
So, at the bottom, you can see I have "#pressreps(left)", which equates to left 34 milliseconds, wait 150 milliseconds, and repeat that 7 times.

127
00:17:41,600 --> 00:17:50,700
And you can see from the animation I'm using it with left and I'm also using it with down. So, with down it would just push down a bunch,

128
00:17:50,700 --> 00:17:56,800
7 times. And you can see the character moving left slightly, and then ducking a lot.

129
00:17:56,800 --> 00:18:03,000
And dynamic macros can contain other dynamic macros, it can contain other macros...it can contain any number of arguments.

130
00:18:03,000 --> 00:18:09,800
If you had, for example, press reps star comma star ("#pressreps(*,*)"), then a "1" in angled brackets would indicate

131
00:18:09,800 --> 00:18:16,000
whatever is in the second argument replaces the "1".

132
00:18:16,000 --> 00:18:22,900
And, ultimately, dynamic macros are not validated once they're added. And this is for various reasons.

133
00:18:22,900 --> 00:18:28,000
You could substitute anything. So, you can substitute the star there, or whatever else you want.

134
00:18:28,000 --> 00:18:38,300
You can substitute the "7" for the number of repetitions. And there's just no valid way to really validate everything right away once it's added.

135
00:18:38,300 --> 00:18:44,900
But there will be validation when it tries to parse the input and it finds that it's valid or invalid.

136
00:18:44,900 --> 00:18:49,350
So there are three different, three major components to how TRBot actually works.

137
00:18:49,350 --> 00:18:56,200
First is the text parser, next is the virtual controller...and then finally is like the game console concept.

138
00:18:56,200 --> 00:19:06,000
So, this one is the text parser, I'm just gonna cover it at a fairly high level. So, say someone types in an IRC channel. A message.

139
00:19:06,000 --> 00:19:17,400
TRBot will do two things. First, it will prepare the string for parsing - it will normalize the string, it'll do things like remove all whitespace or lowercase everything.

140
00:19:17,400 --> 00:19:21,800
So like "right" in all caps, will turn into a "right" in lowercase.

141
00:19:21,800 --> 00:19:29,000
Many things so that TRBot can be ready to actually parse it into data it can read.

142
00:19:29,000 --> 00:19:36,800
The second step is parsing it into input data. So say you have like "a1s", like we saw in the previous example.

143
00:19:36,800 --> 00:19:44,200
This would create a parsed input structure, internally, with the name of the input as "a" and the duration as 1000.

144
00:19:44,200 --> 00:19:49,800
Inputs are always processed in milliseconds, just for full accuracy.

145
00:19:49,800 --> 00:19:53,800
If it was "a1000ms", the duration would still be 1000, for instance.

146
00:19:53,800 --> 00:20:01,000
Basically, this puts it into data in code that TRBot can actually read and process later on.

147
00:20:01,000 --> 00:20:08,600
And then, after it's done parsing that, it will validate that data. So for instance, say you have three controllers available on your stream,

148
00:20:08,600 --> 00:20:20,100
and a player puts something on controller port four. Well, that's just not valid - there are only three controllers. So it'll return invalid for that data.

149
00:20:20,100 --> 00:20:30,200
Other things it validates include permissions. Say there's a reset button just for the host. If a user inputs the reset input,

150
00:20:30,200 --> 00:20:33,000
it'll invalidate that if they don't have permission.

151
00:20:33,000 --> 00:20:41,600
Other things include like the maximum input duration - if it's like 10 seconds and someone inputs something that goes on for 20 seconds, it'll say "no, that's too long".

152
00:20:41,600 --> 00:20:48,250
And usually it tries to spit out error messages that pertain to where the input went wrong.

153
00:20:48,250 --> 00:20:57,000
But sometimes it can't successfully do this because it uses regex for parsing. And sometimes it doesn't pick up anything at all.

154
00:20:57,000 --> 00:21:04,500
So here's the second component, the virtual controller. I touched on this in the very beginning, but here's it in more detail.

155
00:21:04,500 --> 00:21:09,500
What exactly is the virtual controller? It's a virtual device running entirely in software.

156
00:21:09,500 --> 00:21:21,800
You can see from the first screenshot on the left here, I have "jstest-gtk" open, and you see "TRBot Joypad 0" at the device "/dev/input/js0".

157
00:21:21,800 --> 00:21:30,100
It has 8 axes and 32 buttons. And then on the right you can see it in use in Maze Burrow, "TRBot Joypad 0".

158
00:21:30,100 --> 00:21:36,500
And on that screen in Maze Burrow, you can configure all the different inputs.

159
00:21:36,500 --> 00:21:44,300
So think of the virtual controller as just one running entirely in software. Like...imagine a game controller in your hand.

160
00:21:44,300 --> 00:21:53,300
You feed inputs to it by pushing buttons physically, with your hands. But the virtual controller can be fed data through code.

161
00:21:53,300 --> 00:21:59,600
Just to press and release buttons and axes. And to the computer, it's no different than the physical controller in your hand.

162
00:21:59,600 --> 00:22:10,500
And here's a high-level overview of how it works. On GNU/Linux, the virtual controllers are written in C using the libevdev library and the uinput Linux kernel module.

163
00:22:10,500 --> 00:22:16,400
uinput can be used to emulate all sorts of devices, including mice, keyboard, and what have you.

164
00:22:16,400 --> 00:22:19,200
But here I'm using it to emulate game controllers.

165
00:22:19,200 --> 00:22:28,100
And so, on the C# side there's a wrapper to this C code, and this allows TRBot to have full control over the virtual controllers.

166
00:22:28,100 --> 00:22:36,900
On GNU/Linux, aside from maybe...needing write access to /dev/uinput, there's no setup involved.

167
00:22:36,900 --> 00:22:41,400
TRBot can create all the virtual controllers right there for you. They live as long as the application is.

168
00:22:41,400 --> 00:22:50,500
And then one huge benefit of this is, say you're a host and you have a single-player game - sorry, a multiplayer game - but you have only one controller available,

169
00:22:50,500 --> 00:22:52,600
and more people join in the stream.

170
00:22:52,600 --> 00:23:04,300
You can use the internal controller count command and pass "2" as an argument and it'll get rid of that existing virtual controller and create two new ones, which can be used by your game.

171
00:23:04,300 --> 00:23:16,600
And then players can successfully play multiplayer games using this. It's just very adaptable having it on the C# side and having full control over it.

172
00:23:16,600 --> 00:23:23,000
And here is the third component to how TRBot works. Game consoles and input data.

173
00:23:23,000 --> 00:23:29,500
So, what exactly is the game console? Well, it's just a collection of inputs, essentially.

174
00:23:29,500 --> 00:23:35,200
Here is it in the SQLite database. You can see "mazeburrow" console, one for "test", various other consoles.

175
00:23:35,200 --> 00:23:37,750
And they're in the "console" table.

176
00:23:37,750 --> 00:23:46,900
And then on the inputs below: each input is associated with a certain console. On the first row, there's a "select" input. It has a name of "select".

177
00:23:46,900 --> 00:23:51,300
It has a button value of 12, it has an input type of 1, which means it's a button.

178
00:23:51,300 --> 00:23:58,850
And then at the bottom row there, there's the hash symbol, which has an input type of 0, which means it's a blank input.

179
00:23:58,850 --> 00:24:08,900
Overall, here's where all the data regarding the inputs are stored so TRBot knows how to...what to do with the virtual controller.

180
00:24:08,900 --> 00:24:12,700
And then touching on that, here's the input handler that glues all of them together.

181
00:24:12,700 --> 00:24:17,400
So, it takes the data from the parser, the text parser.

182
00:24:17,400 --> 00:24:22,600
It uses the game console data, and then it feeds all that to the virtual controller.

183
00:24:22,600 --> 00:24:31,800
And this is a very performance-critical section of code. Imagine pushing a controller on...a physical controller - pushing a button on a physical controller.

184
00:24:31,800 --> 00:24:37,600
The second you put- the instant you push that button, you would expect the game to react. So, it's the same concept here.

185
00:24:37,600 --> 00:24:47,300
There's inherently some delay passing data to the virtual controller, and so this needs to be very tight code, especially with the input.

186
00:24:47,300 --> 00:24:53,400
So let's use the example on the third bullet point there, "a1200ms b300ms+c200ms".

187
00:24:53,400 --> 00:25:03,300
So, if you're a player on someone's stream running TRBot...if you enter "a1200ms", you would expect the "a" button to be pressed for exactly 1200 milliseconds.

188
00:25:03,300 --> 00:25:07,600
And then after that, it would move onto "b" for 300 milliseconds.

189
00:25:07,600 --> 00:25:16,300
So this is why it's performance-critical. There's an expectation there - it needs to be responsive and it overall helps create a better play experience.

190
00:25:16,300 --> 00:25:25,400
And then regarding how the input handler processes it, let's use the same example. So, "a1200ms". Say "a" is a button with an input type of...

191
00:25:25,400 --> 00:25:31,900
sorry, a button value of 2. "b" is a - has a button value of 3, and "c" has a button value of 4.

192
00:25:31,900 --> 00:25:37,350
So, what it'll do is: the parser data will come back, saying oh, here's an input name of "a".

193
00:25:37,350 --> 00:25:42,800
And then it'll look up that input name in the game console data, and it'll see that it has a button value of 2.

194
00:25:42,800 --> 00:25:49,650
And so, it'll go to the virtual controller and it sees by default it's on controller port 1, if there's nothing specified.

195
00:25:49,650 --> 00:26:05,250
So it'll say, controller port 1, which is the first virtual controller device available, it'll push - it'll say, hey, set the value of button 2 to 1, which means to push it.

196
00:26:05,250 --> 00:26:07,400
To press the input.

197
00:26:07,400 --> 00:26:16,500
And then it'll wait 1200 milliseconds and say set the value of button 2 to 0, which means release it. Then it'll move onto "b" and "c", except it'll push "b" and "c" at the same exact time.

198
00:26:16,500 --> 00:26:26,000
And then you'll notice here that "b" is longer than "c". So, after 200 milliseconds, it'll release "c", and then "b" will continue going on for 100 more milliseconds.

199
00:26:26,000 --> 00:26:35,200
And then because it's a chained input, if there's anything else longer in the chain, it'll wait for that before moving on.

200
00:26:35,200 --> 00:26:42,700
And then at the end of the input sequence - I've touched on this before - everything is fully released. "a", "b", and "c" will be released no matter what, even if there's a hold.

201
00:26:42,700 --> 00:26:47,300
This is just how TRBot works; this was based off the TwitchPlays_Everything design.

202
00:26:47,300 --> 00:27:00,100
I have seen community-oriented play bots that can hold inputs indefinitely, until they're manually released. That's just not a design right now, and TRBot-uh, but it can easily be one.

203
00:27:00,100 --> 00:27:08,000
So I described how TRBot works. Now, here's some of the community-building features, and I think TRBot has a lot that intrinsically helps build communities.

204
00:27:08,000 --> 00:27:13,950
So, macros for instance. They can, by default, be added, removed, and modified by anyone.

205
00:27:13,950 --> 00:27:22,500
So say you have a 3D platformer that is being hosted, and you're a player that comes in, and there's a sideflip action the character can do.

206
00:27:22,500 --> 00:27:27,400
So, someone can create a macro for a sideflip.

207
00:27:27,400 --> 00:27:36,250
And then instead of having to manually remember - like, oh, what was the command for this - they can create a macro to do the sideflip,

208
00:27:36,250 --> 00:27:40,250
and then even a dynamic macro to do it in different directions.

209
00:27:40,250 --> 00:27:46,850
And so, say a new player joins - well, they can see all the macros available, and do things.

210
00:27:46,850 --> 00:27:53,350
And maybe they look at the macro and say, "hey, you know, I can do this better and more accurately", and they change it and modify it.

211
00:27:53,350 --> 00:27:58,500
And there's just like a community dynamic there where people are improving how they play.

212
00:27:58,500 --> 00:28:03,200
And then the syntax itself I mentioned can be very simple. It can just have the input name.

213
00:28:03,200 --> 00:28:06,950
But it can also be very complex as I described earlier.

214
00:28:06,950 --> 00:28:16,350
The syntax can be used by experts very well, but newer players can just come in and just start playing around with maybe just putting the input name, starting out.

215
00:28:16,350 --> 00:28:23,900
And then there can be some sort of teacher-student dynamic here, where if there's experienced players and newer players on a stream at the same time,

216
00:28:23,900 --> 00:28:30,800
more experienced players can help guide them. And then the newer players will learn, and they'll be more engaged and they'll be happier.

217
00:28:30,800 --> 00:28:40,500
And they can make friends and form bonds here. And overall that's just a good experience. It makes them want to continue playing and something to look forward to.

218
00:28:40,500 --> 00:28:48,800
And lastly, TRBot is free software. So, there are no restrictions on hosting it or modifying it. Anyone can adapt it to their needs.

219
00:28:48,800 --> 00:28:56,900
If there's, say, like - anyone can also host it and say there's like a stream. A host that wants to just pick it up and use it, they can.

220
00:28:56,900 --> 00:29:04,700
And if there's another one that wants to do the same, well, then if there's experienced players on let's say one stream, they can just move to another stream

221
00:29:04,700 --> 00:29:11,500
seamlessly because it has the same syntax and they can just like get right in there and help out with other games.

222
00:29:11,500 --> 00:29:16,900
So, here is some of the impact that TRBot has had to date.

223
00:29:16,900 --> 00:29:27,000
So...on December 2020, Brace Yourself Games, a prominent indie developer, was hosting their own game, Crypt of the Necrodancer,

224
00:29:27,000 --> 00:29:33,000
and they had over 100 viewers playing at the same exact time. And this was using TRBot.

225
00:29:33,000 --> 00:29:38,550
So, this is the type of game that's like called a "roguelike", so...

226
00:29:38,550 --> 00:29:46,600
if your character dies then it's permanent death, and you basically have a better chance at progressing farther the next time.

227
00:29:46,600 --> 00:29:52,800
And so, this was during the height of the COVID pandemic, so this was something that players can look forward to -

228
00:29:52,800 --> 00:29:58,000
really look forward to because they can't visit - they weren't able to visit family; most of them weren't.

229
00:29:58,000 --> 00:30:04,400
So they're stuck at home you know, something they can just play around with and look forward to.

230
00:30:04,400 --> 00:30:10,900
And I would tune in every now and then and I'd the same players come on. They'd say "Hey, did we get past so and so?",

231
00:30:10,900 --> 00:30:15,400
"How far did we get?", and sometimes be like "Wow! we got this far!" so...

232
00:30:15,400 --> 00:30:22,500
you know, something for people to look forward to, and just overall everyone had a great time with it.

233
00:30:22,500 --> 00:30:26,400
And here's the impact on a charity called Extra-Life.

234
00:30:26,400 --> 00:30:33,000
Extra-Life raises money for children in local hospitals across the United States and Canada.

235
00:30:33,000 --> 00:30:42,500
I collaborated with another stream called TwitchPlays_ZeldaGames. We had a cross-community Zelda: Ocarina of Time 100% Charity Race.

236
00:30:42,500 --> 00:30:50,750
What do I mean by that? So...even though TwitchPlays_ZeldaGames was using their own software, I was using TRBot,

237
00:30:50,750 --> 00:30:58,750
and that enabled us to basically have a race. So we both started the game at the same exact time, and then two separate streams,

238
00:30:58,750 --> 00:31:05,000
my stream and theirs, would be competing to see who can complete the game first, collecting every single item.

239
00:31:05,000 --> 00:31:14,000
And so...we had quite ambitious goals, but overall we ended up raising over $700, which was far more than we expected.

240
00:31:14,000 --> 00:31:21,300
And umm...we chose the 100% category in particular because it'd allow more time for donations

241
00:31:21,300 --> 00:31:28,200
and there are more difficult challenges involved. So, if uh...say people are having trouble and someone else donated some money,

242
00:31:28,200 --> 00:31:34,500
that would definitely motivate them to, you know, keep going and push harder, and take things further.

243
00:31:34,500 --> 00:31:38,800
Overall, it was just a really great event, and I had a lot of fun.

244
00:31:38,800 --> 00:31:40,700
Here's the impact on the speedrun community.

245
00:31:40,700 --> 00:31:49,400
So, just for those unaware, speedruns are basically completing games-involve completing games as quickly as possible, and there are whole communities around this.

246
00:31:49,400 --> 00:31:56,500
Pictured is the game Metal Gear Solid, a 1998 video game for the original PlayStation.

247
00:31:56,500 --> 00:32:03,500
This is a Twitter post, viewed on the free Nitter client, of a brand new discovery in this game.

248
00:32:03,500 --> 00:32:09,600
And it's quite old: it's fairly popular to speedrunners; they know mostly every trick in the book, but this was something brand new to them.

249
00:32:09,600 --> 00:32:17,000
And it's a single player game, and they found that the guards at the end of the game can be controlled with the second controller, which is something

250
00:32:17,000 --> 00:32:22,100
completely off-the-wall, that no one would really ever check...

251
00:32:22,100 --> 00:32:24,400
especially on a single player game such as this one.

252
00:32:24,400 --> 00:32:34,500
And so, TwitchPlaysSpeedruns, who is a prominent TRBot user, was hosting this game, and they accidentally misconfigured their inputs.

253
00:32:34,500 --> 00:32:40,100
So they had everything that TRBot would input on controller port one to also happen on controller port 2.

254
00:32:40,100 --> 00:32:46,000
And this is just like...when they were aiming around with the character, they found the guards were also moving,

255
00:32:46,000 --> 00:32:53,500
and this is just like something completely off-the-wall that would only be possible in this sort of circumstance with TRBot.

256
00:32:53,500 --> 00:32:57,400
And then here's an example of the community-building that I touched on earlier.

257
00:32:57,400 --> 00:33:03,100
So, on the leftmost screenshot, there's a user named "dragonc0".

258
00:33:03,100 --> 00:33:06,600
They say they know the system quite well and they played a lot on a different channel.

259
00:33:06,600 --> 00:33:15,900
And there are newer players here playing this game, and they're trying to complete some challenge. They're trying to...trying to beat the game, essentially.

260
00:33:15,900 --> 00:33:24,600
I believe dragonc0 came from TwitchPlaysSpeedruns, and then this is on my stream.

261
00:33:24,600 --> 00:33:28,750
Basically, you can see the teacher-student dynamic here in a way.

262
00:33:28,750 --> 00:33:35,500
dragonc0 is like asking if they can try, giving some slight advice. And overall they're having a great time.

263
00:33:35,500 --> 00:33:42,500
You know, a more experienced player is coming in, helping them learn how to play, and they're also staying more engaged -

264
00:33:42,500 --> 00:33:46,500
they're also enjoying themselves. And together, they feel they accomplished something.

265
00:33:46,500 --> 00:33:50,200
And then, this is possible because TRBot is free software.

266
00:33:50,200 --> 00:33:58,050
I was hosting it on my stream, sure, but TwitchPlaysSpeedruns can host it on their stream because there's no restrictions on using TRBot.

267
00:33:58,050 --> 00:34:06,500
And then dragonc0 is able to jump from TwitchPlaysSpeedruns to my stream because of that.

268
00:34:06,500 --> 00:34:11,300
So, future plans for TRBot.

269
00:34:11,300 --> 00:34:17,600
Right now it supports only joysticks and game controllers. I would really like to abstract it out farther

270
00:34:17,600 --> 00:34:28,600
to virtual devices beyond game controllers, such as keyboard, mouse, touchpad - ultimately allow playing more types of games.

271
00:34:28,600 --> 00:34:41,000
Right now it's possible to emulate keyboard and mouse inputs through free software such as AntiMicroX, which can remap game controller buttons to keyboard and mouse inputs.

272
00:34:41,000 --> 00:34:51,200
It works in certain cases, but it's not ideal. The input syntax is not catered to that at all, catered just to game controllers. It can be kinda clunky and suboptimal.

273
00:34:51,200 --> 00:35:03,050
Next is more support for free platforms. Matrix protocol would be excellent. Matrix is a pretty new protocol for real-time communication, and it's all decentralized.

274
00:35:03,050 --> 00:35:09,900
Having Matrix rooms, and maybe even entire Matrix servers dedicated to collaborative play would be a lot of fun.

275
00:35:09,900 --> 00:35:15,200
You can just hop back and forth between rooms and games that you enjoy.

276
00:35:15,200 --> 00:35:24,200
XMPP and BOSH support. So, this would allow running over XMPP...running TRBot over XMPP.

277
00:35:24,200 --> 00:35:34,300
Support particularly for two more platforms: Open Streaming Platform, which is a self-hosted platform for just game streaming.

278
00:35:34,300 --> 00:35:44,000
Its chat uses XMPP. And then also PeerTube chat, which is a peer-to-peer decentralized video hosting platform that has streaming support.

279
00:35:44,000 --> 00:35:50,000
They have a third-party chat extension that can be connected over XMPP/BOSH.

280
00:35:50,000 --> 00:35:59,600
And then further performance improvements, including how repetitive inputs are handled. Right now, it expands everything out to a gigantic string.

281
00:35:59,600 --> 00:36:08,700
So for instance if you had "[a100ms]*10", that would literally create a string that's "a100ms" 10 times.

282
00:36:08,700 --> 00:36:18,500
This provides quite a load on the parser, especially for longer inputs. I would like to be able to offload this to the input handler.

283
00:36:18,500 --> 00:36:26,600
It'd have the same result; maybe a marker for where the input starts-where the repetition starts and where it ends.

284
00:36:26,600 --> 00:36:35,500
This would have the same result of it repeating it a certain number of times, but the parser would finish much more quickly, and this wouldn't be a bottleneck.

285
00:36:35,500 --> 00:36:42,300
And lastly, how can you help? Well, you can host a collaborative play stream of your favorite free software game.

286
00:36:42,300 --> 00:36:46,650
This would be great for spreading free software games and also spreading collaborative play.

287
00:36:46,650 --> 00:36:50,500
And it'd just be a great way to play your favorite games in a new light.

288
00:36:50,500 --> 00:36:58,000
Code contributions; the repository is below, hosted on codeberg (https://codeberg.org/kimimaru/TRBot).

289
00:36:58,000 --> 00:37:05,000
I accept PRs, issues - if you want to change anything yourself, fork it, do what you want with it.

290
00:37:05,000 --> 00:37:11,000
And then creating packages for GNU/Linux distros, maybe even ports for BSD.

291
00:37:11,000 --> 00:37:21,300
I believe the latest version, TRBot 2.5.1, added support for a system-wide install, so it can be used over apt...you can create an apt package of it, for instance.

292
00:37:21,300 --> 00:37:29,200
And then spreading the word. I mentioned earlier: collaborative play can be very fun, and it can be a unique way, a unique take, on your favorite games.

293
00:37:29,200 --> 00:37:36,500
I recommend everyone to try it, especially in like the closer, community-oriented ways of play.

294
00:37:36,500 --> 00:37:44,100
Maybe something more hectic like TwitchPlaysPokemon may not be your style, but feel free to try it regardless.

295
00:37:44,100 --> 00:37:46,800
And...thank you! I have time for Q & A.

296
00:37:53,500 --> 00:38:03,100
Thank you Thomas for the excellent talk. It was very cool. It's an excellent project, super cool, with I think a lot of possibilities.

297
00:38:03,100 --> 00:38:14,800
And especially like things like adding IRC support and having automated tests with CI to increase the overall quality of the codebase,

298
00:38:14,800 --> 00:38:18,500
adding support for GNU/Linux of course.

299
00:38:18,500 --> 00:38:26,200
And also happy to hear - or see - that you went with the AGPLv3 strong copyleft license,

300
00:38:26,200 --> 00:38:32,200
and I also very much appreciated the community-building aspects and impacts of it - I think that's super cool.

301
00:38:32,200 --> 00:38:32,900
Thank you.

302
00:38:33,700 --> 00:38:34,800
Cheers, thank you!

303
00:38:35,300 --> 00:38:49,000
So, with that, we are opening the floor to questions. So, folks if you do have questions, please paste them on IRC. Ideally, tag my nick, "bandeli", there, so I could relay them.

304
00:38:52,800 --> 00:39:00,400
Okay, I see we have one question already by Michael, which I'll paste here as well.

305
00:39:00,400 --> 00:39:10,000
But Michael is asking: "Are there other uses of TRBot beyond collaborative play? Could there be a machine learning application of this software of some sort?"

306
00:39:12,100 --> 00:39:17,400
Yeah, so there are two questions here, so I'll address the first one: "Are there other uses of TRBot beyond collaborative play"?

307
00:39:17,400 --> 00:39:26,600
You could use it for your own uses. So, for instance, if you have to like mash a button in a video game, you can set up TRBot and then mash super quickly,

308
00:39:26,600 --> 00:39:29,000
more quickly than you could, and just automate that.

309
00:39:29,000 --> 00:39:36,800
You could also automate other things like grinding in an RPG. It might take some effort to create macros for that, but that's possible.

310
00:39:36,800 --> 00:39:42,500
Could there be machine learning applications of the software? Admittingly, I don't have too much knowledge on machine learning, but

311
00:39:42,500 --> 00:39:50,800
I think that it could be possible to use TRBot to play games entirely through machine learning.

312
00:39:50,800 --> 00:39:55,200
It has all the capabilities of playing the games, so now you need to teach it how to do that.

313
00:39:59,100 --> 00:40:00,300
Very cool!

314
00:40:02,000 --> 00:40:08,000
Umm, so I guess while we wait for more questions from the audience, I do have a question of my own.

315
00:40:08,000 --> 00:40:16,100
So if I understand correctly, TRBot uses a DSL with a custom parser, right?

316
00:40:16,200 --> 00:40:19,600
Yeah, the parser is all custom using regular expressions.

317
00:40:20,150 --> 00:40:25,800
Right, cool. Yeah, so I guess my main question I was wondering...

318
00:40:25,800 --> 00:40:34,600
so while this language - input language - seems very terse and efficient and purposely designed I guess for TRBot.

319
00:40:34,600 --> 00:40:42,000
And for this kind of use, I was wondering, for example, if you considered maybe supporting some kind of an API with

320
00:40:42,000 --> 00:40:52,300
an actual like complete programming language perhaps, like Scheme or Lua, that I think tend to lend themselves nicely to, I guess, scripting or embedded use-case.

321
00:40:53,650 --> 00:41:02,200
No, I haven't considered that, and admittingly I'm not very familiar with Scheme or Lua, but that sounds like an excellent idea. It's definitely something to consider.

322
00:41:03,200 --> 00:41:13,200
Awesome, cool. Yeah, I think it would be an interesting addition to be able to fully, like, script and automate things with a complete programming language.

323
00:41:13,200 --> 00:41:17,000
It could add an interesting - or open a bunch of interesting avenues I guess.

324
00:41:17,000 --> 00:41:18,800
Mmhmm, yeah definitely.

325
00:41:22,400 --> 00:41:30,800
Umm, let's see...Alex asks: "What are some popular channels that use TRBot?"

326
00:41:33,200 --> 00:41:37,900
Popular channels...so, I don't keep tabs on every channel that uses it.

327
00:41:37,900 --> 00:41:43,500
There are...there's Brace Yourself Games, which hosted the Crypt of the NecroDancer run.

328
00:41:43,500 --> 00:41:52,400
TwitchPlaysSpeedruns has been growing. They use TRBot for many games. They've been more recently using it for, like, more modern games.

329
00:41:53,500 --> 00:42:00,400
There was a popular Russian streamer called "flashKO", I think there's a one in there - "f1ashKO" -

330
00:42:00,400 --> 00:42:05,700
has used it for like little breaks every now and then on the stream and played some older games.

331
00:42:05,700 --> 00:42:11,500
Aside from that, I am not very aware of all the streams using it, especially popular ones.

332
00:42:11,500 --> 00:42:20,700
It's really hard to get into streaming, you know, like there are so many channels and so on, but like not too many big players there.

333
00:42:24,300 --> 00:42:25,000
Right.

334
00:42:35,000 --> 00:42:42,100
Okay, so we have about two and a half more minutes for questions, so folks, if you do have more questions, feel free to keep them coming.

335
00:42:48,000 --> 00:42:59,150
So I guess a question of my own. I think you might've mentioned this in the beginning and I missed it. What is the implementation language of TRBot? Is it C#, or...?

336
00:43:00,100 --> 00:43:06,800
Oh yes, TRBot is written in C# using the .NET 5 SDK, both of which are free software under the MIT license.

337
00:43:07,700 --> 00:43:10,000
Okay, gotcha - cool!

338
00:43:14,500 --> 00:43:17,500
Umm, oh yes, I see one question here that I missed.

339
00:43:17,500 --> 00:43:25,900
Afdahl asks: "Is PeerTube plugin live chat the 3rd party extension that you were referencing?"

340
00:43:27,000 --> 00:43:31,700
Uhh, yes, that's the one. I believe that is the XMPP/BOSH.

341
00:43:36,400 --> 00:43:37,400
Awesome!

342
00:43:52,200 --> 00:43:55,400
I see another question, also from Afdahl:

343
00:43:55,400 --> 00:44:07,000
"How would you compare non-text direct control, especially mouse pointer control, in this, to controlling games with remote desktop control systems?"

344
00:44:07,100 --> 00:44:14,300
Non-text direct control with the mouse pointer...controlling games with...direct desktop control?

345
00:44:14,300 --> 00:44:19,700
Umm, I guess I'm not sure what it means by "direct desktop control systems"? So...

346
00:44:19,700 --> 00:44:23,300
I guess I'm not sure what that means - what "direct desktop control systems" are.

347
00:44:25,600 --> 00:44:32,000
Okay, no worries. Yeah, if the question isn't completely clear, perhaps Afdahl could clarify further.

348
00:44:32,000 --> 00:44:36,700
And I think we're approaching the end of our Q & A time period in like 20 seconds.

349
00:44:37,100 --> 00:44:42,000
Umm...so I guess we can go ahead and slowly wrap up.

350
00:44:42,000 --> 00:44:49,300
And, yeah, it'd be great, for example, if you could hang out on IRC after your talk, and perhaps, take some more questions there.

351
00:44:49,950 --> 00:44:51,200
Yeah, definitely!

352
00:44:52,100 --> 00:44:54,500
Okay, great. Thanks again, Thomas, for the great talk!

353
00:44:55,500 --> 00:44:57,800
Thank you Amin, it was a pleasure. Take care!

354
00:44:58,500 --> 00:45:02,000
* Music *

355
00:44:58,500 --> 00:44:59,000
Thank you, you too!

356
00:44:59,500 --> 00:45:00,000
Bye!

357
00:45:00,000 --> 00:45:00,999
Bye - see you around!