Skip to content

Project Architecture Overview

Garrett Luskey edited this page Jul 4, 2023 · 3 revisions

All Projects

In Bannerlord Coop exists 4 projects
Projects

Module Communication

Our mod uses event driven design, meaning we heavily use the pub-sub pattern to create a reactive system. To do this we use a Message Broker
MessageBroker

The message broker can be used to send messages between domains (Client/Server/Common/etc.) and even between services.

There are 3 different types of messages

  • Command (Changes the state of the game or mod)
  • Response (Response to a command, i.e. Success, Failure, or some other relevant information)
  • Event (Something has happened without a command)

Network Communication

To communicate over the network we use a the same messages as above. To send a message over the network, we use the INetwork interface.

Using the network object, you can send any message over the network (that is serializable using protobuf). An example.

The Service Structure

Every domain (Client/Server/Common/etc.) will have a set of services. A service a collection of functionality for a given piece of the mod. For example, Kingdoms will have it's own service. In the Kingdom service, some of the main separation of functionality includes Handlers, Messages. Handlers Handlers manage sending and receiving messages, both over the network and internally (through the message broker).

Coop

The Coop project is the entry point for the mod. The functionality here will be minimal and likely you will not touch this.
Coop

Coop.Core

The Coop.Core project is responsible for networking between the server/clients, controlling mod functionality, and dependency injection management. CoopCore

Coop.Core and GameInterface are separate is to allow automated test of most of the mod without having to start the game.
SystemTests

Dependency Injection

This project also makes use of dependency injection (DI). DI is the inversion of control (IoC) concept where class instances are automatically passed to the constructor of dependent instances.

An example

// How DI setup looks with AutoFac
ContainerBuilder builder = new ContainerBuilder();
// Implementations can be easily swapped out, this makes ExampleClient much easier to write tests for
// as you can swap out BattleManager with a BattleManagerStub while testing
builder.RegisterType<BattleManager>().As<IBattleManager>();

class ExampleClient
{
    private readonly IBattleManager _battleManager;

    public ExampleClient(IBattleManager battleManager)
    {
        _battleManager = battleManager;
    }

    public void StartNetworkBattle()
    {
        // Tell all clients to start a battle
        // ... some more functionality
        _battleManager.StartBattle();
        // ...
    }
}

interface IBattleManager
{
    void StartBattle();
}

class BattleManager : IBattleManager
{
    public void StartBattle()
    {
        // Start Battle logic
    }
}

Battles

The Battles project is responsible for facilitating a single battle instance.

Common

The Common project is for shared utilities between projects. As an example the MessageBroker lives here

GameInterface

The GameInterface project is responsible for separating the game from the rest of the mod, interfacing with the game, controlling game specific functionality, and managing Harmony patches.
GameInterface

This project should be as small as possible to allow for more testing on the Coop.Core side. The service structure is slightly different here.
For the Kingdoms example

  • Kingdom Service
    • Messages
    • Handlers
    • Interfaces
    • Patches

Harmony

Harmony is used to add and remove functionality from the game. You can utilize harmony to add functionality to the beginning or end of a method you are unable to change (i.e. any game method). You are also able to entirely skip existing methods, but be careful if that method is returning something. It is also possible to change anything inside the method but this is done through a transpiler and is fairly complex.

See more about Harmony