-
-
Notifications
You must be signed in to change notification settings - Fork 39
Home
Welcome to the wiki of HKMP! This wiki will mostly contain information regarding the HKMP API.
The core idea of the API is to expose internal workings of HKMP to so-called "addon". This will allow developers to create integrations of existing mods with HKMP or create new specific features that are not necessarily fit to be included in the HKMP codebase. Addons are divided in two parts, namely "client addons" and "server addons" and are similar to Hollow Knight mods in that they are assemblies that are loaded and can interact with another codebase. In this case, the client and server addons are loaded separately by the client-side and server-side of HKMP and can then interact with their respective APIs (namely, the client API and server API). This is important to keep in mind, because it means that you can be either developing for the client-side, the server-side of both sides of HKMP in terms of addons. The reason for having this divide between client and server addons is basically due to the existence of the standalone server. The standalone server has no notion of the game code, because it runs independently of Hollow Knight, but it may still be interesting to have a server addon that solely interacts with the server API. Addons that require networking are allowed a channel over which they can communicate with their respective counterpart using the existing HKMP network implementation. This means that in order to use networking in your addons, you will need to develop both a client and a server addon. These addons can then communicate with each other to relay important information.
The first thing to do when trying to develop an addon for HKMP is to create a new C# class library. The framework you should be targeting is .NETFramework v4.7.2
, similar to Hollow Knight mods. Next, you should add the reference to the HKMP DLL (HKMP.dll
) in your project. The latest DLL can be found on the releases page. It is also recommended to put the HKMP.xml
file (which contains the documentation) next to the HKMP.dll
. This will allow you to see the documentation of all HKMP API members and methods in your IDE.
Then, we can start writing code. In order for HKMP to recognise your class library as an HKMP addon, you'll need to extend either the ClientAddon
or the ServerAddon
class. See the code snippets below for an example:
using Hkmp.Api.Client;
namespace ExampleAddon {
public class ExampleClientAddon : ClientAddon {
public override void Initialize(IClientApi clientApi) {
Logger.Info("Initializing client-side example addon!");
}
protected override string Name => "ExampleAddon";
protected override string Version => "0.0.1";
public override bool NeedsNetwork => true;
}
}
using Hkmp.Api.Server;
namespace ExampleAddon {
public class ExampleServerAddon : ServerAddon {
public override void Initialize(IServerApi serverApi) {
Logger.Info("Initializing server-side example addon!");
}
protected override string Name => "ExampleAddon";
protected override string Version => "0.0.1";
public override bool NeedsNetwork => true;
}
}
These classes are the starting point of your addon and will allow you to access the respective API. The client and server APIs are passed as a parameter through the Initialize
method, but they are also accessible as a protected
member variable in the ClientAddon
and ServerAddon
classes.
Once you have created either a client addon or a server addon class, there are two ways you can have HKMP recognise your addon. HKMP will try to load all available assemblies (.dll
files) in the Mods/HKMP
directory and find classes that extend either ClientAddon
or ServerAddon
and construct and initialize them. Otherwise, if you are making an integration with an existing mod that is not located in the Mods/HKMP
directory, you can construct the client or server addon class yourself and pass the instance to HKMP via ClientAddon.RegisterAddon(clientAddon)
or ServerAddon.RegisterAddon(serverAddon)
. This should happen during your mods initialization, otherwise these methods will throw an exception.
The APIs offer the addons a way to communicate with their respective counterpart through the existing HKMP networking. This is discussed in more detail and with examples below.
To be able to use HKMP networking, you will need to develop both a client and a server addon. These addons will be communicating with each other by sending data to each other and registering handlers for receiving data.
First of all, make sure that the Name
and Version
properties in both classes that extend ClientAddon
and ServerAddon
match. Also, make sure that NeedsNetwork
is true
, otherwise you will not be able to access the networking classes needed.
Let's start with sending data (which is bit more straightforward than receiving). Using the client API (IClientApi
) you can obtain a network sender (IClientAddonNetworkSender
). To do this however, you need to provide an enum type that contains all possible packet ID values. For example, consider the following:
// Enum for client to server communication
public enum ServerPacketId {
PacketId1,
PacketId2
}
This enum type contains two values: PacketId1
and PacketId2
, which can be used to identify the type of data we are sending to the server. Now we can obtain a network sender as follows:
// The client addon instance
ClientAddon clientAddon = ...;
// The client API
IClientApi clientApi = ...;
var netSender = clientApi.NetClient.GetNetworkSender<ServerPacketId>(clientAddon);
Using this network sender we can start sending data to the server (if we are connected of course). The data we want to send should be a class that extends IPacketData
:
public class ServerPacketData : IPacketData {
// Denote whether this data should be handled as reliable
public bool IsReliable => true;
// Whether to drop data if a newer version of the data is also included in the packet
public bool DropReliableDataIfNewerExists => true;
// The data we are transmitting
public float SomeFloat { get; set; }
public void WriteData(IPacket packet) {
packet.Write(SomeFloat);
}
public void ReadData(IPacket packet) {
SomeFloat = packet.ReadFloat();
}
}
This class contains two methods for writing and reading the data to and from the packet. It also contains some information on reliability. If we mark our data as reliable then the data will be resent if the packet is lost during transit. This should only be used for data that is necessary to be received by the other end.
Using the network sender based on the enum type and the packet data class, we can finally send data to the server. However, there is still another distinction to make on how we want to send our data: single or collection data. Single data means that only a single instance of the packet data class will be included in the packet. If we try to send another packet data instance as single data when the packet was not sent yet, it will be overridden. This can be useful if newer instances are strict successors of old data, but if not we can make use of collection data. Collection data is useful if it should be possible to include multiple instances of packet data in one packet. The following code snippet gives an example of both types:
IClientAddonNetworkSender<ServerPacketId> netSender = ...;
// Send single data using the given packet ID
netSender.SendSingleData(ServerPacketId.PacketId1, new ServerPacketData {
SomeFloat = 3.141592f
});
// Send multiple instances of data in a collection using another packet ID
for (var someFloat = 1f; someFloat <= 5f; someFloat += 1f) {
netSender.SendCollectionData(ServerPacketId.PacketId2, new ServerPacketData {
SomeFloat = someFloat;
});
}
Now that we know how to send addon data over the network, we need to do something with that data once it is received on the other end. Similarly to addon data sending, we need to obtain a network receiver through which we can register handlers for the different packet IDs we have. Differently to the network sender, we need to inform the network receiver on how to instantiate our packet data classes when they arrive. This should be a function as follows:
private IPacketData InstantiatePacket(ServerPacketId packetId) {
switch (packetId) {
case ServerPacketId.PacketId1:
return new ServerPacketData();
case ServerPacketId.PacketId2:
return new PacketDataCollection<ServerPacketData>();
}
return null;
}
In our running example, we only have a single class for packet data (ServerPacketData
), so for all cases of packet IDs we will instantiate that class. However, there is a caveat: since we are sending the ServerPacketData
class as collection data with the ID PacketId2
, we need to also instantiate the packet on the receiving side as such. Note that we therefore instantiate a PacketDataCollection<ServerPacketData>
class instead. Now, using this function we can obtain our network receiver:
// The server addon instance
ServerAddon serverAddon = ...;
// The server API
IServerApi serverApi = ...;
var netReceiver = serverApi.NetServer.GetNetworkReceiver<ServerPacketId>(serverAddon, InstantiatePacket);
The network receiver allows us to register packet handlers for any of the packet ID enum values. If we want to handle the ServerPacketData
on the packet ID PacketId1
for example, we can do the following:
IServerAddonNetworkReceiver<ServerPacketId> netReceiver = ...;
netReceiver.RegisterPacketHandler<ServerPacketData>(ServerPacketId.PacketId1, (id, packetData) => {
var someFloat = packetData.SomeFloat;
// Do something with this data
});
Note that the type parameter for the method is the same as the type of the packet data is for the given packet ID. We know that on the given packet ID (PacketId1
) our client addon is always sending ServerPacketData
classes, therefore, this is the type we use when registering the packet handler. Similarly for ID PacketId2
, where we instantiate a PacketDataCollection<ServerPacketData>
class, we still want to register the packet handler for the type ServerPacketData
as the collection will automatically be unpacked and the handler will be called for each instance in it.
This is the gist of addon networking, but what we covered above is only for sending data from a client addon and receiving that data on the server addon. Of course, the reverse communication is also possible and mostly similar, but there are a few differences. Namely, when registering a packet handler for receiving data on a client addon, the signature of the handler will not contain a player ID (since the data is always from the server):
// A client addon network receiver with a different type parameter (a similar enum for server to client communication)
IClientAddonNetworkReceiver<ClientPacketId> netReceiver = ...;
// Register packet handler, but this time for a different packet data class (again similar to ServerPacketData, but now for server to client communication)
netReceiver.RegisterPacketHandler<ClientPacketData>(ClientPacketId.PacketId1, packetData => {
var someData = packetData.SomeData;
// Do something with this data, but we don't have the ID in the handler delegate
});
This covers the basics of addon networking and should get you started with writing your own addons. For example projects that implement the API, see the following:
- HKMP.ExampleAddon: A project that shows the basics of client and addon creation and uses the networking in a basic form as well.
- HKMP.Tag: A project that implements a custom game-mode using the HKMP API. This a more advanced project that showcases a fully featured client and server addon.
For more in-depth topics, see the other pages: