Custom Content: Robes - WoL-Modding-Extravaganza/WoLWiki GitHub Wiki

Creating a Robe (Outfit)

If you've skipped to this page and don't know how to make a mod, you'll want to do that first lol

For this mod, we will be using LegendAPI to streamline content creation. If you haven't, you'll want to add LegendAPI to your references

Let's start with an example:

OutfitInfo coolOutfitInfo = new OutfitInfo()
{
    name = "Cool Guy",
    outfit = new Outfit(
        "ModName::OutfitID", //newID
        1, //newColorIndex
        new List<OutfitModStat>
        {
            new OutfitModStat(OutfitModStat.OutfitModType.Health,
                              100, //addValue
                              0, //multiValue
                              0, //override value
                              false), //bool value
            // more OutfitModStats here
        },
        false, //isUnlocked
        false) //isLeveling
};

LegendAPI.Outfits.Register(coolOutfitInfo);

Boom, we've just created a robe.

Robes are so simple, that this one snippet of code might be enough by itself for you to figure it out.
If you think you can handle this, go forth and create your own robes! Then skip to Not So Basics below if you want to know about more advanced robe creation.

The Basics

Let's go line by line and see what's happening.

OutfitInfo

We start by defining a new OutfitInfo:

OutfitInfo coolOutfitInfo = new OutfitInfo()
  • OutfitInfo is a constructor class from LegendAPI that makes adding a robe to the game trivial.
    • In it, we define a few fields to get our Outfit going:

First is the name:

    name = "Cool Guy",
  • pretty self explanatory, this is the name that will show up in game.

Next is the Outfit itself:

    outfit = new Outfit(
  • This is the actual outfit using the Outfit class from the game

Outfit

    outfit = new Outfit(
        "ModName::OutfitID", //newID
        1, //newColorIndex
        new List<OutfitModStat>
        {
            new OutfitModStat(OutfitModStat.OutfitModType.Health, 100, 0, 0, false),
            // more OutfitModStats here
        },
        false, //isUnlocked
        false) //isLeveling

This is pretty straightforward. We just use the game's constructor.
Before continuing, try to do this part yourself, using only Visual Studio's intellisense. It should all be self explanatory based on the constructors' parameters.

        "ModName::OutfitID", //newID
  • The internal ID of the outfit. This is what the game uses to track your outfit.
  • I recommend you preface your outfit ID with your mod name, to avoid conflicts if another mod adds a robe with the same ID
        1, //newColorIndex

OutfitModStats

        new List<OutfitModStat>
        {
            new OutfitModStat(OutfitModStat.OutfitModType.Health, 100, 0, 0, false),
            // more OutfitModStats here
        }) 

The list of outfit mods.

  • OutfitModStat.OutfitModType.Health is, as it sounds, the type of stat your outfit modifies
  • 100, 0, 0, false in order, these parameters are:
    • float addValue (default: 0f)
    • float multiValue (default: 0f)
    • float overrideValue (default: 0f)
    • bool boolValue (default: false)
    • Choose one of these values and change them from the default. The outfit will change your OutfitModType to the chosen value.
  • In our example, we're modifying OutfitModStat.OutfitModType.Health, and setting addValue to 100.
    • So this outfit adds 100 health.

More Examples:

new OutfitModStat(OutfitModStat.OutfitModType.Damage, 0, 0.1f, 0, false),
  • Modifying OutfitModStat.OutfitModType.Damage, setting multiValue to 0.1f.
  • Think of your multiValue as a percentage modifier. So this would increase your damage by 10%.
new OutfitModStat(OutfitModStat.OutfitModType.Cooldown, 0, -0.2f, 0, false),
  • This will reduce cooldowns by 20%.

Outfit, continued

only a few parameters left in the Outfit class

        false, //isUnlocked
        false) //isLeveling

Pretty self explanatory

  • Set isUnlocked to true if you want this robe unlocked by default.
    • Otherwise it will be sold at Savile's shop like all the other robes.
  • Set isLeveling to true if you want your stat modifiers to level up like the Level robe
    • That is, theoretically. I have actually never tried this so if you want let us know how it goes.

Finally, Register your outfit

LegendAPI.Outfits.Register(coolOutfitInfo);

Ya done.

This will add your OutfitInfo to LegendAPI, and from there it'll handle the rest, adding your new Outfit to the game.

Not So Basics

Now that you know how to make a basic robe that changes stats, here's where you can learn to make more complex robes

  • custom effects
  • custom descriptions
  • custom unlock requirements
OutfitInfo coolerOutfit = new OutfitInfo()
{
    name = "Cooler Guy",
    outfit = new Outfit(
        "ModName::OutfitID", //newID
        1, //newColorIndex
        new List<OutfitModStat>
        {
            new OutfitModStat(OutfitModStat.OutfitModType.Health, 100, 0, 0, false),
            new OutfitModStat(LegendAPI.Outfits.CustomModType, 0, 0, 0, true),
            // more OutfitModStats here
        },
        true, //isUnlocked
        false), //isLeveling
    customDesc = (showStats) =>
    {
        string description = "- Gives Cool Effect";

        if (showStats)
        {
            string statString = "+ 10 %";
            string formattedStats = $"<color=#009999>( </color><color=#00dddd>{statString}</color><color=#009999> )</color>";

            description = $"- Gives Cool Effect {formattedStats}";
        }

        return description;
    },
    customMod = (player, isEquipping, onEquip) =>
    {
        if (isEquipping)
        {
            //do fun things to the current `player`
        }
        else
        {
            //undo the fun things we did with the current `player`
        }
    },
    unlockCondition = () =>
    {
        return true;
    }
};

Custom OutfitModStat

Everything up to the outfit is the same. We'll note an important change here:

    outfit = new Outfit(
        "ModName::OutfitID", //newID
        1, //newColorIndex
        new List<OutfitModStat>
        {
            new OutfitModStat(OutfitModStat.OutfitModType.Health, 100, 0, 0, false),
            new OutfitModStat(LegendAPI.Outfits.CustomModType, 0, 0, 0, true),
            // more OutfitModStats here
        },
        true, //isUnlocked
        false), //isLeveling

In order to have a custom description and/or a custom effect, you'll need an OutfitModStat of type LegendAPI.Outfits.CustomModType.
This is what tells legendAPI to look for further instruction

Custom Description

    customDesc = (bool showStats, OutfitModStat modStat) =>
    {
        string description = "- Gives Cool Effect";

        if (showStats)
        {
            float statValue = modStat.multiModifier.modValue * 100;
            string statString = $"+ {statValue} %";
            string formattedStats = $"<color=#009999>( </color><color=#00dddd>{statString}</color><color=#009999> )</color>";

            description = $"- Gives Cool Effect {formattedStats}";
        }

        return description;
    },

customDesc is of type Func<bool, string>. It takes in a bool and OutfitModStat and returns a string for the description.

In the game there are two descriptions.

  • One when you're just looking at the robe in menus, e.g. "- increases max health"
  • One when you're looking at the robe in your inventory screen, e.g. "- increases max health ( + 10 % )"

The showStats parameter is what decides this. I recommend copying what I have there for the formattedStats variable. It's set up to format your text in the way the stats text looks in the game.
The modStat parameter is your custom OutfitModStat that you passed in above. When you upgrade your cloak with savile, he will increase the stats of this OutfitModStat by x1.5, so we grab this value to get the right stat for our description.

If you don't want to bother with any of this, you can simply return a string you want for the description and move on

customDesc = (showStats, modStat) => { return "- Gives Cool Effect"; }

Aside

Here we have made use of lambda functions, or arrow functions, or anonymous functions, or whatever you wanna call them.
This is where I should probably say Learn some C#, but I'll try to explain it here. Skip past this section if you already know what these are.

The variable customDesc is of type Func. Which means this variable is a function.
You can create a function elsewhere and plug it in, or, what we do instead, is use the lambda operator => to create an anonymous function right here.
They look like this:

(parameter1, parameter2) => {
    //function body
}

You specify your parameter names in parenthesis then use the => operator and follow with the code block of your function.
The types of these parameters will be inferred based on the func<> or action<> you're using.

Imagine that this is creating a function that looks something like this:

void SomeFunction(bool parameter1, bool parameter2){
    //function body
}

In our example, customDesc is of type Func<bool, OuftitModStat, string>. This Func passes in the first two values, and returns the last value. Our anonymous function would look something like this:

(parameter1, parameter2) => {
    return "somestring"
}

This equates to a function that would look something like

string SomeStringFunction (bool parameter1, OutfitModStat parameter2) {
    return "somestring"
}

Somewhere in LegendAPI's code, it's going to invoke the customDesc variable with the proper parameters, and it's going to call our anonymous function here. This lets us have some logic behind what we return, rather than simply plugging in a variable that doesn't change.

Did this help? let me (thetimesweeper on discord) know if it helped or not, I'm curious.
If not, I'm sure you can find some better resources/tutorials online about this. It takes a little getting the hang of but you got it.


Custom Modification

    customMod = (Player player, bool isEquipping, bool onEquip, OutfitModStat outfitModStat) =>
    {
        if (isEquipping)
        {
            //do fun things to the current `player`
        }
        else
        {
            //undo the fun things we did with the current `player`
        }
    },

This customMod variable is of type Action<Player, bool, bool>. It takes in three parameters and returns nothing. This is the work horse of the custom functions you want to put on your robe.

This gets called by LegendAPI whenever the robe is equipped, unequipped, or updated on your character.

  • player is the reference to the player that equipped or unequipped this.
  • isEquipping is as it sounds, true when equipping and false when unequipping the robe
  • onEquip is a little complicated. You can probably just ignore it.
    • It's always true, and the only time it's false is for level robe when an enemy is defeated. What it means when it's false is it's not being equipped or unequipped. it's simply updating stats.
      • did that make sense? no? thought as much. Again you can just ignore it

What you do with this code is up to you! It's now really up to you to figure out how to do what you want to do.
Open up DNSpy and see how the existing robes do certain functions, see how relics do certain functions, mess with the player in any way you like!

Try this example it's funny

    customMod = (Player player, bool isEquipping, bool onEquip, OutfitModStat outfitModStat) =>
    {
        if (isEquipping)
        {
            float scaleValue = outfitModStat.overrideModifier.modValue;
            player.transform.localScale = new UnityEngine.Vector3(scaleValue, scaleValue, scaleValue);
        }
        else
        {
            player.transform.localScale = new UnityEngine.Vector3(1, 1, 1);
        }
    },

Custom Unlock Condition

    unlockCondition = () =>
    {
        return true;
    }

unlockCondition is of type Func<bool>. It takes in nothing and returns a bool for whether you want the robe to be locked or unlocked.

  • This part depends on what you want to do. In this code, check for the condition you want to lock your robe behind.
  • Once the condition is met, it'll show up in Savile's shop like the other robes.

That should be it

If you have any questions, head over to the Wizard of Legend discord (#modding-extravaganza).
Looking forward to seeing what you create!

⚠️ **GitHub.com Fallback** ⚠️