Creating Custom Items - HasteModding/HasteModding GitHub Wiki

Creating a custom item json

Via the F1 Editor

TODO

Via the .hasteitem.json file

An example dummy Item JSON

QuantumLeap.hasteitem.json

This item is a barebones version of the Quantum Leap item from Convergence

{
  "minorItem": false,
  "rarity": 0,
  "itemTags": [
    0,
    3,
    200,
    101
  ],
  "itemName": "QuantumLeap",
  "title": {
    "m_TableReference": {
      "m_TableCollectionName": "ConvergenceItems"
    },
    "m_TableEntryReference": {
      "m_KeyId": 0,
      "m_Key": "QuantumLeap_Title"
    },
    "m_FallbackState": 0,
    "m_WaitForCompletion": false,
    "m_LocalVariables": []
  },
  "usesTriggerDescription": true,
  "triggerDescription": {
    "m_TableReference": {
      "m_TableCollectionName": "ConvergenceItems"
    },
    "m_TableEntryReference": {
      "m_KeyId": 0,
      "m_Key": "QuantumLeap_trigger"
    },
    "m_FallbackState": 0,
    "m_WaitForCompletion": false,
    "m_LocalVariables": []
  },
  "usesEffectDescription": true,
  "description": {
    "m_TableReference": {
      "m_TableCollectionName": "ConvergenceItems"
    },
    "m_TableEntryReference": {
      "m_KeyId": 0,
      "m_Key": "QuantumLeap_desc"
    },
    "m_FallbackState": 0,
    "m_WaitForCompletion": false,
    "m_LocalVariables": []
  },
  "flavorText": {
    "m_TableReference": {
      "m_TableCollectionName": "ConvergenceItems"
    },
    "m_TableEntryReference": {
      "m_KeyId": 0,
      "m_Key": "QuantumLeap_flavor"
    },
    "m_FallbackState": 0,
    "m_WaitForCompletion": false,
    "m_LocalVariables": []
  },
  "iconScaleModifier": 1.0,
  "statsAfterTrigger": false,
  "cooldown": 20.0,
  "triggerType": 7,
  "triggerConditions": [],
  "effects": [],
  "stats": {
    "maxHealth": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "runSpeed": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "airSpeed": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "turnSpeed": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "drag": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "gravity": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "fastFallSpeed": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "fastFallLerp": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "lives": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "dashes": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "boost": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "luck": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "startWithEnergyPercentage": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "maxEnergy": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "itemPriceMultiplier": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "itemRarity": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "sparkMultiplier": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "startingResource": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "energyGain": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "damageMultiplier": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "sparkPickupRange": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "extraLevelSparks": {
      "baseValue": 0.0,
      "multiplier": 1.0
    },
    "extraLevelDifficulty": {
      "baseValue": 0.0,
      "multiplier": 1.0
    }
  },
  "effectEvent": {
    "m_PersistentCalls": {
      "m_Calls": [
      ]
    }
  },
  "keyDownEvent": {
    "m_PersistentCalls": {
      "m_Calls": [
      ]
    }
  },
  "keyUpEvent": {
    "m_PersistentCalls": {
      "m_Calls": [
      ]
    }
  },
  "references": {
    "version": 2,
    "RefIds": []
  }
}
Key Default Value Usage
minorItem false Whether this item is an actual item and not a "?" event reward. Most of the times, you want it to be false.
rarity 0 The rarity of this item, from 0 (Common) to 3 (Legendary).
itemTags [] This is used to qualify an item (Courier, SciFi...). We recommend editing those from the F1 editor until a proper tool or table is made.
itemName "yourItem" The internal item name used for your item.
title --- This is a translatable string that corresponds to the Name of your item.
usesTriggerDescription false Whether to use your custom trigger description rather than the default generated trigger description.
triggerDescription --- This is a translatable string that corresponds to the Trigger Description of your item.
usesEffectDescription false Whether to use your custom effect description rather than the default generated effect description.
description --- This is a translatable string that corresponds to the Effect Description of your item.
flavorText --- This is a translatable string that corresponds to the Flavor text of your item.
iconScaleModifier 1.0 The scale of your item icon. 0.1 means 10x smaller, 1.0 means default size, 10.0 means 10x bigger, and anything in between is valid
statsAfterTrigger false Whether to display the stat changes after (true) the trigger, or before (false).
cooldown 15.0 The cooldown of your item.
triggerType 8 What triggers your item. We recommend editing this from the F1 editor until a proper tool or table is made.
triggerConditions [] Conditions for your item to be activated successfully. If one or more condition returns false, your item won't be activated.
effects [] Effects that happen once your item is activated successfully.
stats --- All the stats that can be modified by your item. Default values for each stat are "baseValue": 0.0, "multiplier": 1.0
effectEvent --- Relates to the effects value. See section below for more information.
keyDownEvent --- This is used to call one or more C# methods on key pressed. This is used by the grappling hook and the teleport item for instance.
keyUpEvent --- This is used to call one or more C# methods on key released. This is used by the grappling hook and the teleport item for instance.
references --- All references to the effects are indicated here. See further for more information.

References and Events

References and Events are Haste's way of embedding C# code into the hasteitem system.

In triggerConditions or effects, you can indicate a rid field, as such

Excerpt from CooldownReductionOnPerfectLanding.hasteitem.json

...
"triggerConditions": [
    {
        "rid": 1000
    }
],
"effects": [
    {
        "rid": 1001
    }
],
...

The rid field is a unique identifier for a reference. Here, rid 1000 and 1001 are used to reference behavior defined later in the JSON.

Tip

To remember what rid is, think of Reference IDentifier. Make sure that your rid is unique, otherwise you will run into issues.

The actual behavior of your item is defined in the references section of the JSON. Each reference entry contains:

  • Type Information: This specifies the corresponding C# class (e.g., LandingType_IT, AddVariable_Effect) that holds the behavior logic.
  • Data Object: This includes all the parameters (e.g. landingType, minAirTime, or amount) that customize the behavior for that particular item and method.

A reference entry might look like:

Excerpt from CooldownReductionOnPerfectLanding.hasteitem.json

{
    "rid": 1000,
    "type": {
        "class": "LandingType_IT",
        "ns": "",
        "asm": "Assembly-CSharp"
    },
    "data": {
        "landingType": 3,
        "compareType": 0,
        "minAirTime": 0.0,
        "maxAirTime": Infinity,
        "streak": 0,
        "resetStreakOnTrigger": true
    }
}

Caution

Please mind that this requires installing CustomItemLib as a dependency !

Tip

The main features of this mod are :

  • Factory methods for creating ItemInstance and PlayerStats objects with optional parameters to reduce clutter
  • A subscribable event that allows for better compatibility between custom item mods

It is recommended to know C# before using this library!

Setup

This library can be used via the steam workshop as a dependency or compiled from source directly in your mod (see LICENSE file). If you choose to include it directly in your mod, remember to change the namespace to avoid namespace collisions.

By default, this library uses the CustomItemLib namespace.

Note

This library does not provide any method of making or loading item meshes. To add cutom models to your items, see the 3d models section in The Official Landfall Documentation.

Usage

The primary feature of CustomItemLib is the ItemFactory.AddItemToDatabase method, which acts very similarly to the native Haste method that imports a JSON item file. This method must be provided an itemName argument, which will be the name of the item you either edit (if the item already exists) or create (if it doesn't).

Any parameters not included will default to the original item's value or to a default value when creating a new item. A full list of parameters will be included later.

Here is an example of a simple edit to the cooldown of the Rocket Boots item.

ItemFactory.AddItemToDatabase(
    itemName: "Active_Boost", // Internal itemName of Rocket Boots
    cooldown: 3f              // 3 second cooldown
);

Custom Items

The same can be done to create a new item. In a few places in Haste's codebase, enums are used like the ItemTriggerType below rather than the ints used in the JSON files. If you are unfamiliar with the enums, I would recommend keeping them open on a decompiler like dnSpy or ILSpy. The example below creates a new item which will trigger continuously every 3 seconds. We will get to effects later, for now this item does nothing when it triggers.

ItemFactory.AddItemToDatabase(
    itemName: "ExampleItem",
    triggerType: ItemTriggerType.Continious, // Item will trigger continuously, as often as it is allowed to
    cooldown: 3f                             // However, its cooldown will only allow it to trigger every 3 seconds
);

Tooltips and Item Descriptions

Items have four different string fields that are used to create the tooltip that appears when you hover over one. In the first example, because we were modifying an item that already exists, we don't have to include these parameters, and the library will fall back to the original item's values. In the second example, because we created a new item without including them, the library uses a default warning string. These use Unity's LocalizedString class instead of normal strings, however Haste has helpfully provided a UnlocalizedString class which we will use below.

ItemFactory.AddItemToDatabase(
    itemName: "ExampleItem",
    title: new UnlocalizedString("Example Item"),                                 // The title displayed at the top of the item tooltip
    triggerDescription: new UnlocalizedString("This item's trigger description"), // Item trigger description
    description: new UnlocalizedString("This item's description"),                // Item Description (I don't know which description is which at the moment)
    flavorText: new UnlocalizedString("Some flavour text about this item")        // The flavour text at the bottom of the item tooltop
);

Tip

To use localization features, see the Translation page of the wiki, and use LocalizedStrings in place of the unlocalized versions below.

Tip

Haste supports Unity Rich Text. You can use this to create coloured (and otherwise modified) strings.


Simple Stat Effects

So far, the items we've created haven't actually done anything, so let's create a simple item that increases our max energy when we have it.

Here we see the other factory method provided by this library, ItemFactory.NewPlayerStats. Haste's native PlayerStats class is intended to be serialized into, and therefore doesn't assign default values to its fields. This would normally leave us the responsibility of initializing each field with a new PlayerStat object, even if we aren't assigning any meaningful values. ItemFactory.NewPlayerStats takes care of this boilerplate by assigning a default PlayerStat object to any fields you don't specify. Any fields in a PlayerStat that aren't specified will use a default value. See an exported JSON to see all available stats that you can modify.

In the example below, we create a PlayerStats that adds 50 max energy to the player. You can also set a multiplier instead of / in addition to the baseValue.

ItemFactory.AddItemToDatabase(
    itemName: "ExampleItem",
    stats: ItemFactory.NewPlayerStats(maxEnergy: new PlayerStat { baseValue = 50f }) // Add 50 max energy to the player when they have this item
);

Warning

Keep in mind that ItemFactory.NewPlayerStats returns a PlayerStats object, while ItemFactory.AddItemToDatabase returns nothing.

Caution

There are two different similarly named native classes here. PlayerStats is a native class containing multiple PlayerStat fields.


Triggered Effects

While the stats field is great for simple effects, most items in Haste have effects that are triggered by something.

Below we will create an object that gives the player 1 energy whenever they pick up a spark using the triggerType parameter we saw earlier, and a new parameter called effects. The effects parameter contains a list of ItemEffect instances, or more accurately, a list of instances of classes derived from ItemEffect.

Important

The game provides a few default classes derived from ItemEffect, which you can find in your decompiler by selecting "Analyze" on the ItemEffect class and checking the "Subtypes" section, or by using the F1 menu in-game.

Here we will use the AddVariable_Effect class to add energy to the player.

ItemFactory.AddItemToDatabase(
    itemName: "ExampleItem",
    triggerType: ItemTriggerType.GetResource,                                     // ItemTriggerType.GetResource triggers when the player picks up a spark
    effects: new List<ItemEffect>
    {
        new AddVariable_Effect { amount = 1f, variableType = VariableType.Energy} // AddVariable_Effect adds an effect to the player
    }
);

Tip

effects can contain multiple ItemEffect objects. When an item triggers, it triggers ALL of its ItemEffects.

Note

You can create your own ItemEffect classes to do just about anything by deriving the native ItemEffect class. (Guide coming sometime in the future)


Trigger Conditions

Many items in Haste also have a condition that must be met for their effect to trigger. These are defined with the triggerConditions, which contains a list of instances of classes derived from the native class ItemTrigger, very similarly to the effects from before. Below we'll add a simple ChanceCheck_IT trigger condition that only lets the item trigger 50% of the time.

ItemFactory.AddItemToDatabase(
    itemName: "ExampleItem",
    triggerType: ItemTriggerType.GetResource,                                     // ItemTriggerType.GetResource triggers when the player picks up a spark
    triggerConditions: new List<ItemTrigger>
    {
        new ChanceCheck_IT { probability = 0.5f }                                 // ChanceCheck_IT only allows the item to trigger sometimes at random
    },
    effects: new List<ItemEffect>
    {
        new AddVariable_Effect { amount = 1f, variableType = VariableType.Energy} // AddVariable_Effect adds an effect to the player
    }
);

Tip

triggerConditions can contain multiple ItemTrigger objects. Items will only trigger if ALL of their ItemTriggers return true.

Note

Note 2: You can create your own ItemTrigger classes in the same way as before with ItemEffect. (Guide coming sometime in the future)


ItemsLoaded Event / Full Example

Unfortunately, you can't just put these method calls in your LandfallPlugin's static constructor, because plugins are loaded before the ItemDatabase is initialized. Normally, you would need to use a MonoMod hook or something similar to load the items at the right time (and you still can if you prefer).

However, CustomItemLib provides an event that you can subscribe to load your items when the game would normally load items, to ensure proper loading and provide some extra compatibility between custom item mods.

Below is a full example of the recommended structure when using CustomItemLib.

using Landfall.Modding;
using Landfall.Haste;
using CustomItemLib;

namespace ExampleItems
{
    [LandfallPlugin]
    public class ExampleItems
    {
        static ExampleItems()
        {
            ItemFactory.ItemsLoaded += LoadCustomItems; // Subscribe your method to ItemFactory.ItemsLoaded so it runs when the ItemDatabase loads items
            // Your plugin's other initialization code
        }
        private static void LoadCustomItems() // Put all of your AddItemToDatabase calls in a method that you can then subscribe to ItemsLoaded (seen above)
        {
            ItemFactory.AddItemToDatabase( // A custom item
                itemName: "ExampleItem1",
                triggerType: ItemTriggerType.Continious,
                cooldown: 3f
            );

            ItemFactory.AddItemToDatabase( // Another custom item
                itemName: "ExampleItem2",
                triggerType: ItemTriggerType.GetResource,
                effects: new List<ItemEffect>
                {
                    new AddVariable_Effect { amount = 1f, variableType = VariableType.Energy}
                }
            );
        }
    }
}

Exhaustive Example

Putting everything we've learned together, let's recreate the Brittle Breastplate item from Haste. Keep in mind, this is an extreme case for a complicated item, default item modifications and simpler custom items will rarely be as long as this. Because Brittle Breastplate already has localization for it's tooltip strings, we'll try using LocalizedStrings this time instead of UnlocalizedStrings.

ItemFactory.AddItemToDatabase(
    itemName: "BrittleBreastplate", // The actual itemName is MaxHealthButDieOnOkOrBadLanding, but we're making a custom copy
    rarity: Rarity.Epic,
    itemTags: new List<ItemTag>     // I think these are just for flavour, I don't know if they even have an effect ingame
    {
        ItemTag.Object,
        ItemTag.Fantasy,
        ItemTag.Captain,
        ItemTag.Heir,
        ItemTag.Agency
    },
    title: new LocalizedString("Items", "MaxHealthButDieOnOkOrBadLanding_title"), // Reference the actual Brittle Breastplate's title
    triggerDescription: new LocalizedString("Items", "MaxHealthButDieOnOkOrBadLanding_triggerDesc"), // and triggerDescription
    description: new LocalizedString("Items", "MaxHealthButDieOnOkOrBadLanding_desc"),               // and description
    flavorText: new LocalizedString("Items", "MaxHealthButDieOnOkOrBadLanding_flavor"),              // and flavorText
    cooldown: 1f,
    triggerType: ItemTriggerType.Landing, // Brittle Breastplate's damage is triggered by landing
    triggerConditions: new List<ItemTrigger>
    {
        new LandingType_IT
        {
            landingType = LandingType.Ok,                           // Lets the item trigger if the landing was Ok
            compareType = LandingType_IT.CompareType.WorseOrSameAs, // or worse
        }
    },
    effects: new List<ItemEffect>
    {
        new AddVariable_Effect { amount = 50f, variableType = VariableType.Damage } // Deal 50 damage to the player when triggered
    },
    stats: ItemFactory.NewPlayerStats(maxHealth: new PlayerStat { multiplier = 2.5f }) // Give the player +150% max health when they have this
);

Note

ItemFactory.AddItemToDatabase includes a parameter autoUnlocked which determines whether to automatically unlock the item for the player. This is set to true by default, but you can set it to false if your mod involves player progression and you want the player to have to unlock the item the normal way.


Parameters and Default Values

Below is a full list of parameters available with ItemFactory.AddItemToDatabase and their default values.

string itemName;                                                    // itemName is required, and is the internal name of your item.
bool autoUnlocked = true;                                           // Whether to unlock the item automatically
bool minorItem = false;                                             // Whether the item is considered a "minorItem", which can't appear in shops
Rarity rarity = Rarity.Common;                                      // The item's rarity
List<ItemTag> itemTags = new List<ItemTag>();                       // The item's tags (seemingly cosmetic)
LocalizedString title = new UnlocalizedString("Missing Title");     // The item's tooltip title
bool usesTriggerDescription = false;                                // Whether to overwrite the auto-generated trigger description with yours.
LocalizedString triggerDescription = new UnlocalizedString("Item is missing trigger description") // The trigger description (eg. "On Activate,")
bool usesEffectDescription = false;                                 // Whether to add your custom description on top of the auto-generated one.
LocalizedString description = new UnlocalizedString("Item is missing description"); // The effect description (eg. "Gain +100% Boost.")
LocalizedString flavorText = new UnlocalizedString("Dalil didn't explain what this item is..."); // The item's flavour text at the bottom of the tooltip
float iconScaleModifier = 1f;                                       // Not sure, maybe for scaling meshes that are the wrong size?
bool statsAfterTrigger = false;                                     // Whether to invert the order of "description" and "trigger description"
float cooldown = 0f;                                                // The minimum time between this item's triggers
ItemTriggerType triggerType = ItemTriggerType.None;                 // The type of event that causes this item to attempt to trigger
List<ItemTrigger> triggerConditions = new List<ItemTrigger> { };    // The item's trigger conditions
List<ItemEffect> effects = new List<ItemEffect> { };                // The item's effects when triggered
PlayerStats stats = ItemFactory.NewPlayerStats();                   // The PlayerStats object that will be added to the player when they get the item
UnityEvent effectEvent = new UnityEvent();                          // Used as an alternative to effects and trigger conditions. Not fully understood yet
UnityEvent keyDownEvent = new UnityEvent();                         // Used as an alternative to effects and trigger conditions. Not fully understood yet
UnityEvent keyUpEvent = new UnityEvent();                           // Used as an alternative to effects and trigger conditions. Not fully understood yet

Note

While some parameters map to ItemInstance fields that aren't fully understood by the community yet, they should still work as intended if you figure them out, so feel free to test them if you have any ideas, and share your discoveries with the discord.

Adding custom C# code for your item

TODO

Unlocking your custom Item

Saves in Haste are done using "Facts", which are basically keys in the save file.

This fact allows the item to be displayed in the item list as an unlocked item.

FactSystem.SetFact(new Fact("[CustomItem]_ShowItem"), 1.0f);

This fact allows the item to be used and purchased in the shop.

FactSystem.SetFact(new Fact("item_unlocked_[CustomItem]"), 1.0f);

Below is a sample code that unlocks your items every time a save file is loaded :

This should be in your constructor.

On.SaveSystem.Load += (orig) => {
    HasteSave save = orig();
    UnlockItems();
    return save;
};

Put this method anywhere you feel like.

static void UnlockItems()
{
    FactSystem.SetFact(new Fact("[CustomItem]_ShowItem"), 1.0f);
    FactSystem.SetFact(new Fact("item_unlocked_[CustomItem]"), 1.0f);
}
⚠️ **GitHub.com Fallback** ⚠️