Login Object Pt. 1
Big commit today. I’ve gotten users to their initial login state which is a milestone. Tomorrow I’ll continue to work on actually getting them into the game, which I don’t expect to finish before the weekend’s out. Here’s the breakdown:
The most noticeable change is a shift from the traditional “Enter name here” input_to() chain to a plain angle bracket prompt, with a collection of commands the user can enter to engage with the game from the login object: login, register, quit, and if I have time, guest. Also maybe help, though I’m conflicted on that one for reasons that are tough to explain. I’ve always wanted help to be something you had to unlock, for instance by visiting a library, or by engaging with someone has visited one. I’m also opposed to it for philosophical reasons; I feel like you shouldn’t ever have to ask for help at that stage of the game, the code should be robust enough to sense when you need help and provide it. Once you’re in the game, though, then you’re on your own. If you need help, get a map and find the nearest library. And don’t get killed on the way.
As for the move from a login prompt to a login command, it just made sense for a variety of reasons. First, it’s more representative of how you’re going to engage with the game most of the time, and you don’t get a second chance to make a first impression. By the same token, it’s scary to anyone who’s never used a CLI before, which is most everybody. In the end, though, most people are going to use the GUI, and people will only have to remember a basic set of commands depending on how they play. The GUI will compose the commands for you in many cases, in which case it’s much easier to have a complicated (to humans) array of command options than it is to run through a bunch of plain English input prompts. We can still come up with a framework for that kind of interface, but the way I figure it, most people using the CLI are power users who won’t be intimidated by complex command syntax, and the others using the GUI can avoid it most of the time. We’ll also be showing them every command they send as its sent, so they can learn the CLI while using the web forms.
Let’s step back though. Last time I checked in I’d gotten the master loading and the login object either did or didn’t load, but beyond that, nothing. First it needs to display the welcome screen, then it needs to prompt the user, now with just the default “> “ prompt. I also did a little bit of work with how the commands get added to the user’s login object, too. There’s actually quite a bit of thinkings that have to go into that. For the GUI, I decided a while ago that it’d probably best to just send a stream of JSON and let the GUI sort it out after that. There might still be some actual message strings to be echoed to the console in the message payload, but there could be also sorts of other stuff too, like parameterized format strings or message properties. The goal here is to let the browser be in charge of much rendering as possible, since it’s generally better and faster. Just package up all the data points it’s going to need, render them to JSON strings and shoot ‘em up the pipe. Of course we can also take that same payload, and send it to a text-based renderer on the server, then display the message as an ANSI string to telnet users. So we get the best of both worlds.
However, with both these options in play, before you can show the welcome screen you have to figure out which renderer you’re going to use, and for that, you need telnet negotiations. Fortunately, Iffy already figured out all this stuff on EotL ages ago so I could borrow most of what I needed from there. All the communication parts of it stayed exactly the same, but instead of putting my handlers in the player body I put them in what I named the ConnectionTracker. With the gabbo architecture you could be bouncing around a lot between avatar objects as you move between flavors, and we don’t want to have to renegotiate every time that happens. So now this service object assigns each unique connection a connection id and maintains a mapping of connection details, such as the current avatar, the time the user connected or last switched avatars, terminal type, and screen dimensions.
Because I moved the telnet negotiation code out of THISP, I had to create a simul_efun to allow the ConnectionTracker to send a binary message to THISP. There’s not a lot to talk about here, except that soon I’m going to have to make some decisions about what should and shouldn’t be a simul_efun. What I have now is overloaded efuns, and functions with privileged operations. I also have this ever-growing library of miscellany called ObjectLib, but I hate it. I also found out that structs need to go in libraries because they can only be shared between programs by inheritance. They also ignore class boundaries so you can’t have two inherited programs that define structs with the same name. Still not 100% on using structs at all since anything GUI-bound is getting serialized to JSON, but I’m gonna try them out for a few small things and see how it goes.
Once you figure out the term type, the next step is sending the message. Gone are the days of tell_object(str), all messages are now routed through the PostalService, which is the only object in the game privileged to send a message to the client (until FedEx comes along or something). There’s a MessageLib with some wrapper functions you can inherit to make things easier, but eventually you’ll be calling:
<pre>PostalService->send_message(object sensor, string topic, mapping msgdata, object sender);</pre>
The sensor is an object inheriting the SensorMixin, which provides three functions:
<pre>int prevent_message(string topic, mapping msgdata, object sender);
struct Message render_message(string topic, mapping msgdata, object sender);
void message_signal(struct Message msg);</pre>
First, prevent_message() is called, which returns a flag indicating whether the message should be prevented. Then render_message() is called, which returns a Message struct that currently contains only one member: message. At that point, PostalService will actually output the rendered message to the client, and finish the process by calling message_signal() with the message it ended up printing. (I’m leaving the room here for additional rendering to be done by the PostalService after SensorMixin rendering has completed. This may be necessary for things like censors.)
Every message must be accompanied by a topic; right now the topic for the welcome message is “system.login” but the organization of topics is probably something that will be revisited often. The other argument, msgdata, is pretty open ended. This is all the stuff you need to render the event to the screen, which is dependent on the renderers themselves. Certain message content may be incompatible with certain renderers, such as an image definition. The msgdata for the welcome screen contains the text of the /.etc/issue file, as well as properties for whether or not the connection is secure or the terminal type was successfully detected.
The sender argument is the object sending the message. Right now it’s optional and defaults to 0, which would be used for messages originating from an unknown source such as the driver, but since it’s not actually a driver-applied lfun, there’s always some source, even if it’s just previous_object(). I’m also not sure if the sender should be the command or the command giver. The command giver needs to be passed along regardless, for rendering things like feelings, but is that the same thing as the sender? Not sure about that, and there are also security concerns.
It’s hopefully a simple enough API; the devil will be in the renderers, and molding msgdata without it getting out of control. Just a few stray things I didn’t mention. I did a little refactoring since the login object needs to be a command giver and it needs all the telnet negotiation stuff. The platform directory now has a .modules subdir with four members: CommandModule, CommandGiverMixin, SensorMixin, and the new AvatarMixin, which abstracts the ConnectionTracker info as well as exporting the forthcoming commands. Speaking of which, the capabilities API and the command giver API have been retooled to make use of the fact that variable_list() will return values of variables scoped by inheritance. This means you can inherit CarrierMixin and MobileMixin and each can initialize a commonly named global variable the value of which will be queried individually to find out which commands should be imported by each of the included mixins. Of course I have no idea of it works yet, that’ll be the first thing I do tomorrow when I move onto the commands.