-
Notifications
You must be signed in to change notification settings - Fork 52
Project Architecture Overview
In Bannerlord Coop exists 4 projects
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
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)
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.
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).
The Coop project is the entry point for the mod. The functionality here will be minimal and likely you will not touch this.
The Coop.Core project is responsible for networking between the server/clients, controlling mod functionality, and dependency injection management.
Coop.Core and GameInterface are separate is to allow automated test of most of the mod without having to start the game.
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
}
}
The Battles project is responsible for facilitating a single battle instance.
The Common project is for shared utilities between projects. As an example the MessageBroker lives here
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.
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 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.