NOTE: I need to revise these docs, I haven't checked them for veracity in a but, and I'm in the middle of rework of the system. Stay tuned for details
Right now, writing mods with the Empyrion API is really difficult, so I created a framework that takes a way a lot of the plumbing, and replaces it with an API that uses the C# type system to help you tp write mods.
The base mod API has two critical concepts, requests and events.
Events are messages that the server sends to the mod through the Game_Event method. Each method call comes with 3 arguments:
void ModInterface.Game_Event(CmdId eventId, ushort seqNr, object data)
Let's look at the "Playfield Load" event. This if fired every time that a new player moves into a previously unloaded playfield. (a list of CmdIds can be found here: https://empyrion.gamepedia.com/Game_API_CmdId#Request_Blueprint_Resources)
CmdId eventId
is the "type" of the event, in this case the value is, "Event_Playfield_Loaded"
ushort seqNr
will be 0 in this case, and for all events that aren't fired in response to requests (more on that later)
object data
will be a PlayfieldLoad
object, that you can inspect ot find out information about the event.
Because of the nature of the API, using enum CmdId means that you don't know the actual type of the data
until you've received the CmdId, this means that the type system can't be used to reason about what you receive, meaning that you can't use intellisense, or typechacking to verify that your code will work ahead of time. This mod takes all that away by offering native C# event management for game generated events. Where before to interpret a chat event you would have to write a lot of infrastructure, for example:
void ModInterface.Game_Event(CmdId eventId, ushort seqNr, object data)
{
if(eventId == CmdId.Event_ChatMessage)
{
var chatInfo = (ChatInfo)data;
if (chatInfo.msg.Contains("hello"))
{
//do something
}
}
}
With this repo, you can you use a SimpleMod
and it looks like this:
this.Event_ChatMessage += (chatInfo) =>
{
//do something with chatInfo
};
if you wanted to make it a little easier to read, you could also do this:
this.Event_ChatMessage += ExampleMod_Event_ChatMessage;
private void ExampleMod_Event_ChatMessage(ChatInfo chatInfo)
{
//do something with chatInfo
}
Which is a lot easier to work with.
The other key concept is Requests, which are sent to the game using the API. When you submit a request to the server, you use do it useing ModGameAPI
's Game_Request
method, whose prototype looks like this:
bool Game_Request(CmdId reqId, ushort seqNr, object data)
Notice how it looks like the prototype for Game_Event
? When you submit a request to the server, you do it with a CmdId
, that describes the type of request that you're submitting, along with an appropriately typed object
that will act as a parmeter, and a seqNr
that acts as an identifier for your request. When the server is finished processing your request, it will fire a Game_Event
with the response to your request, but identify it with the appropriate seqNr
. Let's look at an example
Let's say we want to get the PlayerInfo
for the player with EntityId 23. Using the old method, it would look something like this:
dediAPI.Game_Request(CmdId.Request_Player_Info, 31, new Id(23))
void ModInterface.Game_Event(CmdId eventId, ushort seqNr, object data)
{
if(eventId == CmdId.Event_Player_Info && seqNr == 31)
{
var info = (PlayerInfo)data;
//do something with info for player 23
}
}
That might not look too bad, but it's pretty hard to mantain as you scale up the number of requests. Using a SimpleMod
, you can accomplish the same thing by using a call like this:
this.Request_Player_Info(new Id(23), (info) =>
{
//do something with info
});
If you've worked with the API before, you might be wondering, "how do I track the errors that might be returned as part of my request?" Please note that all request methods accept an optional errorHandler
method, that will be fired if an error is returned for the specified request along with it's assoociated ErrorInfo
. For Example:
this.Request_Player_Info(new Id(-230), (info) =>
{
//do somehthing with info
}, (err) =>
{
// do something with err
});
In principle, using the framework is very simple, and it has 4 steps:
- Create a new .NET 4.7 Class Library
- Add references to dlls
- Implement a class that inherits from
SimpleMod
- Bundle using ILMerge
- Deploy to server
There are two DLLs that you'll need to get started, they are:
EmpyrionAPITools\build\EmpyrionAPITools\EmpyrionAPITools.dll
EmpyrionAPITools\build\EmpyrionAPITools\EmpyrionAPIDefinitions.dll
Make sure that you add references to both of them in your new project. Make sure that in the properties menu for each reference, "Copy Local" is set to true.
Note: In the future these might be compressed to a single dll, but for now there will be two
Create a new class that inherits from SimpleMod
. It will require you to implement an Initialize
method, which is where you'll initialize your mod. For Example:
using Eleon.Modding;
using EmpyrionAPITools;
namespace ExampleMod
{
class Example : SimpleMod
{
public override void Initialize(ModGameAPI dediAPI)
{
this.Event_Player_Connected += Example_Event_Player_Connected;
}
private void Example_Event_Player_Connected(Id obj)
{
var msg = new IdMsgPrio()
{
msg = $"player: {obj.id} has connected. Welcome!"
};
this.Request_InGameMessage_AllPlayers(msg);
}
}
}
Note that all this mod does is listen for Plyer_Connected
events, and when they are received, it fires a message to all clients on the server.
In order to deploy this to your server, you'll need a single DLL artifact that contains all of the dependencies. THe easiest way to do this, that I've found is to use the ilmerge msbuild task (found on nuget as MSBuild.ILMerge.Task or here https://archive.codeplex.com/?p=ilmergemsbuild ).
In order to use it, just install the plugin using nuget, and your build artifact will be bundled appropriately.
This will produce a single DLL, that you can copy into a folder on your server in the Content/Mods
folder. For example
Content/Mods
Content/Mods/Example
Content/Mods/Example/Example.dll
Note that you must restart the server in order for the mod to be loaded.
The ChatCommands
API is a way of describing functions that can be invoked with text entry to the chat box (using the "." in-game).
The basic usage looks like this:
this.ChatCommands.Add(new ChatCommand(@"loudly (?<yellthis>.+)", (data, args) => {
var msg = new IdMsgPrio()
{
id = data.playerId,
msg = $"{args["yellthis"].ToUpper()}!!!!!"
};
this.Request_InGameMessage_SinglePlayer(msg);
}));
In this example, there is a call made in the Initialize
function of our mod that is adding to the ChatCommands
collection, inherited from SimpleMod
The constructor for ChatCommand
takes two arguments, a regex pattern as a string. In this case, the pattern is, "@"loudly (?<yellthis>.+)""
which, matches phrases like, "loudly he went to the store". Note that in this case, "loudly " would be matched by the concrete part of the pattern, whereas "he went to the store" would be captured as part of the named capture group, "yellthis".
Next we specify a handler for the ChatCommand, which is a function that returns void
, and accepts two arguments, a ChatInfo
object from the Eleon.Modding
namespace and a Dictionary<String, String>
. The ChatInfo
for the the chat message event is passed in this case as the variable data
, The dictionary, args
will contain all of the named capture groups. In this case, the dictionary will have only 1 key value pair: "yellthis": "he went to the store"
Oh god no! If you have a better way I'd be thrilled to integrate it.
This isn't very well documented at the moment, but if it starts seeing traction, I'll document it more throughly. For now feel free to leave questions in the issue tracker.
Feel free to leave feedback, or submit pull requests. I'm doing this so that we get more mods for Empyrion, so anything I can do to help enable you, I'd love to know about.
Have Fun!