URL: https://www.github.com/ethanmoffat/EndlessClient

Background

EndlessClient is an open-source client for an old MMO, Endless Online. While the original developer has been actively working on the game in the past year, the original game went offline around 2012 and hadn’t received updates for about 4 years prior to that. An initiative to create an open-source server emulator has been in development since 2008/2009, and has been running a “clone” of the original game server for the past few years. Other popular private servers have been developed based off this server emulator software.

Project Goals

EndlessClient was developed as a complement to the eoserv software powering the open-source server emulator for the original game. The goals of EndlessClient are to:

  1. Reach feature parity with the original closed-source client
  2. Fix usability and performance bugs in the original closed-source client
  3. Provide a base platform that can be easily extended by server owners that want custom client functionality

EndlessClient should be a drop-in replacement for the original client that supports the original assets with no additional setup.

Architectural Patterns

MVC

EndlessClient relies on a typical MVC-based architecture. Data models are immutable types and use a builder object for mutations. A repository pattern is used for game state. Views are written in C# code using a custom control library. Controllers are the top-level interface, called by event handlers in the view code.

Dependencies and structure

Dependency Injection is used to provide dependencies of each type. The game exists as a set of “control sets”, with each control set representing a different state of the game. Each of the pre-game menus (account create, login, etc.) has its own state. The in-game state is one of said states, which adds input listeners for handling movement and attack actions.

Networking

As it is an MMO, the network stack is an important part of the game. Endless Online relies on a TCP stream for all game data. This project’s goal is to emulate the network protocol, so no changes to the protocol were made.

The operation receiving network traffic happens on a background thread and places received packets into a queue that is then polled by the main game thread.

Handling of packets happens in the same thread as the main game loop, so handlers generally update the game state without triggering any changes in controls. Most controls will check game state on each iteration of the game loop and update themselves when changes are detected. Certain actions (such as request dialogs) require a “notification” from the network layer to trigger something in the UI, but these are an excception and not the rule.

Sending data happens in the same thread as the game loop. When a “send” operation is called, the data is immediately written to the TCP stream.

EOBot Project

A bot project was developed for automated testing of high-level operations on the game server (create account, log in, create character, etc.)

This bot project originally used hard-coded C# that would execute library functions as if the bot were a real client. However, an extension was developed to support a domain-specific bot language. This involved writing a tokenizer and parser for a custom language grammar.

An example script follows. The syntax uses some elements from PHP as well as Javascript.

// Note: this test requires the environment to have ThreadPoolThreads = LoginQueueSize = 4
//
$version = 28

$expectedConcurrentLogins = 4

$account_name = "botacc" + $botindex
$account_pass = "BotP@ssw0rd" + $botindex

$LOGIN_OK = 3
$LOGIN_BUSY = 6

print ("SERVER=" + $host + ":" + $port + " Logging in " + $account_name)
sleep(10 * $botindex)

$loginRes = Login($account_name, $account_pass)
if ($botindex >= $expectedConcurrentLogins)
{
    if ($loginRes != $LOGIN_BUSY)
        error("Expected busy login for bot " + $botindex + " but was " + $loginRes)
    else
        print("Got expected busy response for bot " + $botindex)
}
else
{
    if ($loginRes != $LOGIN_OK)
        error("Expected ok login for bot " + $botindex + " but was " + $loginRes)
    else
        print("Got expected OK login for bot " + $botindex)
}

Note that functions are all predefined, as well as the variables $botindex, $host, and $port