-
Notifications
You must be signed in to change notification settings - Fork 1
Adding Magical Properties
Like most custom registrations, adding custom magical properties is done via the @Register
decorators.
This documentation's examples will describe adding magical properties for the "Odd Magicks" mod.
Let's say we want to add a magical property called "floaty" which reduces the stamina that jumping takes.
export default class OddMagicks extends Mod {
@Register.magicalProperty("Floaty", {
isApplicable: () => true,
getInfo: item => ({
max: 100,
value: () => item.island.seededRandom.intInRange(100),
}),
})
public readonly magicalPropertyFloaty: MagicalPropertyType;
}
This method is called to determine whether this custom magical property can be applied to a given item. In the example above, true
is returned, which makes the "floaty" magical property always applicable — any item can have it.
But wait... is that really what we want? It probably makes more sense if "floaty" only works on equipment:
isApplicable: (item, description) => !!description.equip,
This makes it so that the magical property can only be applied to items that are equippable. We'll make it so that the magical property only takes equipped items into account in our implementation, when we get there.
When magical properties are manipulated by the player via actions that add them and whatnot, they all go through getInfo
, which is generated for the item that will have/does have the magical property. In other words, getInfo
allows for item-specific magical property configuration.
In the example above, we set the maximum value for the magical property to 100, and make the random value generator return an integer between 0 and 100 (inclusive).
But hmm... how do we want to use the floatiness value? What if we make it so that each floatiness piece of equipment adds additional floatiness, and you can have anywhere between 0% floatiness and 90% floatiness. Let's say if a player maxes out four pieces of equipment with floatiness, they'll have enough to reach the cap.
How about this:
getInfo: item => ({
max: 0.3, // ie 30% floatiness
value: () => 0.05 // base 5% floatiness
+ item.island.seededRandom.float() * 0.2, // plus 0-20% additional floatiness generated by chance!
}),
Making the max
value greater than the maximum value that can be generated means the magical property "upgrading" system can increase the magical property beyond its natural limit, which is kinda fun I think!
- As most things, there's a
magicalPropertyType
dictionary we can extend in the english file to include our new magical property. The name generated is consistent with other mod registrations —mod<your mod name><your registration name>
.
"magicalPropertyType": {
"modOddMagicksFloaty": ["floatiness", "A strange wind appears to hold you aloft, reducing the stamina it requires to jump."]
}
This is a convenient method we can inject into in the ItemEquipInfo
"info-provider", which is what generates the magical property on-equip effects in item tooltips. All we need to do is tweak the return value so that it includes our magical property.
@InjectObject(ItemEquipInfo.methods, "getMagicalEquipTypes", InjectionPosition.Post)
protected onItemEquipInfoGetMagicalEquipTypes(api: IInjectionApi<typeof ItemEquipInfo["methods"], "getMagicalEquipTypes">, info: IGetUseInfo<typeof ItemEquipInfo>) {
api.returnValue?.add(this.magicalPropertyFloaty);
}
Time to look in the game and see what we get!
It's there! But it's giving the raw value instead of a percentage. Can we fix that?
Coincidentally, there's a helper we can inject into for that, too!
@InjectObject(ItemEquipInfo.methods, "isMagicalPropertyPercentage", InjectionPosition.Post)
protected onItemEquipInfoIsMagicalPropertyPercentage(api: IInjectionApi<typeof ItemEquipInfo["methods"], "isMagicalPropertyPercentage">, info: IGetUseInfo<typeof ItemEquipInfo>, type: MagicalPropertyType) {
if (type === this.magicalPropertyFloaty) {
api.returnValue = true;
}
}
Testing in game...
Amazing! Now we just need the magical property to actually do something.
We want to tweak the Jump action, and lucky for us, all we need to do is inject into a single method again. This time we'll inject into Jump.canUseHandler
, which is what determines if the Jump action is usable. We need to do it here because the jump action will be prevented if the player doesn't have enough stamina, and we're reducing the stamina requirement.
@InjectObject(Jump, "canUseHandler", InjectionPosition.Post)
protected onJumpCanUseHandler(api: IInjectionApi<typeof Jump, "canUseHandler">, action: IActionHandlerApi<Human, IJumpCanUse>) {
const canUse = api.returnValue;
if (!canUse?.usable && canUse?.message !== Message.TooExhaustedToJump) {
// do nothing, the jump failed for some other reason than not enough stamina
return;
}
// get a list of the player's equipped items, and add all the floaty values together
const floatyAmount = action.executor.getEquippedItems()
.map(item => item.magic.get(this.magicalPropertyFloaty) ?? 0)
.splat(Math2.sum);
const stamina = action.executor.stat.get<IStatMax>(Stat.Stamina)!;
const jumpStamina = Math.floor(((10 + action.executor.getScaledWeight() / 4) / 75) * stamina.max * Math.max(0.1, 1 - floatyAmount));
// this formula is pulled from the base game's Jump, and all we do is reduce based on floaty ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
if (stamina.value < jumpStamina) {
return api.returnValue = {
usable: false,
message: Message.TooExhaustedToJump,
};
}
const jumpPosition: IVector3 = {
x: action.executor.x + (action.executor.direction.x * 2),
y: action.executor.y + (action.executor.direction.y * 2),
z: action.executor.z,
};
return api.returnValue = {
usable: true,
stamina,
jumpStamina,
jumpPosition,
};
}
And there we go... magical property complete! What else could we do...?
Magical properties don't need to just be a singular property for each registration. You can also have a magical property "sub" type. A base game example of this would be MagicalPropertyType.Skill
, which can have any SkillType
as a subtype.
(TODO finish this guide)
Getting Started
- Introduction
- Prerequisites
+mod create
&+mod update
- mod.json
- Extracting Assets
- Resources & Examples
- Frequently Asked Questions
Mod Content
Script Documentation
- Using Translations
- Registrations
- Event Handlers
- Injection
- Adding Items
- Adding Doodads
- Adding Creatures
- Adding Magical Properties
- Actions & Multiplayer
- Adding Dialogs
- Context Menu/Action Bar Actions
- Inter-mod Registries
(apologies for all the missing guides, we'll get to them at some point)