All pages
Powered by GitBook
2 of 14

Modding Examples

This section contains various examples on how to create things in Core Keeper

Check these guides out if you are new to modding.

Do you know how to create something that isn't explained here? Please add it!

Items

This page describes how to create various item archetypes. For example: ingredients, tools and weapons, etc.

Item are the simplest form of content you can add to the game. These range from things like wood to sword and armor.

Basic Item

To create an item, first create a new Prefab (Right click in the project tab, Create -> Prefab).

Open the prefab and add following components: ObjectAuthoring, InventoryItemAuthoring.

ObjectAuthoring

This component defines what this object is.

First field you must fill out is Object Name. Following the naming pattern: ModName:ItemName. Learn more about Object Names here.

Next field you must set is the Object Type. This field defines the kind of item this is. For this example set it to Non Usable.

You may fill other fields on your own discretion.

InventoryItemAuthoring

This component defines information about the item for display in the inventory.

You must fill out fields Icon and Small Icon. These are the icons the player will see ingame. Both need to be 16x16 px.

Here you can also define item's crafting recipe, sell and buy values, and whether it can be stacked. Crafting recipes will be covered in another guide.

Item Localization

To set item's localized name create folder named Localization in your mod root folder. Now create a file in there called Localization.csv. This file will define all of your mod localization strings. Here you can add both English texts and other languages.

The file is encoded as Tab Separated Values (TSV). Initialize the file with the following content (Choose one of these two):

Reduced set
Key	Type	Desc	English	German	Japanese	Korean	Spanish	Chinese (Simplified)	Thai
All languages
Key	Type	Desc	English	German	French (France)	Portuguese (Brazil)	Italian (Italy)	Japanese	Korean	Russian	Spanish	Ukrainian	Chinese (Simplified)	Chinese (Traditional)	Thai

From the next line starts the actual content which needs to be tab separated. I would recommend to get a good CSV editor. One good option is Rons Data Edit, which can be used for free.

Add following content:

  • Key: Items/ExampleMod:ExampleItem , English: Example Item

  • Key: Items/ExampleMod:ExampleItemDesc, English: My first item.

Fill other fields on your own discretion. Please note that ExampleMod:ExampleItem here is your Object Name as set in ObjectAuthoring.

Trying Out Items In-Game

Once you have created your new item you will need to get it to test the mod.

Unfortunately, at the moment, the only way to get an item if it wasn't explicitly added to a workbench, drop table or other source is by using cheats like Chat Commands.

LogoChatCommands for Core Keeper - mod.iomod.io

With Chat Commands you can acquire any item like so: /give ExampleMod:ExampleItem [optional amount]

Weapons and Tools

Follow the Item Guide

Follow the steps in the generic item guide first to define the other required properties of your item.

Items

Creating Melee Weapons and Tools

To make a equipable item with use animation you need to:

  • Set the ObjectType to tool or weapon type

  • Add DurabilityAuthoring, GivesConditionsWhenEquipedAuthoring, CooldownAuthoring, WeaponDamageAuthoring and LevelAuthoring component authorings and configure them correctly

  • Assign both icons to first sprite in item animation sheet.

Sprite sheets should be 120x120px and have 7 sprites showing item in different states. You can find such sheets for all base game weapons and tools by ripping game content via Asset Ripper.

Screenshot of an example weapon sprite sheet

Creating Ranged Weapons

The CoreLib.Entity submodule is required for this guide section.

To make a ranged weapon you mostly need to do the same as with any other weapon. Except for the fact that you will need a custom projectile entity added. To hook modded projectile entity use ModRangeWeaponAuthoring component instead of RangeWeaponAuthoring (CoreLib feature)

Armor

Follow the Item Guide

Follow the steps in the generic item guide first to define the other required properties of your item.

Items

Armor

The CoreLib.Equipment submodule is required for this guide section.

To make armor you need to:

  • Set the ObjectType to armor type

  • Add DurabilityAuthoring, ModEquipmentSkinAuthoring, GivesConditionsWhenEquipedAuthoring and LevelAuthoring component authorings and configure them correctly

Make a armor spite sheet. Examples of such sheets can be found in the Unity Editor. Now assign your texture to ModEquipmentSkinAuthoring (CoreLib feature). The component will automatically set everything up.

Food

Follow the Item Guide

Follow the steps in the generic item guide first to define the other required properties of your item.

Items

Creating Food

This guide has not been written yet! If you know how to do this, add it!

Obtaining items

This section describes ways you can make your items obtainable

Adding Crafting Recipe

To a basic crafting recipe open Item Entity prefab and expand Required Objects To Craft. Increase the size of the list to the needed amount and then proceed to select each item.

Example crafting recipe

To add a modded item as a crafting ingredient enter its Object Name in the field.

Warning: defining the crafting recipe does not mean players will now be able to obtain it.

For that to happen you must assign it to one of many existing workbenches or make your own.

Adding your items to crafters

Existing Crafting buildings

One way to make your item obtainable is to add it to already existing workbench. Please be aware that each workbench can have AT MOST 18 slots. You cannot exceed this value. As such this approach isn't too scaleable.

To do so add the following code to your IMod class:

Following snippet makes use of CoreLib library

public const string MODNAME = "Example mod";
public const string VERSION = "1.0.0";

public static Logger Log = new Logger(MODNAME);

// You might already have this method
// if so, just add method body to your code
public void EarlyInit() 
{
    Log.LogInfo($"Mod version: {VERSION}");

    API.Authoring.OnObjectTypeAdded += EditWorkbench;

    Log.LogInfo($"{MODNAME} mod is loaded!");
}

private static void EditWorkbench(
    Entity entity, 
    GameObject authoring, 
    EntityManager entityManager
){
    ObjectID objectID = authoring.GetEntityObjectID();
    
    // This will determine what workbench we are targeting 
    // In this case it is Solarite Workbench
    if (objectID != ObjectID.SolariteWorkbench) return;
    
    // This is the item we will be adding
    var targetItem = "ExampleMod:ExampleItem"

    var canCraftBuffer = entityManager.GetBuffer<CanCraftObjectsBuffer>(entity);
    var item = API.Authoring.GetObjectID(targetItem);

    for (int i = 0; i < canCraftBuffer.Length; i++)
    {
        if (canCraftBuffer[i].objectID == item) return;
        if (canCraftBuffer[i].objectID != ObjectID.None) continue;
        
        Log.LogInfo($"Adding itemId {targetItem} to {objectID}");
        canCraftBuffer[i] = new CanCraftObjectsBuffer
        {
            objectID = item,
            amount = 1,
            entityAmountToConsume = 0
        };
        break;
    }
}

Please note that values returned by API.Authoring.GetObjectID() are not static and will change between game launches. Do not try to hardcode it or save it.

Adding a custom workbench

The CoreLib.Entity submodule is required for this guide section.

CoreLib.Entity submodule allows to easily add custom workbenches. All you have to do is define it via a special Scriptable Object.

Create a new Workbench definition by right clicking in project tab and selecting Create -> CoreLib -> New Workbench Definition

Example Workbench Definition (Source: WIP Mod Rail Logistics)

Workbench Definition fields

There are following fields to fill out:

  • Item ID - the same as Object Name in Object Authoring, refer to this

  • Icons - two sprites the player will see in inventory

  • Asset Skin - An Asset skin for a workbench, defines how the workbench will look in the world

  • Recipe - What is required to make the workbench

  • Can Craft - Most important section. Here you can list everything players will be able to find in the workbench. You can only at most 18 slots!

  • Related Workbenches - Any workbenches listed here (Modded via Workbench Definition) will be considered related and ingame you will see up and down arrows that allow switching between these.

  • Titles - Text that will be displayed on top of workbench UI. Must be localized, refer to this

  • Skin - Visual appearance of workbench UI

Ensure you have added localization texts for your workbench as described here

Create Sprite Asset Skin

To create Sprite Asset Skin open create menu and select Create -> 2D -> Sprite Asset Skin. Assign Target Asset property. The sprite you want to select is Workbench, and is bundled with CoreLib Package.

Example Sprite Asset Skin (Source: WIP Mod Rail Logistics)

Assign main texture (Emmisive optionally) and three variants textures. Refer to original Sprite Asset for how these should look like.

Register Workbench Definitions

In order for CoreLib to find your mod workbenches add following code to your IMod class:

public void EarlyInit()
{
    CoreLibMod.LoadModules(typeof(EntityModule));
}

public void ModObjectLoaded(Object obj)
{
    if (obj == null) return;

    if (obj is WorkbenchDefinition workbenchDefinition)
    {
        EntityModule.AddModWorkbench(workbenchDefinition);
        return;
    }
}

You are done! Now you should see your workbench in CoreLib's Root Workbench, which can be crafted in player inventory.

Adding items to Enemy loot

This guide has not been written yet! If you know how to do this, add it!

Placeables

This page describes how to create various objects that can be placed by player

Placeable Object

The CoreLib.Entity submodule is highly recommended for this guide section.

A placeable object is a item with a graphical prefab that can be placed into the world. Player can interact with said object, and it can do various things in the world.

Making the item

First, follow the steps in the generic item guide to define the other required properties of your entity.

Items

Set the Object Type to Placeable Prefab.

Assigning components

Add following components:

  • Placeable Object Authoring. This component allows to set various properties of the object. For example here you can make the object take up more than one tile. It also allows to set on what the object can be placed.

  • Physics Shape. This will define the collision of the object

  • Ghost Authoring Component. This is a component that controls your object ghost settings. Ghosts are a NetCode concept and really mean networked object. For most objects keep setting as defaults.

  • Mineable Authoring

  • Health Authoring. Set Health and Max Health to 2

  • Health Regeneration Authoring

  • Damage Reduction Authoring. Set Max Damage Per Hit to 1

  • State Authoring

  • Idle State Authoring

  • Death State Authoring

  • Took Damage State Authoring

  • Animation Authoring

  • Ignore Vertex Offsets

Optional components:

  • Interactable Authoring. Enables your object to be interacted with. Ensure your graphical prefab has a Interactable Object component

  • Rotation Authoring. Allows your object to be rotated

  • Electricity Authoring. Allows your object to be powered and to power things

  • Supports Pooling (CoreLib). Fixes issues you will encounter with Graphical Prefabs. HIGHLY recommended.

Making the graphical prefab

Graphical prefab is a Game Object that represents the entity on screen. It is important to understand that Graphical prefabs live only on clients, and are only responsible for visual appearance of an entity. They should never encode core behavior of an entity. The only exceptions to this are UI and inventory logic.

To make a graphical prefab create a new Prefab. On the root of it you need to place a EntityMonoBehaviour deriven class.

Warning: Do NOT use existing classes that derive from EntityMonoBehaviour in your prefabs. You will have issues if you do. If you want to use a specific class, make a new class deriving from it, and use that

Then in the hierarchy create a GameObject called XScaler. Then inside of it create two GameObjects: SpriteObject, ShadowSprite and particleSpawnLocation. Optionally you can create GameObject Interactable, if your object will need to be interacted with. The resulting prefab should look like this:

Example graphical prefab

On sprite GameObjects add a new component called SpriteObject. On the root component assign fields X Scaler, Shadow and set list Sprite Objects

Making the Sprite Asset

Now create two Sprite Assets (Create -> 2D -> Sprite Asset), one for main sprite, one for shadow. In the main one assign your main texture, and in the shadow one assign a shadow texture. Now set these Sprite Assets as the Asset field on each of the sprites.

One of the Sprite Objects set

If you want the object to be rotatable create 3 more static variations: up (When facing away from camera), side (Facing right) and side2 (Facing left). Assign their texture to relevant textures. To the root of the prefab add component Sprite Variation From Entity Direction. Assign both sprites.

Intractability

If you want the object to be interactable, on the Interactable GameObject a new component Interactable Object. Assign field Entity Mono, set sprites in Sprite Objects field (Except shadow) and wire up Unity Events On Use Actions and On Trigger Exit Actions to your root component methods. On the root component assign its field Interactable.

Interactable Object

At the end the root of the prefab should look something like this:

Graphical Prefab Root

Once you are done with the prefab, link it to the Object Authoring component.

Ensure CoreLib knows about the prefab

Also don't forget to add following code to ModObjectLoaded() method in order to fix issues caused by lack of pooling support by default: (CoreLib)

if (obj is not GameObject gameObject) return;

var entityMono = gameObject.GetComponent<EntityMonoBehaviour>();
if (entityMono != null)
{
    EntityModule.EnablePooling(gameObject);
}

Also ensure you have requested CoreLib to be loaded:

Make sure to call CoreLibMod.LoadModules(typeof(EntityModule)); to in your mod EarlyInit() function, before using the module. This will load the submodule.

Troubleshooting

Nothing appears (Can't find the item)

Firstly ensure that your mod is indeed is loading. For that make your IMod class log this, and look for logs

If you have Chat Commands installed try watching logs for a log from CoreLib in a format like this: objectName -> number. If you see your name in here, your object is ingame.

You might have some issues with the object name working from Chat Commands, to fix this either add a localized name or try using the number you see in that log message from CoreLib.

My entity seems to be still present after destruction

This is a known issue that devs could fix, but so far don't. Your only way to solve this is to use CoreLib's Entity module, and it's component SupportsPooling.

Ensure you have added this component to your entity prefab, and that the grahpical prefab has a unqiue root script that derives from EntityMonoBehaviour. (Note: this script MUST be custom, you CANNOT reuse existing scripts)

Lastly ensure you have done this

Tiles

This page describes how to create a tile item.

The CoreLib.Entity and CoreLib.Tilesets submodules are required for this guide section.

Tiles are special items whose appearance is defined by a tileset. Tiles are not entities and are grouped together by the Tilemap. Tiles cannot have any logic. Exception are tile-entities, which are entities with a tile as a visual

When placed they are displayed by a tile system instead of a graphical prefab. This is how one placeable (like walls, floors, bridges, fences, etc) can have many appearances depending on neighbor tiles.

To make custom tileset(s) create a ModTileset asset in your Unity project.

Example tileset

Tileset Id is a unique identifier of your tileset that you will need later. Use the format ModName:TilesetName. Learn more about Tileset Ids here.

Layers is a reference to a tileset definition asset. CoreLib includes reference assets for three definition assets:

  • tileset_main

    • Used by biome tilesets. Includes most tiletypes.

  • tileset_base_building

    • Reduced tileset made specifically for player built things.

  • tileset_extras

    • Contains various other tiles.

You can make your own Layer asset and assign it here. (Please note that assets provided with core lib are empty placeholders and are replaced at runtime.)

Tileset Texture is first texture field. It contains a reference texture for tileset, and contains a variety of different things. Look at vanilla textures to figure out what is what.

Adaptive Textures dict contains set of adaptive textures. These are important, as they store variations of your tile as it contacts other tiles. Usually these textures are autogenerated by the game developers of the game. Unfortunately, the modding community does not have info on how they are made, so you will need to reference some of existing ones to make yours.

After setting up the asset add following code to your ModObjectLoaded() method:

if (obj == null) return;

if (obj is ModTileset tilesetBank)
{
    TileSetModule.AddCustomTileset(tilesetBank);
    return;
}

Now you can use ModTileAuthoring component to set the tileset and tile type in Editor.

NPCs and Enemies

This page describes how to create NPC's (Non Playable Characters) and enemies.

This guide has not been written yet! If you know how to do this, add it!

UI and Interactions

This page describes implementing various features that allow user to interact with the mod. For example: keybinds, custom interfaces, etc.

This guide has not been written yet! If you know how to do this, add it!

Client-Server communications

This page describes how to handle various things related to client-server communications. This includes sending messages, implementing prediction, etc.

Core Keeper is a server authoritative game. This means for an action to occur, the server must be aware of it and approve. When we write mods we must be able to create logic that allows for Client-Server communication to occur.

Note that Unity uses extensive code gen to implement features outlined here. If you ever get errors regarding CodeGen ensure your mod output Scripts folder has Generated folder. If it doesn't your Unity project might have an error. A restart usually helps

Additionally you might want to utilize Burst

Ghost Components

Ghost component is one of the simplest ways to transfer data between Client and server. In its essence Ghost Component is just a normal ECS component that is synced over network.

To make a Ghost Component first Create IComponentData deriving ECS component:

public struct ExampleCD : IComponentData
{
    public int importantField;
    
    public float normalField;
}

To make this component a Ghost Component all you have to do is add a GhostComponent attribute to it. Additionally you must mark every field you want to be synced with GhostField attribute:

[GhostComponent]
public struct ExampleCD : IComponentData
{
    // This field will end up synced
    [GhostField] 
    public int importantField;
    
    // This field isn't marked, so it won't be synced
    public float normalField;
}

Making Ghost Components is just as easy as this. Now any data set to this component of the server will be automatically replicated to all clients. Refer to this guide to learn more

Remote Procedure Calls

Remote Procedure Calls (or RPC's) are a way to send information to the Server or clients in one-shot manner. They allow you to send arbitrary data or commands to the other side, be it requests or data

This example will focus on Client to Server communication, as that is the most common scenario, however you can send RPC from Server to Clients or even both ways

To make a RPC first add RPC command data definition:

public enum MyModCommandType : byte
{
    UNDEFINED,
    DO_VERY_IMPORTANT_THING,
    ANOTHER_IMPORTANT_TASK
    
    //etc
}

public struct MyModCommandRPC : IRpcCommand
{
    // This enum will define what kind of command this is
    public MyModCommandType commandType;
    
    // This is bundled request data.
    // This can be target entities, such as players
    // Or any other neccessary data
    public Entity entity0;
    public int value;
    
    // you can add more fields as needed
}

Now define two systems. One for Clients:

[UpdateInGroup(typeof(RunSimulationSystemGroup))]
[WorldSystemFilter(WorldSystemFilterFlags.ClientSimulation)]
public partial class ClientModCommandSystem : PugSimulationSystemBase
{
    private NativeQueue<MyModCommandRPC> rpcQueue;
    private EntityArchetype rpcArchetype;

    protected override void OnCreate()
    {
        UpdatesInRunGroup();
        rpcQueue = new NativeQueue<MyModCommandRPC>(Allocator.Persistent);
        rpcArchetype = EntityManager.CreateArchetype(typeof(MyModCommandRPC), typeof(SendRpcCommandRequest));

        base.OnCreate();
    }

    #region Commands

    // This is the most important section
    // Here you can make send functions to have 
    // easy interface with the rest of your code
    public void DoVeryImportantThing(Entity target, int value)
    {
        rpcQueue.Enqueue(new MyModCommandRPC()
        {
            commandType = MyModCommandType.DO_VERY_IMPORTANT_THING,
            entity0 = target,
            value = value
        });
    }

    #endregion

    protected override void OnUpdate()
    {
        EntityCommandBuffer entityCommandBuffer = CreateCommandBuffer();
        while (rpcQueue.TryDequeue(out MyModCommandRPC component))
        {
            Entity e = entityCommandBuffer.CreateEntity(rpcArchetype);
            entityCommandBuffer.SetComponent(e, component);
        }
    }
}

And one for Server:

[UpdateInGroup(typeof(SimulationSystemGroup))]
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
public partial class ServerModCommandSystem : PugSimulationSystemBase
{
    protected override void OnUpdate()
    {
        var ecb = CreateCommandBuffer();

        Entities.ForEach((Entity rpcEntity, in MyModCommandRPC rpc, in ReceiveRpcCommandRequest req) =>
            {
                switch (rpc.commandType)
                {
                    case MyModCommandType.DO_VERY_IMPORTANT_THING:

                        // Do very important thing
                        // This code will be executed on the server
                        break;
                }
                ecb.DestroyEntity(rpcEntity);
            })
            .WithoutBurst()
            .Schedule();

        base.OnUpdate();
    }
}

Now to use these systems you just have to add this code in your IMod class:

public static ClientModCommandSystem clientSystem;

// You might already have this method
// if so, just add method body to your code
public void EarlyInit() 
{
    API.Client.OnWorldCreated += ClientWorldReady;
}

private static void ClientWorldReady()
{
    var world = API.Client.World;
    clientSystem = world.GetOrCreateSystemManaged<ClientModCommandSystem>();
}

Now you can use MyMod.clientSystem.DoVeryImportantThing() from anywhere in your code

Prediction

This guide has not been written yet! If you know how to do this, add it!