Custom Content: Robes - WoL-Modding-Extravaganza/WoLWiki GitHub Wiki
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
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.
Let's go line by line and see what's happening.
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:
- In it, we define a few fields to get our
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 = 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
- Long story short, this is what color your robe is.
- There are 32 in the base game, but for general purposes you can stick to 0-19.
- If you want to create your own custom palette, you can use this website to create your own robe https://tailoroflegend.epizy.com/?i=1
- Then add a dependency to this mod: https://thunderstore.io/c/wizard-of-legend/p/TheTimesweeper/CustomPalettes/
- Then, as described on that page, use one of its functions to add your palette image
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 settingaddValue
to100
.- So this outfit adds 100 health.
More Examples:
new OutfitModStat(OutfitModStat.OutfitModType.Damage, 0, 0.1f, 0, false),
- Modifying
OutfitModStat.OutfitModType.Damage
, settingmultiValue
to0.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%.
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.
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.


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;
}
};
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
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"; }
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.
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
- 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.
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);
}
},
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.
If you have any questions, head over to the Wizard of Legend discord (#modding-extravaganza).
Looking forward to seeing what you create!