Creating Your First Weapon
This tutorial will guide you through creating a simple mod that modifies a basic in-game function.
The mod code for this tutorial is stored on Github.
Most of the code in this chapter comes from Frostbite. Thanks to Frostbite for their help with this tutorial.
Creating the Mod Project
Follow the tutorial to create a new mod project named FirstWeapon.
Making res.pak
Follow the tutorial to create a diff pak. The CDB should include:
- A new weapon named OtherDashSword and its corresponding item.
OtherDashSword can be replaced with another name.
A pre-made res.pak is available.
Copying res.pak
- Copy the res.pak file obtained in the previous step to the project's root directory.
- Modify FirstWeapon.csproj by adding the following content:
<ItemGroup>
<!--Copies the res.pak file to the bin\Debug\net9.0 directory-->
<None Update="res.pak">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<!--Copies the res.pak file to the final output directory-->
<OutputFiles Include="res.pak" />
</ItemGroup>
Writing the Code
Loading res.pak
- Modify the
FirstWeaponMod.FirstWeapon
class to implement theIOnGameEndInit
interface.
// Load res.pak and refresh the CDB
void IOnGameEndInit.OnGameEndInit()
{
var res = Info.ModRoot!.GetFilePath("res.pak"); // Get the absolute path of the res.pak file in the mod's root directory
FsPak.Instance.FileSystem.loadPak(res.AsHaxeString()); // Load res.pak
var json = CDBManager.Class.instance.getAlteredCDB(); // Get the merged CDB JSON
Data.Class.loadJson( // Load the merged CDB JSON
json,
default);
}
After successfully loading res.pak, you should see something similar to this in the log:
[14:30:13 INF][FsPak] Loading pak from C:\SteamLibrary\steamapps\common\Dead Cells\coremod\mods\FirstWeapon\res.pak
The IOnGameExit
and IOnGameEndInit
interfaces are part of the event system. They are called when their corresponding events are triggered.
IOnGameExit
will be called before the game exits.OnGameEndInit
will be called after the game has been initialized.IOnHeroUpdate
will be called once every frame within the game.
Creating the Weapon Class
Create a new class FirstWeaponMod.OtherDashSwordWeapon
that inherits from the DashSword
class and implements the IHxbitSerializable<object>
interface.
// Weapon class
internal class OtherDashSwordWeapon :
DashSword, // Base class
IHxbitSerializable<object>
{
// Default constructor
public OtherDashSwordWeapon(Hero o, InventItem i) : base(o, i)
{
}
// Leave empty
object IHxbitSerializable<object>.GetData()
{
return new(); //TODO
}
// Leave empty
void IHxbitSerializable<object>.SetData(object data)
{
//TODO
}
// Test effect - add 10 cells per frame
public override void fixedUpdate()
{
base.fixedUpdate();
bool noStats = false;
this.owner.addCells(10, new HaxeProxy.Runtime.Ref<bool>(ref noStats));
}
}
The IHxbitSerializable<>
interface is used to save game object data.
For simplicity, this weapon class inherits from the DashSword
class instead of directly from the Weapon
class (the base class for all weapons).
Registering the New Weapon
Simply adding the new weapon's information to the CDB is not enough.
To make the game recognize the new weapon, you also need to modify the FirstWeaponMod.FirstWeapon
class by adding the following:
public override void Initialize()
{
Logger.Information("Hello, World");
Hook__Weapon.create += Hook__Weapon_create; // Hook $Weapon.create
}
private Weapon Hook__Weapon_create(Hook__Weapon.orig_create orig, dc.en.Hero o, InventItem i)
{
var id = i._itemData.id.ToString(); // Get the weapon ID
if(id == "OtherDashSword")
{
return new OtherDashSwordWeapon(o, i); // Return the custom weapon
}
else
{
return orig(o, i); // Call the original method
}
}
Getting the New Weapon
Obviously, you can't get the new weapon yet because it has no acquisition method.
You can create the item corresponding to the new weapon with the following code:
// Spawns the item
private void SpawnWeapon(Hero hero)
{
InventItem testItem = new InventItem(new InventItemKind.Weapon("OtherDashSword".AsHaxeString()));
bool test_boolean = false;
ItemDrop itemDrop = new ItemDrop(hero._level, hero.cx, hero.cy, testItem, true, new HaxeProxy.Runtime.Ref<bool>(ref test_boolean));
// The init method must be called after creating the drop, otherwise the game will crash
itemDrop.init();
itemDrop.onDropAsLoot();
itemDrop.dx = hero.dx; // Not sure why this step is necessary, but it's in the original code
}
For simplicity, use the following code to allow obtaining the new weapon by pressing the backslash key in-game.
// Implement the IOnHeroUpdate interface
void IOnHeroUpdate.OnHeroUpdate(double dt)
{
if(Key.Class.isPressed(0xDC /**Backslash key code**/))
{
SpawnWeapon(Game.Instance.HeroInstance!);
}
}