The MUD is THE OG of the MMO world. Created by two University of Essex attendees it defined the very idea of an online multiplayer game. One would think, that in a world where this concept has evolved into a billion-dollar industry with titans such as World of Warcraft, EVE Online, and Elder Scrolls Online, the age of the MUD is long passed. Nothing could be further from the truth.

MUDs still hold strong as a niche game, and most avid players would rather play a MUD than any modern MMO. Many feel there is a richness that the imagination can provide with text based online games that you don’t get with the “big box” products.

I am one of those. While I am not one who has played MUDs since the dawn of the internets, I was introduced to the style of game in the early nineties, and it still remains one of my favorite game genera over nearly thirty years later.

With nearly two decades of software engineering and architecture experience behind me, including having worked on several AAA game projects, I think its time that I scratch this particular itch.

If you want to build your own MUD NOW then it doesn’t make sense to create your own engine. With many amazing modern frameworks to choose from, (Ranvier, Evennia, or Kalevala to name a few). Most actively managed and developed by members of the excellent Mud Coders Guild.

Choosing a Language

For my language I am using GoLang. There are a few reasons for this. For one it is alanguage that I enjoy working with, and for another it doesn’t have a strong representation within the MUD Coders Guild. It also, as a compiled language is a lot faster than an interpreted language. Finally, it has an in built ability to handle concurrency that is easy to use and easy to understand.

After a few false starts over the years, I want to build a MUD engine from scratch that is pluggable, fast, and easy (for me) to use.

Getting Started, Building a Simple Plugin Framework

I want to build a Mud Engine that supports the loading and execution of plugins. Before creating a server, or any other related code, lets start with a simple plugin framework. To start we have three basic requirements:

  • It must support executing plugin code when the overall game is loaded.
  • It must support some sort of hook mechanism that allows plugins to execute code based on the inclusion of other plugins.
  • When either of these hooks are triggered, the plugin should have the opportunity interact with the game state.

Considering the above, we probably need to start with some sort of basic game state.

The Game State

We’re not looking for anything complex here, so let’s just create a struct that can be configured with a list of plugins to load.

package engine

type State struct {
	// Plugins is an `array` of Plugins to be loaded by the engine.
	Plugins []Plugin
}

func NewState(plugins []Plugin) *State {
    return &State{
		Plugins: plugins,
    }
}

The Plugin Interface

Now that the game state is defined, on to the plugin interface. This will the interface that will need to be implemented by any game plugins.

package engine

// Plugin is the interface that must be implemented by a plugin.
type Plugin interface {
  // Name returns the name of the plugin.
  Name() string

  // Init initializes the plugin when the game starts.
  Init(state *GameState) error

  // PluginInitialized  is called when another plugin has been initialized.
  PluginInitialized(name string, state *GameState) error
}

The Plugin Loader

We need a way to load the plugins. This will be the final piece to our plugin framework. The loader will loop through our list of plugins and call the Init method on each one, passing in the game state.

package engine

// LoadPlugins loops through the list of plugins and calls the Init method on each one.
func LoadPlugins(state *State) error {
	for _, plugin := range state.Plugins {
        if err := plugin.Init(&state); err != nil {
        return err
        }
    }
	
    return nil
}

Putting It All Together and Writing Tests

Now to write some tests for our plugin framework to ensure it works as expected.

package engine

import "testing"

type testPlugin struct{}

var initCalled = false

func (p *testPlugin) Name() string {
	return "test"
}

func (p *testPlugin) Init(state *State) error {
	initCalled = true

	return nil
}

func TestLoadPlugins(t *testing.T) {
	state := newState([]Plugin{&testPlugin{}})

	if err := LoadPlugins(state); err != nil {
		t.Error("Expected LoadPlugins to return nil error")
	}

	if !initCalled {
		t.Error("Expected Init to be called")
	}
}

And our test run:

=== RUN   TestLoadPlugins
--- PASS: TestLoadPlugins (0.00s)
PASS

Process finished with the exit code 0

Great! Now we have a simple plugin framework that can be used to build a MUD engine. Next we’ll start building out the TCP server that will handle incoming connections.

The actual code for this nascent MUD engine can be found at https://github.com/mjolnir-mud/engine.

Next Post In This Series