Skip to content

Ferny's Modding Corner

FernyFer edited this page Aug 19, 2025 · 29 revisions

I'm going to be sharing tons of random bits of information I've learned while creating my Ferny Fantasy IX gameplay overhaul mod. From creating new status effects, adding new icons to the game, and full on coding enemy AI.

Field Script for adding custom new items to treasure chests and events

AddItem( ConvertKeyItemId(X), 1 ) - Adds a new KEY ITEM, where X is the item ID in Hades Workshop.

AddItem( ConvertRegularItemId(X), 1 ) - Adds a new REGULAR ITEM, where X is the item ID in Hades Workshop.

SetTextVariable( 0, X) - Used for the text popup window for base game items when receiving an item, where X is the item ID in Hades Workshop.

SetTextVariable( 0, ConvertRegularItemId(X) ) - Used for the text popup window for custom items when receiving an item, where X is the item ID in Hades Workshop.

Battle Patch - Stealable Items and Enemy Item Drops

Battle Patch Wiki

When adding new custom items to enemy drops via Battle Patch you can only have 2 drops total. No idea why this is a limitation, but it is.

WinItems: 25020 25007 255 255 - The 1st two items are the only ones that will drop, you notate the last two with 255 because that is a "blank" entry, so nothing will drop there.

WinItemRates: 256 256 0 0 - The item drop rate chances are here. 256 = 100% chance to drop, 128 = 50% and so on and so forth. The last two entries won't matter since only 2 drops can happen anyways.

Here is an example of stealable items. From LEFT to RIGHT, the RIGHT entry is the most "Rare" item. The 255 is a "blank" entry, which means there is no item there. 256 = 100% chance to steal the item 25023. The 0 entries means there is zero chance to steal because there is no item in that slot.

StealItems: 255 255 255 25023

StealItemRates: 0 0 0 256

Battle Patch - Adding CUSTOM STATUS effects to enemy attacks

In Hades Workshop, you can't have enemies apply new custom status effects, so you have to do this via the Battle Patch.

  • First, you must create the status effect: Status Effect Modding Wiki
  • Second, you add the status effect to the StatusSets.csv
  • Third, you use the following code to attach the status effect to an enemy attack: AddStatusNo: X - where X is the ID of the status you added to the StatusSets.csv. For example, in the entry below, during the Black Waltz 2 fight (battle ID 294 in Hades Workshop), I made his FIRE spell, which is attack ID 0, add a new custom status I created called Burn. Burn is ID# 1017 in StatusSets.csv.
// Black Waltz II < --- This is commented out by the //
== Battle: 294 == <--- This is the Battle ID in Hades Workshop
= Attack: 0 = <--- This is the attack ID in Hades Workshop
AddStatusNo: 1017 <--- This is the code that applies the status effect to his attack

Do note - you must set the ACCURACY of the status being applied like any other status effect in Hades Workshop.

Ability Features - New/Edited Support Abilities

Create an SA or even add a the ability to pierce/ignore a portion of the target's DEFENCE armor during attacks.

This example adds the armor piercing effect to any character with SA 12 equipped.

>SA 12 Penetrator ~~All attacks ignore 5% of Target's Defences~~
Ability WhenCalcDamage EvenImmobilized
[code=Condition] CasterIsPlayer && !TargetIsPlayer && (EffectFlags & (BattleCalcFlags_Miss | BattleCalcFlags_Guard)) == 0 [/code]
[code=DefencePower] DefencePower * 0.95 [/code]

You can also make it a global modifier and add it to certain abilities without the need for the SA like so:

>SA Global+ ~~ 10% Armor Pen~~
Ability WhenCalcDamage EvenImmobilized
[code=Condition] AbilityId == X [/code] // Where X is the ID of the ability
[code=DefencePower] DefencePower * 0.90 [/code]

In FernyFantasyIX, I stopped Chemist from doubling the heals from Auto-Potion for #BalanceReasons.

>SA 27 Chemist
Ability WhenCalcDamage EvenImmobilized
[code=Condition] (ScriptId == 69 || ScriptId == 70) && (CommandId != BattleCommandId_AutoPotion) [/code]

When using the AA [Patch] feature, you have to use a different syntax to check if a character has a current status effect than the one used in SA features. The code below shows you an example of it in action:

SA Version: CheckAnyStatus(CasterCurrentStatus, BattleStatus_STATUSIDHERE)

AA Version: CheckAnyStatus(GetUnitProperty(CharacterId_CHARACTERIDHERE, 'CurrentStatus'), BattleStatus_STATUSIDHERE)

In the example below, Amarant's new single target Chi Strike is replaced with his multi-target hitting version when he is in Trance.

>AA 148 Base Chi Strike -> Trance Chi Strike
[code=Patch] CheckAnyStatus(GetUnitProperty(CharacterId_Amarant, 'CurrentStatus'), BattleStatus_Trance) ? 35038 : -1 [/code]

Ability Features - Enemy Damage Scaling

Want to add a bit of enemy scaling to your mod without needing to go in and separately edit EVERY single enemy entry? Here's a quick and dirty method that surprisingly works well with the way the vanilla experience was balanced. Obviously, balance and difficulty will vary, so figure out what formula works for you.

In this example, the damage the player takes will scale by about 10% every 5 levels and then cap off at 2x damage taken at level 50. The damage no longer scales after level 50.

>SA GlobalEnemy+ ~~ Player damage taken scaler ~~         
Ability WhenCalcDamage EvenImmobilized                                                      
[code=AttackPower] Min((AttackPower * 2), (AttackPower * (1 + (TargetLevel/50)))) [/code]

Note: If you wanted to increase the "max" that this formula scales to, you would change the 2 in the (AttackPower * 2) code to whatever number you wanted.

Ability Features - Specific Spell/Ability Damage Scaling or bonuses

Some abilities scale poorly in the game. Here's an easy solution to keep them powerful even as you progress through the game. In this example, I've created a damage scaling factor for Freya's Jump and Trance Jump abilities. This formula will scale the damage of her Jumps based on 3x her current Level and Strength.

>SA Global+ ~~ Jump DMG Scaler ~~
Ability WhenCalcDamage EvenImmobilized
[code=Condition] AbilityId == 185 || AbilityId == 186 [/code]
[code=AttackPower] AttackPower + ((AttackPower * (CasterLevel * 3 + CasterStrength)) / 200) [/code]

In Dissidia Final Fantasy Opera Omnia (RIP), Freya's Reis Wind ability had a small initial heal. Let's make it a thing in FF9! In Hades Workshop change the Effect code from 103 - Good Status to 10 - Magic Heal. You'll need to add some POWER to it, depending on how strong you want the heal to be. Now, in Ability Features you'll need to re-add the skill's ability to apply Regen:

>SA Global+ ~~ Reis Wind Regen Application~~
Ability WhenBattleScriptEnd EvenImmobilized 
[code=Condition] (AbilityId == 118) [/code] // Ability ID of Reis Wind
[code=CasterCurrentStatus] CombineStatuses(CasterCurrentStatus, BattleStatus_Regen) [/code] // The code for adding Regen

The Lancer ability changes up from one FF game to the next. In FF9, it didn't have a drain component to it. Let's change that. Now Lancer will heal Freya for 1/5 of the damage it deals!

>SA Global+ ~~Lancer HP Leech~~
Ability WhenBattleScriptEnd EvenImmobilized
[code=Condition] (AbilityId == 117) && CasterIsPlayer && !TargetIsPlayer && (EffectFlags & (BattleCalcFlags_Miss | BattleCalcFlags_Guard)) == 0 [/code]
[code=EffectCasterFlags] CalcFlag_HpDamageOrHeal [/code]
[code=CasterHPDamage] HPDamage / 5 [/code]

Ability Features - Change a spell's animation as a character levels up

This one is pretty cool. This allows the animation that plays to change as the caster levels up.

The animations are:

  • 436 if the caster's level is higher than 50.
  • 125 if the caster's level is higher than 25, but lower than 50.
  • -1 this means the default entry for anything below level 25.

The animation ID's can be found here: https://github.com/Albeoris/Memoria/blob/main/Assembly-CSharp/Memoria/Data/Battle/SpecialEffect.cs

>AA 35073 Rebuke - Animation changer
[code=SpecialEffect] CasterLevel > 50 ? 436: (CasterLevel > 25 ? 125 : -1) [/code]

Ability Features - Allow Magic Spells to Critically Strike

Physical attacks can crit, but Magical spells cannot. Let's fix that with this code:

>SA Global+ ~~ Magical Crit BASE ~~
Ability WhenBattleScriptStart EvenImmobilized
[code=Condition] ((AbilityCategory & 16) != 0) && (GetRandom(0, 100) < (CHANCE TO CRIT FORMULA GOES HERE) [/code]
[code=EffectTargetFlags] EffectTargetFlags | CalcFlag_Critical [/code]
Ability WhenBattleScriptEnd EvenImmobilized
[code=Condition] ((AbilityCategory & 16) != 0) && (EffectTargetFlags & CalcFlag_Critical) != 0 [/code]
[code=HPDamage] HPDamage * (DAMAGE MULTIPLIER HERE) [/code]

In Vanilla FF9, Critical Hits deal 2x damage, but Magic has some pretty nice modifiers to them, so you probably don't want to double the damage a magical critical hit deals. I found adding about 30% more damage to a magical critical hit to be a nice middle ground: [code=HPDamage] HPDamage * 1.3 [/code]

Ability Features - Custom Statuses

Ability Features Wiki

This is how you reference a Custom Status via Ability Features: BattleStatus_CustomStatusX - where X is the ID of the custom status.

CheckAnyStatus(TargetCurrentStatus, BattleStatus_CustomStatus24) - Checks to see if the TARGET is currently inflicted with CustomStatus24.

CheckAnyStatus(CasterCurrentStatus, BattleStatus_CustomStatus22) - Checks to see if the CASTER is currently inflicted with CustomStatus22.

RemoveStatuses(TargetCurrentStatus, BattleStatus_Shell, BattleStatus_CustomStatus15) - Removes Shell and _CustomStatus15 _from the TARGET.

CombineStatuses(TargetCurrentStatus, BattleStatus_Regen, BattleStatus_CustomStatus5) - Applies Regen and CustomStatus5 to the TARGET.

TargetCurrentStatus and CasterCurrentStatus are interchangeable in these examples, depending on if you are trying to apply the status to the CASTER or your TARGET.

Battle Effects - "Quickfill" the ATB

You must have prior knowledge in editing battle effect scripts and using the Memoria.dll compiler. In Ferny Fantasy IX I made it so that certain actions didn't fully delete the ATB Gauge, but rather, set it back to a certain amount after the action was taken. This is the code to do so:

if ((_v.Command.AbilityId == (BattleAbilityId)X) && _v.Command.IsATBCommand) //  Where X is the ID of the ability
      {
            _v.Caster.AddDelayedModifier(
            caster => caster.CurrentAtb >= caster.MaximumAtb,
            caster =>
      {
            if (!caster.IsUnderAnyStatus(BattleStatusConst.StopAtb) && caster.CurrentAtb < caster.MaximumAtb * 50 / 100)
            caster.CurrentAtb += (Int16)(caster.MaximumAtb * 50 / 100); // 50 / 100 means 50% of the ATB gauge is filled. 70 / 100 would mean 70% 
      }
    );
}