LibrePlanet: Conference/2022/Transcripts/Empowering-community-oriented-play-with-TRBot
From LibrePlanet
< LibrePlanet:Conference | 2022 | Transcripts
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!