Creating a new 'Who' Command

Setup

Make certain you've followed all the instructions to get an Evennia sandbox up and running, first of all. Once you know the sandbox is working, we'll begin editing.

Now we'll log into our Evennia game as our administrator self. If you type who, you'll see that Evennia already has a command for this. The output will be something like:

Accounts:
+--------------+--------+------+-----------+-------+------+----------+-----------+
| Account Name | On for | Idle | Puppeting | Room  | Cmds | Protocol | Host      |
+~~~~~~~~~~~~~~+~~~~~~~~+~~~~~~+~~~~~~~~~~~+~~~~~~~+~~~~~~+~~~~~~~~~~+~~~~~~~~~~~+
| Pax          | 00:00  | 2s   | Pax       | Limbo | 1    | telnet   | 127.0.0.1 |
+--------------+--------+------+-----------+-------+------+----------+-----------+
One unique account logged in.

This is because we're an administrator; if you type doing you'll see what a player sees. (If you were a player, the two commands would do the same thing.) For a player, it looks something like this:

Accounts:
+--------------+--------+------+
| Account name | On for | Idle |
+~~~~~~~~~~~~~~+~~~~~~~~+~~~~~~+
| Pax          | 00:00  | 25s  |
+--------------+--------+------+
One unique account logged in.

Now let's look at how we can replace this command with something of our own design that adds a 'doing' column like on MUSH's default WHO command.

A Bit About Evennia Commands

The first thing to know about Evennia commands is that they are themselves classes. These classes are added to a Command Set. These command sets 'stack'.

Each command set also has a 'priority'; this determines in what order they stack. If I have a command set with a priority of 2 and one with a priority of -20 both active and both have a foo command, when I type foo it will take it from the set with priority 2.

What command sets you have available to you depends entirely on the situation you're in; there's one set of commands for sessions that haven't logged in yet, which is available at the login screen. You have the default commands available to a player. But a room might have a high-priority command set on it which becomes available to players in that room; perhaps the room is pitch black, so you override the look command in that room to simply return "You can't see anything."

Stacking command sets is a powerful tool, and it allows us to override built-in Evennia commands without having to modify the base server.

There are different subclasses of the Evennia default command class which provide different parsers. The command class we're going to be using is MuxCommand, which uses a TinyMUX-style parser. If we type, for instance, foo/bar baz=blah then the parser will separate that into a command (foo), a series of one or more switches (bar), a left-hand side (baz) and a right-hand side (blah) which it will make available to our code. This is the command class that all of Evennia's default commands use anyway, and it should work well for our use case.

Creating the Command

Making a File

Let's start by creating a nice file to contain our commands. Since this is going to have commands that override built-in system commands, let's call it overrides.py. Technically we could put this file anywhere, but since there's already a commands directory containing things dealing with custom commands for our server, it makes sense to put it there.

Defining the Command

Now, let's put some content into that file:

from evennia.commands.default.muxcommand import MuxCommand


class CmdWho(MuxCommand):
    """
    who

    This command shows who is presently online.
    """

    key = "who"
    aliases = ["doing", ]
    locks = "cmd:all()"

    account_caller = True

    def func(self):
        self.msg("This is the who command")
       

Okay, let's break this down.

First, we're making certain that MuxCommand is in scope for our file, or else we can't refer to it.

Next, we define a class called CmdWho. We could call it anything we want, really, but by convention Evennia command classes usually have names beginning with Cmd. Our class inherits from the MuxCommand class we imported, so we pick up all of its functionality.

Next, you'll notice there's a multi-line comment at the top of the class definition. In Python, this provides an blurb for automatic documentation; Evennia uses this to generate the help entry for your command. Generally we'll want to write something more detailed than this, but for now this is sufficient.

Now for the meat of things. As noted earlier, every command is a class encapsulating its functionality; when a command is executed, several things happen.

  1. The at_pre_cmd() method is called to check whether the command should be blocked. If it returns True, the command aborts.
  2. The parse() method is called, to break the line that was entered apart into its useful component pieces.
  3. The func() method is called, to do the actual work of the command.
  4. The at_post_cmd() method is called, to do any last cleanup.

In addition, there are several fields which are important in the command: key is a string which is used as the command name, aliases is an array of strings which are other names for the command, and locks is an Evennia permission string.

MuxCommand already provides an implementation of parse() for us, as well as empty at_pre_cmd() and at_post_cmd() methods, so we don't need to implement those. We do, however, obviously need to add the fields and the func method!

In this case, we're setting up the command to be called who and adding a single alias of doing.

Understanding lock strings is a little beyond the scope of this first command; in this case, just know that they're in the format permission:function(params), and that the cmd permission is what's necessary for someone to be able to use the command. There are various built-in lock functions and you can add new ones, but in this case the built-in all() function is sufficient since everyone should be able to use the command. Thus cmd:all() is a string that says everyone gets the cmd permission for that lock.

There's also a special case in MuxCommand for commands which we know might be called from an account (rather than a character), so we'll set account_caller to true.

Now on to our func method. Right now, we're going to do something very basic; in this case, there's a convenient msg method on our parent class, which will send text to whatever called the command. So we're just going to send a single line of text, our own equivalent of "Hello, world."

However, we're not quite done! The command isn't in any command set, so no one can use it.

Adding to the Default Command Set

At any given time, there's at least one command set (and often more) available to a user.

The moment you connect, you have the SessionCmdSet. Then while you're at the login screen, the contents of the UnloggedinCmdSet are also added to that which provides commands like connect to users. Once you log in, you have AccountCmdSet which might contain account-specific commands like changing your password. Then there's CharacterCmdSet, once you have a body on the game grid. (Evennia, remember, has a separate concept of "accounts" and "characters"; one account could have multiple characters.)

You can always add more command sets for other scenarios, though that's beyond the scope of this. Right now, let's add this to the AccountCmdSet

In this same directory, let's open default_cmdsets.py, which contains the definitions of Evennia's default command sets. Look for the AccountCmdSet definition:

class AccountCmdSet(default_cmds.AccountCmdSet):
    """
    This is the cmdset available to the Account at all times. It is
    combined with the `CharacterCmdSet` when the Account puppets a
    Character. It holds game-account-specific commands, channel
    commands, etc.
    """
    key = "DefaultAccount"

    def at_cmdset_creation(self):
        """
        Populates the cmdset
        """
        super(AccountCmdSet, self).at_cmdset_creation()
        #
        # any commands you add below will overload the default ones.
        #
 

As it says at the bottom, we want to add our own command to the command set to override the existing one. To do that, we need to add the following line:

        self.add(overrides.CmdWho())

We're creating an instance of that class and adding it to the command set. Since it's in the overrides file, we refer to it by that name.

However, like with MuxCommand in our overrides file, we need to make sure we've imported this. Go back up to the top of the file, and you'll see an existing line importing default_cmds from the Evennia library. Add another line below that of simply:

import overrides

Rather than importing a specific set of classes or commands, we're going to just import the entire overrides file so that, as we add new commands, they're already in scope for us.

Now we're ready!

Testing Your Command

Start your Evennia copy back up. If the game is already running, either type @reload as a character with Developer permissions on-game in Evennia (such as the God character you created when you first started) or, with your Evennia python environment active, type evennia reload.

When you type who you'll see:

This is the who command

Not terribly exciting, but we've created the beginnings of the command!

Making It Actually Do Something

The Session Handler and Sessions

Let's go back to overrides.py and make this command a little more interesting. We're going to import an global instance of a special Evennia class called the session handler, which keeps track of all connected sessions. In order to do that, let's go back up to the top of the file and add an import to get SESSIONS from the evennia.server.sessionhandler package.

Then let's change the body of our command function like so:

    def func(self):
        session_list = SESSIONS.get_sessions()

        self.msg("Sessions: {}".format(session_list))
        

Reload the server, and try who again. You'll see something like

Sessions: [<evennia.server.serversession.ServerSession object at 0x104e5d950>]

If you type doing, you'll see the same output, because we defined doing as an alias of who.

Now, we can see a nice list of all the connected sessions—in this case, one—but that's not a terribly useful format. But each of those sessions is an instance of evennia.server.serversession.ServerSession, as you can see in that list. This is not a class you'll generally interact with except in the special case of the SESSIONS variable you can import to get all connected sessions.

If you open the evennia package and look at evennia/server/serversession.py for the ServerSession class, you'll see there's a ton of functionality there. What's relevant to us, however, is that ServerSession has a method called get_account(), which returns the logged-in account for that session if there is one.

Account is one of the major Evennia classes you can interact with. It's a Django model; this means it's stored in the database, can have attributes, and has a primary key. In this case, the primary key will be the name.

Let's change our command's body once again:

    def func(self):
        session_list = SESSIONS.get_sessions()
        account_names = [sess.get_account().key for sess in session_list]

        self.msg("Accounts: {}".format(account_names))

The line that transforms the session list into a list of account names is fairly standard Python. It simply means "this array contains the account field's key field for each instance of an object in session_list". (Learning all aspects of Python is a bit beyond the scope of this lesson, but I'll try to cover things where I can.)

Now if you restart and run who again, you'll see it prints an list of unicode strings, one account name for each logged-in session.

Now let's make this more useful.

A Real Who Command

Okay, now let's alter our overrides.py. We'll make use of several Evennia utilities (crop and time_format), the default Python time library, and the Evennia EvTable class which can easily format tables for display.

Here's our new overrides.py:

from evennia.commands.default.muxcommand import MuxCommand
from evennia.server.sessionhandler import SESSIONS
from evennia.utils import utils, evtable
import time


class CmdWho(MuxCommand):
    """
    who

    This command shows who is presently online.
    """

    key = "who"
    aliases = ["doing", ]
    locks = "cmd:all()"

    account_caller = True

    def func(self):
        # Get the list of sessions
        session_list = SESSIONS.get_sessions()

        # Sort the list by the time connected
        #
        # The 'key' parameter is a reference to a function that takes one
        # argument and returns a value that the list should be sorted by.
        # In this case, there's no convenient function to reference, so
        # we'll define what's called a lambda function: an anonymous inline
        # function.  In this case, given a single 'sess' argument, it just
        # returns the conn_time field from sess.
        #
        # To read more on lambas, check out:
        # https://www.programiz.com/python-programming/anonymous-function
        #
        session_list = sorted(session_list, key=lambda sess: sess.conn_time)

        # Create an instance of our Evennia table helper class with four
        # columns.
        table = evtable.EvTable("Account Name", "On For", "Idle", "Doing")

        # Iterate across the sessions
        for session in session_list:

            # If this session isn't logged in -- i.e. is at the login screen
            # or something -- just skip it.
            if not session.logged_in:
                continue

            # How long has it been since their last command?
            #
            # time.time() returns the current UNIX time -- in the same format
            # as MUSH softcode's secs() function -- while ServerSession's
            # cmd_last_visible field is the timestamp of when we saw the last
            # command.  We'll store the difference between the two.
            #
            delta_cmd = time.time() - session.cmd_last_visible

            # How long has this session been connected?
            #
            # Once again, we'll store the difference between right now
            # and the time they first connected.
            #
            delta_conn = time.time() - session.conn_time

            # Let's store the account just for easy reference, so we don't
            # have to do get_account() everywhere.  Saves on typing.
            account = session.get_account()

            # An account's 'key' is the name of the account.  There's no
            # reason to store this in a string, really, instead of using
            # account.key when we wanted to reference it, but this lets
            # me comment on it.
            account_name = account.key

            # The 'db' field on Evennia Accounts, Players, and Objects contains
            # a special object that contains all the attributes you've set on-game
            # using the @set command.  Think of it like the attributes on objects in
            # MUSH.
            #
            # If the 'who_doing' attribute on the account isn't empty,
            # let's store it in a new variable called doing_string, otherwise
            # let's store an empty string.
            #
            # The format we're using here is called a 'ternary conditional operator',
            # and you can read a bit more about it at:
            # https://www.pythoncentral.io/one-line-if-statement-in-python-ternary-conditional-operator/
            #
            doing_string = account.db.who_doing if account.db.who_doing else ""

            # Now we have all our data gathered!  Let's add a row to the table for
            # this session, using a few Evennia utilities to crop strings and format
            # times.
            #
            # crop takes a string and a maximum length, to crop it to.
            #
            # time_format takes a value in seconds, and a style; 0 is hours:minutes,
            # 1 is a natural language string like '50m' or '2h'.
            #
            table.add_row(utils.crop(account_name, 25),
                          utils.time_format(delta_conn, 0),
                          utils.time_format(delta_cmd, 1),
                          utils.crop(doing_string, 35))

        # Send the table to our user.
        self.msg(table)

 

This is a bit longer, but rather than do it step-by-step I added comments to the source showing everything explaining what it does.

Most of it is pretty straightforward. crop() takes a string and a length and ensures the string isn't longer than that length. time_format() takes a number of seconds and a style; style '1' will show a human-readable bit of text like '50m' while style '0' will show it in hours:minutes. EvTable takes a list of columns when you initialize it, and then every add_row() call takes one parameter for each column; in addition, EvTable has a convenience function where it will output the table anywhere you try to use the table instance as a string; as a result, we just pass the table to self.msg() and it treats it like a string.

The only non-obvious part is the lambda function in our sorted() call. Lambdas are a way of encapsulating a function; in this case, we're sorting the list of sessions and telling it the sort key should be generated by the lambda function we provided. In this case, the lambda says that given an object sess, return sess.conn_time. When it sorts the list, that function will be executed for each element, and the result used as the sort key. The upshot of this is that we get a our original list of session objects re-sorted by how long the person has been connected.

If you reload again and type who now, you'll get something like:

+--------------+--------+------+-------+
| Account Name | On For | Idle | Doing |
+~~~~~~~~~~~~~~+~~~~~~~~+~~~~~~+~~~~~~~+
| Pax          | 00:00  | 8s   |       |
+--------------+--------+------+-------+

This looks more like a real WHO command! And now, let me do:

@set *Pax/who_doing=Writing a tutorial!

Do who again, and we get:

+--------------+--------+------+---------------------+
| Account Name | On For | Idle | Doing               |
+~~~~~~~~~~~~~~+~~~~~~~~+~~~~~~+~~~~~~~~~~~~~~~~~~~~~+
| Pax          | 00:00  | 1s   | Writing a tutorial! |
+--------------+--------+------+---------------------+

Next, let's make it so we can pass a parameter.

Working With Inner Functions

Python is what as known as a functional programming language; this means every function is itself an object. You can assign functions to variables and call them directly, and you can also define functions within a function.

This is useful when you have a block of code you want to reuse multiple times -- a function, clearly -- but you don't really need that code anywhere but the function you're writing. This means we can work with inner functions.

Let's write an inner function to return a filtered list of Evennia sessions. Right above where you have session_list = SESSIONS.get_sessions(), add this function. Make certain it's indented one level more than the func function is, so that it's contained within func!

def get_sessions(prefix=None):
    """
    Given an optional prefix for account names, return a list of currently connected sessions.
    This is just a convenience function since we might use it several places.

    :param prefix: A prefix that the account name must start with to be included in the list.
    :return:
    """

    # Get the raw list of sessions from Evennia
    sessions = SESSIONS.get_sessions()

    # If our prefix is None -- Python's equivalent of NULL or nil -- then
    # it counts as false.
    if prefix:
        sessions = \
            filter(lambda sess: sess.get_account().key.startswith(
                prefix) if sess.get_account() is not None else False,
                   sessions)

    # We have our session list, so return it to the user.
    return sessions
 

Most of this is pretty straightforward. We have a single parameter, prefix, which we were passed, and which defaults to None if it isn't provided. And you remember SESSIONS.get_sessions() from before; we're getting our list of sessions.

filter is a method that takes a list and an optional function to call as the second parameter; if you don't provide the function, then filter() will just return a list with any values that were None stripped out. In this case, we're providing a lambda again.

This lambda looks pretty awful at first glance (and really, it's not the most readable), but it's just a ternary comparison operation like we already used in our who command. In effect, we're just saying "given sess, if the result of sess.get_account() is not None, then our value is sess.get_account().key.startswith(prefix), otherwise our value is False.

We already remember that get_account() returns an Account, and key on that Account is the account's name. The name being a string, we can use the string function startswith to check if the string begins with another string; in this case, we're checking it against prefix. If get_account() returned None for some reason, though—a session not being logged in, for instance—then we'd be trying to call key on a None value, and our program might crash. Hence our ternary comparison operator.

Now that we've got this function, we can change our main CmdWho so that, instead of

session_list = SESSIONS.get_sessions()

we now use

session_list = get_sessions(self.args)

That args value on our MuxCommand is everything we passed to the command. Let's say that we entered "foo/test bar=baz"

So, to get back to our WHO command, if I entered "WHO P", then args would be "P". If I entered just "WHO", then args will be None. You'll recognize these are precisely the sort of values we coded our get_sessions() to handle.

Now our CmdWho should look something like this, with all the comments from earlier stripped out.

    def func(self):

        def get_sessions(prefix=None):
            """
            Given an optional prefix for account names, return a list of currently connected sessions.
            This is just a convenience function since we might use it several places.

            :param prefix: A prefix that the account name must start with to be included in the list.
            :return:
            """

            sessions = SESSIONS.get_sessions()

            if prefix:
                sessions = \
                    filter(lambda sess: sess.get_account().key.startswith(
                        prefix) if sess.get_account() is not None else False,
                           sessions)

            return sessions

        session_list = get_sessions(self.args)

        session_list = sorted(session_list, key=lambda sess: sess.conn_time)

        table = evtable.EvTable("Account Name", "On For", "Idle", "Doing")

        for session in session_list:

            # If this session isn't logged in -- i.e. is at the login screen
            # or something -- just skip it.
            if not session.logged_in:
                continue

            delta_cmd = time.time() - session.cmd_last_visible
            delta_conn = time.time() - session.conn_time
            account = session.get_account()
            account_name = account.key
            doing_string = account.db.who_doing if account.db.who_doing else ""

            table.add_row(utils.crop(account_name, 25),
                          utils.time_format(delta_conn, 0),
                          utils.time_format(delta_cmd, 1),
                          utils.crop(doing_string, 35))

        self.msg(table)

You may notice that we're only calling get_sessions once, so there's really no value to making it a function of its own. However, it was useful to demonstrate inner functions, which can be incredibly useful at times.

Fleshing It Out

Working with Account Permissions

If we really want to replicate the MUSH "who" command (or for that matter, replace the Evennia one with something that has roughly the same capabilities), we need to have the version that a staff member gets show more information. But how do we do that?

Well, let's add this right above our session iterator:

        if self.cmdstring == "doing":
            show_admin_data = False
        else:
            show_admin_data= self.account.check_permstring("Developer") or self.account.check_permstring("Admins")

We already covered cmdstring earlier; all this does is make sure that if we used the doing command instead of who, the 'showadmindata' value is False. Otherwise, we check if the Account of the person running the command has either the "Developer" or "Admins" permissions. (You can easily define new permissions in your own code, such as making a "Storyteller" permission or something else, but these are two that come stock with Evennia.)

If the account has either of those permissions, we're going to show the staff version of this command.

So, one of the next things we'll want to do is make that table have a few more columns. Replace our table definition with:

        if show_admin_data:
            # Create an instance of our Evennia table helper class with six
            # columns.
            table = evtable.EvTable("Account Name", "On For", "Idle", "Location", "Client", "Address")
        else:
            # Create an instance of our Evennia table helper class with four
            # columns.

This is pretty good, but now we need to actually populate those columns. Let's go down into the session iterator and replace our add_row call:

            # If the session has a puppet (an associated Character), get that.
            # Accounts are never on-grid, but Characters are, so we'll need this
            # to get the location column value.
            character = session.get_puppet()

            if show_admin_data:
                table.add_row(utils.crop(account_name, 25),
                              utils.time_format(delta_conn, 0),
                              utils.time_format(delta_conn, 1),
                              "#{}".format(character.location.id) if character else "",
                              "Web Client" if session.protocol_key == "websocket" else session.protocol_flags['CLIENTNAME'],
                              session.address[0] if isinstance(session.address, tuple) else session.address)
            else:
                table.add_row(utils.crop(account_name, 25),
                              utils.time_format(delta_conn, 0),
                              utils.time_format(delta_cmd, 1),
                              utils.crop(doing_string, 35))

You can see the second half of that block is the same add_row call we had before, in case the admin data isn't being shown. Above, we have a different add_row call. The first three columns are the same, but the last three ones are new. Let's take them one at a time.

"#{}".format(character.location.id) if character else ""

Anything which descends from Object (which includes Character) has a unique object id. This is, effectively, a dbref, and in fact you can use it as such with a number of Evennia builder commands. Your God character is #1, the Limbo room you logged into is #2.

In this case, we're just using another ternary conditional operation to check if character isn't None, and if it isn't, we take the character's location, and that location's id, and format it with a # so it looks like a dbref. If there's no character, we use an empty string.

The format function on a string is very useful, and can be looked up in Python documentation, but the short form is that you can provide any number of {} blocks in a string, and the same number of parameters to format, and those {} will be replaced by the parameters. In this case, we replace {} with the ID number of the room the character is in -- thus, it looks like a familiar dbref.

"Web Client" if session.protocol_key == "websocket" else session.protocol_flags['CLIENTNAME']

The ServerSession class has a protocol_key field which will be "websocket" or "telnet" or "ssh". If we're a websocket from the built-in webclient, we just use "Web Client" for this column. Otherwise, we pull the 'CLIENTNAME' value from the session's protocol_flags. These are all the various bits of goo that the client negotiated during connection; client name, what the window size is, and so on. In most cases you never need to use these, but in this case we want to get the client name.

session.address[0] if isinstance(session.address, tuple) else session.address

Lastly, we have the session address. Unfortunately, this can be either a single address or, in some cases, a collection of several of addresses. So we check if it's an instance of tuple (basically a list or array), and if so we get the first element. Otherwise, we just show the session address.

Reload your server, and try typing who again.

+--------------+--------+------+----------+----------+-----------+
| Account Name | On For | Idle | Location | Client   | Address   |
+~~~~~~~~~~~~~~+~~~~~~~~+~~~~~~+~~~~~~~~~~+~~~~~~~~~~+~~~~~~~~~~~+
| Pax          | 00:00  | 7s   | #2       | ATLANTIS | 127.0.0.1 |
+--------------+--------+------+----------+----------+-----------+

Since I logged in using Atlantis, you can see it in my client column. If I type doing, however, I still get:

+--------------+--------+------+---------------------+
| Account Name | On For | Idle | Doing               |
+~~~~~~~~~~~~~~+~~~~~~~~+~~~~~~+~~~~~~~~~~~~~~~~~~~~~+
| Pax          | 00:00  | 1s   | Writing a tutorial! |
+--------------+--------+------+---------------------+

But we still don't have a way for anyone who isn't a builder (i.e. has access to the @set command on the game) to set their Doing field. Let's change that.

Command Switches

Let's make a switch called 'set', so that I can do doing/set <whatever> to set a new value, or doing/set alone to clear it.

Back in your code, go to just above where we set session_list and add the following:

        if self.cmdstring == "doing" and "set" in self.switches:
            if self.args == "":
                self.msg("Doing field cleared.")
                self.account.db.who_doing = None
            else:
                self.msg("Doing field set: {}".format(self.args))
                self.account.db.who_doing = self.args

            return

This is pretty straightforward. We already know about cmdstring from before, and switches is just an array of any switches provided. So if the command was "doing" and "set" is in the collection of switches, we'll call this code.

We also know about args from our work with filtering on a prefix, so that's easy enough. And we also know about the db field on Account (and anything descended from Object). And the account field on any command is the Account of the command caller. Therefore, we just need to set or clear who_doing on self.account's db field.

We also return a message, because it's only polite.

But there's one last step!

Write Your Helpfile

Python has automatic documentation commenting, where you can use blocks enclosed by """ to generate documentation for a class or function. Evennia uses the class documentation for a given command class as the helpfile entry for that command. So, let's go back up right under CmdWho, where we have a really basic helpfile we wrote:

    """
    who

    This command shows who is presently online.
    """

That's not a very good helpfile. We've added a bunch more functionality! Let's rewrite it:

    """
    who [prefix]
    doing [prefix]
    doing/set [pithy quote]

    This command shows who is presently online.  If a prefix is given, it will 
    only show the accounts whose names start with that prefix.  For admins, if 
    you wish to see the player-side list, type 'doing'.  For all players, if 
    you'd like to set a pithy quote to show up in the last column, use 
    'doing/set'.  If you don't provide a value to doing/set, it will clear yours.
    """

Save the file, reload your server, and type "help who":

------------------------------------------------------------------------------
Help for who (aliases: doing)

who [prefix]
doing [prefix]
doing/set [pithy quote]

This command shows who is presently online.  If a prefix is given, it will
only show the accounts whose names start with that prefix.  For admins, if
you wish to see the player-side list, type 'doing'.  For all players, if
you'd like to set a pithy quote to show up in the last column, use
'doing/set'.  If you don't provide a value to doing/set, it will clear yours.

Suggested: @cwho
------------------------------------------------------------------------------

Now we're done!

Reference

If you need a full copy of the overrides.py file either with or without comments, you can find it on the website.

Hopefully this was helpful!