OpenMU

This project aims to create an easy to use, extendable and customizable server for a MMORPG called "MU Online".

View on GitHub

Documentation

This directory should contain all the (technical) documentation of the OpenMU project, including packets descriptions, game mechanics and software architecture.

Why not using the wiki?

We think that a lot of the documentation (especially packet descriptions) is based on the actual code. So it makes sense that all the (technical) documentation should be available within the code, so there is no point in using the wiki.

However, documentation like getting the project run (like a users manual) could be put on the wiki, of course.

Game Server Architecture

Disclaimer (by sven-n): This is not a perfect architecture, if such architecture exists anyway. However, it makes sense to me for this purpose, as an enterprise business application developer. I tried to make it flexible and I hope it’s not too complicated.

For the big picture, you may have a look at the architecture overview.

There are interfaces for the interoperability between the different “servers” or sub-systems in MUnique.OpenMU.Interfaces.

Communication between game client and server

The network communication between game client and the game server takes place through the Connection class. MUnique.OpenMU.Network contains all what’s required to connect from and to a game server, using the MU Online network protocol. It also contains the message structs of the messages in MUnique.OpenMU.Network.Packets.

Client -> Server

When receiving data from the game client, it gets forwarded to the packet handlers located at namespace MUnique.OpenMU.GameServer.MessageHandler. Every handler is an implementation of a IPacketHandlerPlugIn. These message handlers are parsing the data packets and then calls the player actions located at MUnique.OpenMU.GameLogic.PlayerActions, which should have no knowledge of the packet structure or how the communication took place.

Server -> Client

The other way - data sent to the game game client - is done by views (MUnique.OpenMU.GameServer.RemoteView). These views are using the Connection class to send the data in the specified protocol. The GameLogic has no knowledge about this protocol and just works with the view interface plugins.

Benefits of this architecture

As you can see, the GameLogic itself does not know how the player actions are triggered or how the “view” look like. Instead of working with the network, there could be an implementation of view plugins which is literally a graphical user interface. Also instead of calling the player actions by packet handler plugins, a user interface could call them instead. So this project could be a base for a (non-MU) game client which then could also support multiplayer and co-op with the existing server components.

All plugins are configurable over the AdminPanel. They can be activated or deactivated, so that they can be replaced by extended or modified versions. It’s also possible to offer different protocols to work on the same game world, by implementing multiple view and packet handlers with different client versions attributes. Each game server can have multiple tcp listeners which can be bound to separate tcp ports for different client versions, too.

Data access

The access pattern is mainly this:

Design goal

It should be possible to support multiple different databases, also NoSQL ones, without changing the game logic. For this purpose we don’t just use SQL and database-specific code in the game logic. The access patterns should keep this in mind. So instead of a lot single calls to a lot of different repositories in the game logic, one big call should be done. For example, if we want to use a document based database, an account could be one document and the game configuration as a whole could also be one document.

Abstractions

To accomplish the design goals, the game logic (and other parts) are using abstractions, Repositories, to access data. These abstractions are located at the MUnique.OpenMU.Persistence namespace.

We use a context-based approach to access data, e.g. the GameConfiguration is loaded through a GameConfigurationRepository while “using” a context, and each connected player uses its own player context to load its Account. When saving an account, we actually save the it’s context. The context then takes care that every required change is done at the database. When accessing or creating new persistent objects, the context needs to be “in use” on the current thread, because the actual context implementation may need to track these objects. It takes care of a lot of things, e.g. creating new objects. Contexts can be created with the PersistenceContextProvider.

Current implementation and supported database

At the moment the persistence layer is implemented by MUnique.OpenMU.Persistence.EntityFramework which uses the Entity Framework Core and PostgreSQL as database.

Future

Because the data model is pretty complicated (which is required if the configuration should be that flexible), a full relational model on a database is probably not the best thing to do (performance wise). Currently, we use an approach to load the game configuration or account with just one big dynamically generated query which gives us the data as json. That’s suprisingly fast, as the query itself to load the really complex game configuration finishes in about 1.5 seconds.

If there might be a problem in the future, we could go further and mix relational tables with json columns or to fully switch to a document based database (e.g. RavenDB).

Further information