Tag Archives: Paper Mario TTYD

Demystifying the Mystic: The Mechanics Behind Merlee

Mario pondering purchasing a package of Merlee's spells.

Merlee, a recurring character from the ’00s Paper Mario games, is a fortune-teller who speaks in riddles and gives Mario various boons at random intervals. These effects can be timely at their best and pointless at their worst, but despite the unpredictability of their activation, they’re at least a fun curiosity. Merlee’s magic works fairly similarly in the first two Paper Mario titles, but there’s a surprising amount of little differences between them, and quirks in each, so I thought it’d be worth doing a deep dive on both.

Overview

In both games, Merlee sells packages of spells (or “curses”) that activate a certain number of times before running out, for increasing duration (and oddly enough, increasing cost per spell for the more expensive packages): 5 spells for 5 coins, 10 spells for 20 coins, or 20 spells for 50 coins.

The effects of these spells include:

  • ATK-Up: Mario’s next jump or hammer attack will have its attack power raised by 3*.
  • DEF-Up: On Mario’s next defending turn, his defense power will be raised by 3.
  • EXP Bonus: Experience points earned from a fight will be doubled.
  • Coin Bonus: Coins dropped after a fight will be tripled. (This effect stacks multiplicatively with Money Money in 64, but additively in TTYD; e.g. having 2 Money Moneys equipped will give you an additional +2x from the curse and +2x from the badges, for a total of 5x, not 9x).

* Beneficiaries of this curse might also include exploding Bulky Bob-ombs, if Mario opts to use a boosted Fire Drive on them.

Broadly, each curse has a turn count before the next curse is allowed to activate, but the specifics of when the turn count elapses, as well as when the curse type is determined, differs a fair bit between the games, so let’s just tackle the games individually. Let’s start with The Thousand-Year Door, since its mechanics are generally simpler:

Merlee in The Thousand-Year Door

In TTYD, Merlee’s spells choose a turn count before activation, then only when those turns have elapsed is a type of curse chosen. This spell is then activated at the earliest possible time, persisting across battles, if necessary.

The current state of Merlee’s spells is stored in three variables in “PouchData“:

  • Number of spells remaining (this doesn’t include the current spell, if a turn count is counting down or if a specific spell has been chosen but not activated.)
  • Turn count before the current spell’s type is chosen. Will be set to -1 if the previous spell was activated and a new turn countdown is ready to start.
  • Type of curse to activate next, determined once the turn count reaches 0 for a given spell.

Purchasing a new package

On purchasing a new package of spells, if the current number of spells remaining variable is less than the total number in the purchased package, then the number of spells remaining will be set to that number, and the current curse (either in progress or stored) will be lost. For example, if you have 11 curses remaining, and 3 turns until a new one will be chosen, buying the most expensive package will leave you with 20 curses and no turn count in progress.

If the current number of spells remaining is greater than or equal to the number in the purchased package, nothing will happen; both the current curse progress and the number of banked spells will remain as it is. (Merlee’s more than happy to take your coins regardless.)

Turn count advancement

In-battle, at the start of every turn from the 2nd onward, the spell turn count will progress if there isn’t one currently waiting to activate.

  • If the number of remaining curses is non-zero and the turn count is -1 (i.e. there isn’t a turn count in progress or a spell waiting to activate), a new turn count is picked, from 5 to 10 inclusive, and the remaining number of spells is decremented.
  • If instead, the turn count is non-zero, it is decremented. If it hits 0 on a turn, then a curse type is picked randomly (30 / 30 / 20 / 20% chance for ATK, DEF, EXP or coins), and stored until it is able to be used. From this point on, the spell will not pick a new turn count until the stored curse is activated.

Curse activation

A stored curse will activate at the earliest opportunity, waiting across battles if necessary:

  • ATK-Up: whenever Mario next attacks with a jump or hammer move.
  • DEF-Up: whenever the enemies are next given a turn to attack.
  • EXP Bonus: whenever a fight is finished. The bonus will activate even if 0 EXP are earned from the fight (and will double the ‘pity Star Point’ if no Star Points were going to be earned, but Mario is not yet level 99).
  • Coin Bonus: whenever a field encounter is finished.

Once a curse activates, the turn count is set to -1, and the stored curse type to 0, signaling that a new turn count should be picked if there are curses remaining.

Quirks / Exploits

Curses being stored across battles means that you can get stuck with a stored curse for a very long time if you never give it a chance to activate (e.g. never letting Mario attack with an ATK-Up curse, or never fighting normal field encounters with a coin curse stored).

You would think that the curse type not being determined until the turn count elapses would limit the number of ways you could abuse foreknowledge of a curse with save points, but you can still set up particular effects to happen on the first possible turn of a battle (and even do so for most curses without using save points, provided you’re okay with wasting curses). Some examples:

  • ATK-Up: Let 12 turns pass in a field encounter without attacking with Mario, then finish the fight. If no DEF, EXP, or coin curses occur, you have an ATK curse stored.
  • DEF-Up: Defeat 11 consecutive field encounters with Mario attacking on the second turn to finish the fight. If no ATK, EXP or coin curses occur, you have a DEF curse stored.
  • Coin-Up: Let 12 turns pass in a non-field encounter (e.g. fights in the Glitz Pit), and finish the fight. If no ATK, DEF or EXP curses occur, you have a coin curse stored.
  • EXP-Up is impossible to isolate without it possibly being a coin curse instead, but you can do similar steps to the above curses, attack once with Mario and then run away to have it be a 50-50 shot. You could also just use a save point once you’re sure some curse is stored, then verify if you have the EXP one.

Alternatively, if you want to trigger an ATK or DEF effect on a given turn and don’t mind banking on the random 30% chance of getting the right effect, you can save after the turn count is rolled the turn after a previous curse activates, then keep clearing fights on the 2nd turn with a Mario attack until you get the ATK/EXP/coin curse to trigger, or the DEF curse to trigger on turn 1 of the following fight; the number of fights it took to trigger the curse would be equal to the turn count you saved with.

Merlee in 64

In 64, Merlee chooses a turn count and spell type at the same time, rather than only choosing the latter after the turn count elapses. Once the turn count elapses, the spell is only stored for the current fight, meaning that you can’t rely on save-point-less manipulation of an effect.

The current state of Merlee’s spells is stored in three variables that persist in the player’s data (the equivalent of TTYD’s “pouch”):

  • Number of spells remaining (this does include the current in-progress spell)
  • Turn count before the current spell’s type is allowed to activate.
  • Type of upcoming curse

and one variable in the battle data, which does not persist after the battle ends:

  • Type of curse ready for activation (set once the current curse’s turn count elapses).

Purchasing a new package

When purchasing a new package, the number of spells the player has is set to the larger of its current value and the number of offered by the package. In addition, the current curse’s progress is wiped, picking a new one with equal probability of ATK/DEF/EXP/coins, and a turn count from 1-3. For example, if you have 12 curses remaining, including the current one (which is an ATK curse set to be stored for activation in 8 turns), after purchasing the 5-spell package, you’ll still have 12 curses, but the current curse will be a new one with a turn count of 1-3 turns.

Turn count advancement

At the start of every turn in battle (including turn 1:

  • If the turn count is non-zero, the turn count decrements. If it hits 0 in doing so, the current curse is stored, and the remaining spell count decrements.
  • If the turn count is 0 and there are spells remaining, a new curse is chosen (ATK/DEF/EXP/coin with roughly 30/30/20/20 probability), and a turn count of 6-16 inclusive (which will immediately decrement to between 5-15).

In addition, if the player first strikes with a Jump, Hammer or partner move (not Dizzy Attack), the Merlee curse will do additional checks:

  • If the current curse type is an EXP or coin bonus and has a nonzero turn count, the turn count decrements. If it hits 0 in doing so, the current curse is stored, and the remaining spell count decrements.
  • If the turn count is 0 and there are spells remaining, the a new curse is chosen with the same probability as above, but with a turn count of 5-10, or 5-13 for coin curses specifically. This turn count will also immediately decrement if the curse type chosen was an EXP or coin bonus.

Curse activation, quirks, etc.

A stored curse will activate at the earliest opportunity within or after winning a battle, under the same circumstances as TTYD’s. If a new curse is stored before a previous one (stored in the same battle) is able to activate, the old stored curse will be overwritten.

If the player ends a battle without allowing a stored ATK or DEF curse to activate, or ends the fight by running away or similar, the stored curse is wasted. If this occurs on the last spell of a package, then there will never be an indication that the spell’s power ran out, as there normally is supposed to be on the final curse’s activation.

If an EXP or coin curse is stored when finishing a fight that would not yield any EXP or coins, it won’t activate; however, the the current spell’s turn count will be set to 0, and the number of spells remaining increased by one, effectively picking a new curse in place of the current one in the next battle and resetting its progress. This might have been done to try to ensure that the player never silently ends a package of spells on a failed EXP/coin curse, perhaps (despite the aforementioned other ways this can occur).

Unlike TTYD, curses not being stored across fights means that you can never predict what’s coming next without using save points, but since the curse turn count and type are both determined at once, using trial-and-error with save points is much more powerful if you want to have any type of curse activate on a particular turn of a battle (up through turn 10 or so, at least).

Closing thoughts

That’s it for this relatively short article! Shout-outs once again to the PM64 decomp team for their super-well documented Merlee state variables, which helped fill in a lot of gaps in my knowledge (especially regarding first strikes and ‘wasted curses’). I might drop other shorter articles like this from time to time in the future.

Troublemakers’ Tactics: An Overview of Enemy Battle Scripts

At last, we’ve come to the final big area of Paper Mario: The Thousand-Year Door‘s battle system mechanics I’ve been meaning to cover, enemies’ battle scripts / “AI”. Naturally there’s a ton of complexity that goes into covering anything as broad as the entirety of enemies’ script code, so I’ll be covering mostly general trends, with a few examples and deep dives into a few particular areas.

If you’re interested in looking further into the script code yourself for any particular enemy or boss, my ttyd-utils GitHub repository has tools to dump all the EvtScripts in the game in a text format (using the ttydasm tool from PistonMiner’s tools repository). I’ll be showing some excerpts of these scripts throughout this article, so you can get a sense of what they look like; as a note, internally TTYD refers to scripts in this format as “events”, so I may use that term interchangeably.

Event Types

First off, all enemies (and in fact, all actors or ‘battle units’ in general) have a handful of different top-level scripts to perform certain functions:

  • Initialization event – Runs as soon as the actor is spawned, generally in the initial battle setup (but can be later, when an enemy spawns helpers, or such).
  • Entry event – Runs when the actor “enters” the battle, at the start of the “first act”.
  • Damage event – Runs whenever the actor receives a hit that could deal damage or inflict a status effect.
  • Phase event – Handles events that are intended to occur ‘between’ attacking phases.
  • Unison phase event – Similar, but all actors execute this simultaneously.
  • Attack event – For non-playable actors, handles their attacking action. (For player-controlled ones, this is done largely through a completely different system).
  • Confusion event – Handles the actor’s attacking action on turns that they are afflicted by the Confuse status.
  • Idle (“Wait”) event – Runs by default whenever the actor enters an idle state (after any of their other events finishes executing). Generally, this doesn’t actually do anything but change their animation to the idle pose.

In the course of a fight, actors will run their init event during battle loading, then their entry event at the start of the “first act” (entities spawned later in the fight will run their init events as soon as they are spawned). In each of the five phases of each turn, all actors will all run their unison phase events simultaneously, then their regular phase events individually. Enemies in particular will run their attack events after all phase events are finished in the fourth phase (or their confusion event if they get confused for that turn). At any point, if an actor receives a hit intended to deal damage or status, they will run their damage event concurrently with any currently executing events. My previous post on turn structure covers the execution order of these events in greater detail.

Actors additionally have a variable-length “data table” that serves as a map of additional events used for specific circumstances, such as being defeated, being countered by or countering an opponent with spikes / elemental hazards, getting flipped over or losing their wings, and so on.

Let’s take a look at examples of each of these types of events.

Initialization + Entry events

An actor’s init event generally sets what script to use for all of their other events, as well setting up any ‘work variables’ that need to be initialized to specific values, spawning any child actors needed, and so forth. Here’s Arantula’s init event, as an example:

# sets up remaining event types
callc [btlevtcmd_SetEventWait battle_event_cmd.o] -2 [wait_event unit_piders.o]
callc [btlevtcmd_SetEventUnisonPhase battle_event_cmd.o] -2 [unison_phase_event unit_piders.o]
callc [btlevtcmd_SetEventAttack battle_event_cmd.o] -2 [attack_event unit_piders.o]
callc [btlevtcmd_SetEventDamage battle_event_cmd.o] -2 [damage_event unit_piders.o]
callc [btlevtcmd_SetEventConfusion battle_event_cmd.o] -2 [attack_event unit_piders.o]
callc [btlevtcmd_SetEventEntry battle_event_cmd.o] -2 [entry_event unit_piders.o]

# additional initialization / processing
callc [btlevtcmd_SetUnitWork battle_event_cmd.o] -2 0 0
callc [btlevtcmd_SetUnitWork battle_event_cmd.o] -2 2 0
callc [btlevtcmd_SetUnitWork battle_event_cmd.o] -2 3 1
callc [btlevtcmd_SetUnitWork battle_event_cmd.o] -2 1 255
callc [piders_yarn_init unit_piders.o]
callsa [yarn_event unit_piders.o]

# end event (start idle event)
callc [btlevtcmd_StartWaitEvent battle_event_cmd.o] -2
return
end

If actors have an entry event, they all execute simultaneously immediately at the start of the battle’s “first act”, even before the First Strike occurs (if applicable). These include Mario and his partners walking into the battle scene, and the introductory cutscenes in most boss fights, but in theory normal enemies with unique entry animations (such as Piders and Arantulas) could utilize these as well. Notably, no actors that spawn later in a battle can have entry events, since they only ever run at the start of a battle.

Damage events

Generally, most actors just call the default “btldefaultevt_Damage” script on taking damage. This script will change the actor’s animation, and check whether any special sub-events should be executed based on the properties of the damaging hit. These might include events from the actor’s data table, or generic scripts for attacks with special on-hit effects such as Gale Force, knockback from Super Hammer, or “crushing” attacks like Flurrie’s Body Slam or the dragons’ stomp attack.

There are a ton of hit effects I don’t currently have documented, but relatively few enemies have unique gameplay-impacting effects that occur in their damage script (rather than their death script, or in their next phase / attack scripts); the few examples I can think of include:

  • Gold Fuzzy calling in the Fuzzy Horde for backup, if under 8 HP
  • The Fuzzy Horde losing members per hit, and running if enough damage is taken
  • Hooktail’s reactions to taking a hit with Attack FX R (but not the bargaining event after reducing her to 0 HP; this happens in her “death event”).
  • Hitting Rawk Hawk while on the ceiling knocking him to the floor
  • Lord Crump changing X-Naut formations at half health in his Chapter 5 fight
  • Hitting Cortez’s bone pile in phase 2 to make his head lower / rib cage open
  • Hitting Grodus’s staff to make his next attack have a chance to fail
  • Damaging Kammy causing her to fall off of her broom
  • Elemental attacks causing bomb enemies to immediately explode
  • Enemies with clones deleting them if the correct one is attacked
  • Lakitus dropping their held Spiny Egg

(Unison) Phase events

Phase events are generally used to set up changes in an actor’s state at the beginning of a turn, or between the player and enemy attacking phases.

Unison phase events in particular are most commonly used for all actors of a given type moving in formation, such as Piders moving between high and low positions, Yuxes spawning Mini-Yuxes, or Lakitus choosing to pull a Spiny Egg above their head. Here’s the Dark Puff unison phase event, for example:

# check if currently in turn phase 1 
callc [btlevtcmd_CheckPhase battle_event_cmd.o] LW(0) 0x4000001
if_int_eq LW(0) 0
  goto 99
endif

# check that actor isn't incapacitated, and isn't currently in a charged state
callc [btlevtcmd_CheckActStatus battle_event_cmd.o] -2 LW(0)
if_int_eq LW(0) 0
  goto 99
endif
callc [btlevtcmd_CheckPartsCounterAttribute battle_event_cmd.o] -2 1 1024 LW(0)
if_int_eq LW(0) 1
  goto 99
endif

# randomly choose to switch positions
callc [evt_sub_random evt_sub.o] 99 LW(0)
if_int_lt LW(0) 50
  callc [btlevtcmd_GetUnitWork battle_event_cmd.o] -2 0 LW(0)
  if_int_eq LW(0) 1
    callc [btlevtcmd_snd_se battle_event_cmd.o] -2 ["SFX_ENM_KUMO_MOVE4"] [F1194D80] 0 [F1194D80]
    callc [btlevtcmd_GetPos battle_event_cmd.o] -2 LW(0) LW(1) LW(2)
    setii LW(1) 40
    callc [btlevtcmd_DivePosition battle_event_cmd.o] -2 LW(0) LW(1) LW(2) 60 10 4 0 -1
    callc [btlevtcmd_SetHomePos battle_event_cmd.o] -2 LW(0) LW(1) LW(2)
    callc [btlevtcmd_OnPartsAttribute battle_event_cmd.o] -2 1 6291456
    callc [btlevtcmd_SetUnitWork battle_event_cmd.o] -2 0 0
  else
    callc [btlevtcmd_snd_se battle_event_cmd.o] -2 ["SFX_ENM_KUMO_MOVE5"] [F1194D80] 0 [F1194D80]
    callc [btlevtcmd_GetPos battle_event_cmd.o] -2 LW(0) LW(1) LW(2)
    setii LW(1) 10
    callc [btlevtcmd_DivePosition battle_event_cmd.o] -2 LW(0) LW(1) LW(2) 60 -10 4 0 -1
    callc [btlevtcmd_SetHomePos battle_event_cmd.o] -2 LW(0) LW(1) LW(2)
    callc [btlevtcmd_OffPartsAttribute battle_event_cmd.o] -2 1 6291456
    callc [btlevtcmd_SetUnitWork battle_event_cmd.o] -2 0 1
  endif
endif
...

Regular phase events are primarily used by bosses that get free actions between their ‘turns’, such as Lord Crump calling in the X-Naut platoon in the final phase of his chapter 5 fight:

# check if currently in turn phase 1
callc [btlevtcmd_CheckPhase battle_event_cmd.o] LW(0) 0x4000001
if_int_eq LW(0) 0
  goto 99
endif
...

# check that UW var 0 = 2 (i.e. in final AI phase of fight)
callc [btlevtcmd_GetUnitWork battle_event_cmd.o] -2 0 LW(0)
if_int_eq LW(0) 2
  # if so, call the X-Naut platoon back in
  callss [call_event unit_boss_kanbu3.o]
endif
...

or that have dialogue triggers, transformations or AI state changes upon reaching certain HP thresholds, such as Bonetail:

# check if currently in turn phase 3 (between player and enemy attack phases)
callc [btlevtcmd_CheckPhase battle_event_cmd.o] LW(0) 0x4000003
if_int_eq LW(0) 0
  goto 99
endif
...

# check UW var 0 (tracks Bonetail's AI phase)
callc [btlevtcmd_GetUnitWork battle_event_cmd.o] -2 0 LW(0)
switchi LW(0)
  case_int_eq 0
    # upon first reaching 1/2 health, set UW var 0 to 1
    callc [btlevtcmd_GetMaxHp battle_event_cmd.o] -2 LW(0)
    muli LW(0) 50
    divi LW(0) 100
    callc [btlevtcmd_GetHp battle_event_cmd.o] -2 LW(1)
    if_int_le LW(1) LW(0)
      callc [btlevtcmd_SetUnitWork battle_event_cmd.o] -2 0 1
    endif
  case_int_eq 1
    # upon first reaching 1/4 health, set UW var 0 to 2 and play dialogue cutscene
    callc [btlevtcmd_GetMaxHp battle_event_cmd.o] -2 LW(0)
    muli LW(0) 25
    divi LW(0) 100
    callc [btlevtcmd_GetHp battle_event_cmd.o] -2 LW(1)
    if_int_le LW(1) LW(0)
      callc [btlevtcmd_StatusWindowOnOff battle_event_cmd.o] 0
      callc [btlevtcmd_SetUnitWork battle_event_cmd.o] -2 0 2
      callc [evt_msg_print evt_msg.o] 2 ["tik_boss_15"] 0 -2
      callc [btlevtcmd_StatusWindowOnOff battle_event_cmd.o] 1
    endif
end_switch
...

The Shadow Queen makes particularly involved use of her true form’s phase event, checking in turn phase 1 whether her hands need reviving, in turn phase 3 for whether she wants to swap hand types, and during every turn phase in her invincible state for whether she’s received hits (in order to taunt the player).

Attack events

Finally, the attack event is where most of an enemy’s “AI” comes from; this is where they determine which attack or other action to take on their turn. This may be as simple as executing an attack directly for enemies with only one move, choosing randomly between a number of different attacks, or making different decisions based on their state (such as using a particular attack after a charge turn). Programming styles for both the attack choice and the actual attack execution vary dramatically; to keep this article from going wildly over my time budget I’ll spare most of the gory details, but I will go over a few simpler examples (and strongly suggest you check out the scripts yourself if you’re curious about anything else).

Examples

To start, Embers have relatively simple AI, randomly choosing between their three attacks:

# check whether to use item
callc [btlevtcmd_EnemyItemUseCheck battle_event_cmd.o] -2 LW(0)
if_int_ne LW(0) 0
  callss LW(0)
  callc [btlevtcmd_StartWaitEvent battle_event_cmd.o] -2
  return
endif

# do a random roll with the sum of the attacks' weights
setii LW(0) 50
addi LW(0) 30
addi LW(0) 20
subi LW(0) 1
callc [evt_sub_random evt_sub.o] LW(0) LW(1)

# based on the result (< 50, < 80 or >= 80), use the corresponding attack
setii LW(0) 50
if_int_lt LW(1) LW(0)
  setii LW(9) [weapon_bubble_attack unit_hermos.o]
  callss [normal_attack_event unit_hermos.o]
  goto 99
endif
addi LW(0) 30
if_int_lt LW(1) LW(0)
  setii LW(9) [weapon_bubble_fire_attack unit_hermos.o]
  callss [fire_attack_event unit_hermos.o]
  goto 99
endif
setii LW(9) [weapon_bubble_all_fire_attack unit_hermos.o]
callss [all_fire_attack_event unit_hermos.o]
goto 99
...

Dark Puffs have a bit more state to their AI, choosing to use their swoop attack or charge 50% of the time, then unleashing their lightning attack if charged:

# check whether to use item
callc [btlevtcmd_EnemyItemUseCheck battle_event_cmd.o] -2 LW(0)
if_int_ne LW(0) 0
  callss LW(0)
  callc [btlevtcmd_StartWaitEvent battle_event_cmd.o] -2
  return
endif

# check if has electric hazard, i.e. already charged
callc [btlevtcmd_CheckPartsCounterAttribute battle_event_cmd.o] -2 1 1024 LW(0)
if_int_eq LW(0) 1
  setii LW(9) [weapon_kurokumorn_thunder_attack unit_monochrome_kurokumorn.o]
  callss [thunder_event unit_monochrome_kurokumorn.o]
  goto 99
endif

# otherwise, 50% chance of attacking or charging
setii LW(0) 50
addi LW(0) 50
subi LW(0) 1
callc [evt_sub_random evt_sub.o] LW(0) LW(1)
setii LW(0) 50
if_int_lt LW(1) LW(0)
  setii LW(9) [weapon_kurokumorn_attack unit_monochrome_kurokumorn.o]
  callss [normal_attack_event unit_monochrome_kurokumorn.o]
  goto 99
endif
callss [charge_event unit_monochrome_kurokumorn.o]
goto 99

Flower Fuzzies are interesting, as they only require 2 FP to use their special attack, but they will heavily favor using their leech attack unless they have the full 3:

# check whether to use item
callc [btlevtcmd_EnemyItemUseCheck battle_event_cmd.o] -2 LW(0)
if_int_ne LW(0) 0
  callss LW(0)
  callc [btlevtcmd_StartWaitEvent battle_event_cmd.o] -2
  return
endif

callc [btlevtcmd_GetFp battle_event_cmd.o] -2 LW(0)
callc [btlevtcmd_GetMaxFp battle_event_cmd.o] -2 LW(1)
# if less than 2 FP, always use drain attack
if_int_lt LW(0) 2
  goto 10
endif
# if max FP, always use magic attack
if_int_ge LW(0) LW(1)
  goto 20
endif

# otherwise, weighted choice (5/6 of the time, use drain attack)
setii LW(0) 50
addi LW(0) 10
subi LW(0) 1
callc [evt_sub_random evt_sub.o] LW(0) LW(1)
if_int_lt LW(1) 50
10:
  callss [drain_attack_event unit_flower_chorobon.o]
  goto 99
endif
20:
callss [magic_attack_event unit_flower_chorobon.o]
goto 99

Finally, as an example of a boss that has differing attack patterns based on their HP (or unit variables tracking which dialogue triggers have been hit, in this case), here’s what Macho Grubba’s attack script looks like in its entirety:

# increment the number of attacks used this turn
callc [btlevtcmd_GetUnitWork battle_event_cmd.o] -2 0 LW(1)
addi LW(1) 1
callc [btlevtcmd_SetUnitWork battle_event_cmd.o] -2 0 LW(1)

# check whether Fast status is active, and reapply if so
callc [btlevtcmd_CheckStatus battle_event_cmd.o] -2 19 LW(0)
if_int_eq LW(0) 0
  setii LW(9) [weapon_gance_macho_speed unit_boss_macho_gance.o]
  callss [gance_macho_speed_attack_event unit_boss_macho_gance.o]
else
  # if LW(1) < 2; i.e. fewer than two text triggers, or in first phase of fight
  callc [btlevtcmd_GetUnitWork battle_event_cmd.o] -2 1 LW(2)
  if_int_lt LW(2) 2
    # if first attack this turn, choose which buff to apply
    if_int_le LW(1) 1
      callc [btlevtcmd_DrawLots battle_event_cmd.o] LW(0) 3 30 20 10
      switchi LW(0)
        case_int_eq 0
          setii LW(9) [weapon_gance_build_up unit_boss_macho_gance.o]
          callss [gance_build_up_attack_event unit_boss_macho_gance.o]
        case_int_eq 1
          setii LW(9) [weapon_gance_body unit_boss_macho_gance.o]
          callss [gance_body_attack_event unit_boss_macho_gance.o]
        case_int_eq 2
          setii LW(9) [weapon_gance_footwork unit_boss_macho_gance.o]
          callss [gance_footwork_attack_event unit_boss_macho_gance.o]
      end_switch
    else  # otherwise, use his body slam attack
      setii LW(9) [weapon_gance_attack unit_boss_macho_gance.o]
      callss [gance_attack_attack_event unit_boss_macho_gance.o]
    endif
  else  # second phase of fight
    # if first attack this turn, choose which buff to apply (incl. Charge)
    if_int_le LW(1) 1
      callc [btlevtcmd_DrawLots battle_event_cmd.o] LW(0) 4 25 20 10 10
      switchi LW(0)
        case_int_eq 0
          setii LW(9) [weapon_gance_build_up unit_boss_macho_gance.o]
          callss [gance_build_up_attack_event unit_boss_macho_gance.o]
        case_int_eq 1
          setii LW(9) [weapon_gance_body unit_boss_macho_gance.o]
          callss [gance_body_attack_event unit_boss_macho_gance.o]
        case_int_eq 2
          setii LW(9) [weapon_gance_footwork unit_boss_macho_gance.o]
          callss [gance_footwork_attack_event unit_boss_macho_gance.o]
        case_int_eq 3
          setii LW(9) [weapon_gance_posing unit_boss_macho_gance.o]
          callss [gance_posing_attack_event unit_boss_macho_gance.o]
      end_switch
    else  # otherwise, choose either his punch or backflip attack
      callc [btlevtcmd_DrawLots battle_event_cmd.o] LW(0) 2 10 10
      switchi LW(0)
        case_int_eq 0
          setii LW(9) [weapon_gance_rariat_attack unit_boss_macho_gance.o]
          callss [gance_rariat_attack_attack_event unit_boss_macho_gance.o]
        case_int_eq 1
          setii LW(9) [weapon_gance_body_attack unit_boss_macho_gance.o]
          callss [gance_body_attack_attack_event unit_boss_macho_gance.o]
      end_switch
    endif
  endif
endif
callc [btlevtcmd_StartWaitEvent battle_event_cmd.o] -2
return
end

Item Usage

As you could see in the former examples, most enemies have a check at or near the beginning of their script to see whether they should use their held item, if they have one. Enemies have some rudimentary AI for whether to use each type of item they can hold, with the items separated into the following categories:

  • Attack items – Enemies have a (20% * current turn count) chance of using these every turn. As an exception, if Koops is currently in the battle and incapacitated (either by sleep/stop/freeze status or being flipped), enemies will always use a held POW Block or Earth Quake. Presumably the idea was to keep him stunned, but the latter item doesn’t actually flip shell enemies.
  • Support status items – If an enemy holds one of these, they will use it if there is any target on its side (including itself) that doesn’t already have the status. For Power Punch specifically, there must be a target that isn’t already Huge and is currently able to act.
  • Attack status items – Similarly, the enemy is guaranteed to use these if either Mario or his partner is both able to act and not already afflicted by the status. Note that Ice Storm counts as an attack item, not a status attack item, so enemies are not guaranteed to use it turn 1.
  • HP / FP recovery items – If an enemy has one of these and any target has less than full HP or FP, respectively, they are guaranteed to use it. Enemies are not normally able to hold items that restore both HP and FP, but if they could, they would check for enemies with either missing HP or FP.
  • Status recovery items – If any target has Sleep, Stop, Dizzy, Poison, Confuse, Burn, Freeze, Tiny, Def-Down, Slow, or (oddly) Electric status, an enemy will use their held Tasty Tonic.

Notably, any non-restoration items not listed above (Point Swap, Spite Pouch, etc.) have none of the above functions assigned for their use, and as such could never be used by enemies, even if they were able to hold them.

Nearly all enemies are able to use items, including bosses that normally would never be able to hold them such as Blooper and Smorg (and their appendages, surprisingly); the only exceptions are:

  • Mini-Yuxes
  • Bulky Bob-ombs
  • Macho Grubba
  • Cortez (all forms, + weapons)
  • Grodus + Grodus Xes
  • Shadow Queen (both forms, + hands)
  • X-Fist + X-Punch
  • The X-Naut platoons in the Ch. 5 Lord Crump fight (which oddly can hold items)

Note that the heuristics above only determine whether an enemy will use their item, not on whom they will use it; that uses the same target selection functions as normal enemy attacks. Speaking of, let’s cover that now:

Target Selection

The script for each enemy attack almost always follows this general template:

# set the appropriate attack parameters
setii LW(9) [weapon unit_kuriboo.o]

# find and order all targets that the current attack can hit
callc [btlevtcmd_GetEnemyBelong battle_event_cmd.o] -2 LW(0)
callc [btlevtcmd_SamplingEnemy battle_event_cmd.o] -2 LW(0) LW(9)
# choose which target to hit first (only relevant for single-target attacks)
callc [btlevtcmd_ChoiceSamplingEnemy battle_event_cmd.o] LW(9) LW(3) LW(4)
# if no targets found, play confusion animation if Confused, or just end attack
if_int_eq LW(3) -1
  callc [btlevtcmd_CheckToken battle_event_cmd.o] -2 16 LW(0)
  if_int_ne LW(0) 0
    callss [subsetevt_confuse_flustered battle_event_subset.o]
    return
  endif
  goto 99
endif
...

The btlevtcmd_SamplingEnemy function picks out all valid targets for the attack based on the enemy’s alliance (as determined by btlevtcmd_GetEnemyBelong; if confused, the alliance is swapped). This involves looking at every part of every battle unit, and removing any that the attack’s targeting parameters disallow. After that, all remaining targets are sorted by X-position based on the attacking direction, and filtered down further by ‘frontmost only’ targeting, if necessary.

For instance, Goomba’s headbonk attack has the following targeting parameters:

0x01101260 AttackTargetClass_Flags
0x00000060 Cannot target neutral / system actors
0x00000200 Opposite alliance
0x00001000 Cannot target self
0x00100000 Only high-priority parts
0x01000000 Single-target

0x20001000 AttackTargetProperty_Flags
0x00001000 Jumplike attack range
0x20000000 Target the opposing direction ('front' = rightmost target)

Given this standard battle scene, the initial possible actors to target include the ‘system’ actor, Mario, Yoshi, the Goomba, and the Spiked Goomba, each of which only consist of 1 ‘part’. The “cannot target self”, “opposite alliance”, and “no neutral actors” flags filter this down to only Mario and Yoshi as possibilities (neither of them is out of range of Jump-like attacks), and since the opposing team’s direction is considered the front, Mario’s target is sorted before Yoshi’s.

For a more complicated example, let’s consider the first Dark Koopa in this layout, and assume that it’s afflicted by Confusion this turn:

Its attack’s targeting parameters are:

0x01101260 AttackTargetClass_Flags
0x01000000 Single-target
0x00000060 Cannot target neutral / system actors
0x00000200 Opposite alliance
0x00001000 Cannot target self
0x00100000 Only high-priority parts

0x21002000 AttackTargetProperty_Flags
0x00002000 Hammer-like attack range
0x01000000 Frontmost target only
0x20000000 Target the opposing direction ('front' = rightmost target)

Since the Koopa is confused, the “opposite alliance” flag gets swapped for keeping targets in the same alliance. This in combination with the other Class flags leaves the three other enemies as potential targets, and the Hammer-like distinction removes the Paratroopa as an option.

Since the original attack considers the “front” to mean right, and this directionality is not flipped when an actor is confused, the targets are sorted from right to left, and the “frontmost target” flag means that the rightmost Koopa will become the only valid target.

Once all the possible targets have been determined, for single-target attacks, the btlevtcmd_ChoiceSamplingEnemy function determines which of the possible targets will be attacked, either by strong preference, or random weighted chance. This function starts by giving each target a weight of 100, with the ‘frontmost’ receiving a weight of 110, then modifies the weights based on the attack’s target weighting parameters (you can read up on more details on those in my attack parameters article).

For a simple example, Goomba’s headbonk has the following flags:

0x80002004 AttackTargetWeighting_Flags
0x00000004 Prefer frontmost
0x00002000 (no known effect)
0x80000000 Choose target randomly

If Mario is in front and his partner is in the back, the “prefer frontmost” option multiplies Mario’s weight of 110 by 0.9, and the partner’s weight of 100 by 0.5, giving weights of 99 and 50. A weighted roll then determines which character is targeted, meaning that Mario gets attacked about two-thirds of the time.

For another example, most enemy healing moves have similar weighting parameters to these:

0x00001500 AttackTargetWeighting_Flags
0x00000100 Prefer less healthy
0x00000400 Prefer lower HP
0x00001000 Prefer in Peril

In this case, the Magikoopa and Goomba’s weights are multiplied as follows:

MagikoopaGoomba
Starting weight110100
Less Healthy (x 2.0 – current HP fraction)157150
Lower HP (x 1.5 – current HP/20)204217
In Peril (x1.5)204325

Since Magikoopa’s healing attack doesn’t do a random weighting check, it takes whichever one is the highest, in this case, the Goomba’s. In the event of a tie between multiple targets, whichever one is closer to the ‘front’ will be chosen.

In addition to the targeting weight flags, several items have extra checks in this function to try to ensure that they’re not wasted on targets that don’t need them:

  • If one of the above support status items is being used, targets that already have that status (or are unable to act, in the case of Power Punch) have their weight set to 1. The same is true for Ruin Powder, Mr. Softener and Mini Mr. Mini, even though they’re multi-target attacks.
  • If a Tasty Tonic is being used, targets that don’t have Sleep, Stop, Dizzy, Poison, Confuse, Burn, Freeze, Tiny, Def-Down or Slow status (not Electric this time!) will have their weight set to 1. As such, if a Tasty Tonic is used to attempt to ‘cure’ Electric status when no enemies have negative statuses, it will be equally likely to be used on any of the targets, since the item has the “random weighted” flag and all targets will have a weight of 1.
  • If the item being used restores FP (Gradual Syrup not included), each target’s weight is multiplied by 1.0 + 0.1 * (max FP – current FP) if they are missing FP, or set to 1 otherwise (unless the item also restores HP).

(Note that a target with a weight of 1 is still possible to be picked, but very unlikely.)

Once the final targets are determined, the script checks to see if the attack lands (as opposed to missing due to evasion, invisibility, or accuracy loss), and processes the hit if so, for each target in turn:

# check to see if attack should hit
callc [btlevtcmd_PreCheckDamage battle_event_cmd.o] -2 LW(3) LW(4) LW(9) 256 LW(5)
...

# if LW(5) = 1 (attack lands), process hit
callc [btlevtcmd_ResultACDefence battle_event_cmd.o] LW(3) LW(9)
callc [btlevtcmd_CheckDamage battle_event_cmd.o] -2 LW(3) LW(4) LW(9) 256 LW(5)

If you’re interested in further details on this part of the attack process, my video on the “Defensive Action Storage” glitch goes into more detail, with special focus on all the pitfalls that TTYD’s event programmers ran into when coding multi-target attacks with a single Action Command.

Confusion events

Confusion events are run in the place of the attack events on turns that the Confusion status procs, and are handled in a few different ways between different actor types.

For Mario and his partners, if they become confused, they choose one of the following actions at random:

  • Jump
  • Hammer
  • Partner base move
  • Defend
  • Switch partners (if possible)
  • Run away (if possible); this may or may not fail, based on where the invisible marker ends up.

The way this works under the hood is far more involved than I would have guessed; apparently the game essentially acts as if selecting options from the player’s standard battle menu, with an equal chance of making any valid choice at any time. As such, a confused Mario will select “Jump”, “Hammer”, and “Tactics” each 1/3 of the time, with the latter 1/3 being divided equally into the “Switch partners”, “Defend” and “Run away” options. (Likewise, partners have a 1/2 chance of attacking or using Tactics). If Mario’s partner is down, then he will only be able to choose from the Tactics menu.

The game in theory supports more actions when confused, but most other menu selections are disabled (and all attacks aside from default Jump, default Hammer, and each partner’s first move have the “Can use in Confusion” flag unset). In particular, if the ability to select “Items” were enabled, there’s already working code for player characters to randomly use any single-target item on the incorrect alliance.

As for enemies, most of them use their regular attack event as their confusion event, and will use any of their usual moves or held items (including multi-target ones) exactly as they normally would, but on targets from the opposite alliance from normal. If they attempt to use an attacking move that has no valid targets, they will do nothing instead.

Bosses (aside from Gus), Mini-Yuxes, and Bulky Bob-ombs are exceptions to the rule, and use a default confusion event that results in them always doing nothing when confused. (Bulky Bob-ombs’ fuses will continue to tick down during their phase events if already lit beforehand, however.)

Concluding Remarks

Hopefully this gives a well-rounded taste of how enemies’ scripts are used in battle; again, if you’re ever interested in looking at all of the game scripts and attack parameters, I suggest checking out my the tools on my ttyd-utils GitHub repository.

I don’t have any further plans for large-scale mechanics posts in the near future, but I’m sure I’ll have more topics I want to cover eventually. I’m always open to suggestions on what other battle mechanics to cover, or how to present information in a more digestible manner as well. Until then, I hope this site remains a useful reference, and you definitely haven’t seen the last of my TTYD mechanics-related content!

A Potpourri of Precarious Props: Paper Mario TTYD’s Stage Hazards

One of the most maligned, yet uniquely flavorful additions to Paper Mario: The Thousand-Year Door is the added presence of the battle arena. The occasional dropped props and stray sprays add that little bit of character to the stage, as well as a sometimes unwelcome extra element of unpredictability to the outcome of a fight. Unlike the audience, a surprising amount of whose mechanics can be calculated and planned for with a lot of game knowledge, any attacks with a chance of causing stage hazards can never have their effects predicted completely. That said, it’s always worth knowing exactly what you could be up against, so let’s do a deep dive on all the different types of stage hazards and their individual quirks!

Overview

Pretty much every attack performed in the battle (including using items) does a check to see whether any stage hazards should occur once it’s ended. The attack’s weapon parameters specify chances of each of the different types of hazards occurring; these vary between attacks, typically not exceeding 10% except for particularly destructive attacks such as Power Jump, Quake Hammer or Bob-ombast. Of note, nearly all enemy attacks don’t have any chance of causing stage hazards to occur.

The possible effects are as follows, and occur in this order:

  • Background props falling
  • Stage jets turning
  • Stage jets firing
  • Ceiling beam falling
  • Offscreen props (“objects”) falling

Let’s dive into each of them individually…

Background Props

Every battle scene is outfitted with a varying number of stage props, meant to invoke the look of the area it takes place in. These backgrounds broadly form a few layers, named (from foreground to background) “A1”, “A2”, and “B”. The amount of props fitting each of these layers varies from scene to scene; relatively few actually have all three.

A sample battle scene from the Petal Meadows region, showing which props belong to which layers.

To determine which of these background props fall after an attack, there are two different random checks performed. First, a weighted random roll is performed to determine which of the A layers to destroy, if either. In practice, all attacks have a 100 weight for nothing to be destroyed, some weight < 100 that is used for each of A1 and A2 individually, and a weight of 0 for both to be destroyed at once.

For example, Power Jump has a 25 weight for A1 and A2 individually, and 100 weight for nothing to fall, meaning that 25/150 = 1/6 of the time, A1 is destroyed, and another 1/6 of the time A2 is destroyed. If the background layer this random roll determined should be destroyed doesn’t exist or already fell as the result of a previous attack, nothing happens instead (and the weights aren’t changed).

If the A1 layer doesn’t exist for a given scene or has already fallen down, a second random check is done to see if the B layer should be destroyed, this time just checking if a single random value from 0-99 is less than a given threshold. (Again, Power Jump has a value of 25 for the B layer, leading to a 25% chance of this occurring). If the A2 layer still exists at this point, it falls down with the B layer.

As for how this affects combat, background props are fairly tame, with the A1 and B layers of props both causing a single point of damage upon falling (which can be negated by guarding or Superguarding). This damage may only affect either the player or enemy side of the field, depending on the layout of the props (for the scene above, A1 will only damage enemies, but B will damage everyone). The A2 layer is purely cosmetic, in any case, and usually consists of props that are short and low to the ground.

Ceiling Beam

Similarly to the background falling, the falling ceiling beam causes 1 damage to all actors, as well as grounding anything currently attached to the ceiling. This can be used to end Rawk Hawk’s ceiling phase prematurely (though all attacks available at that time have a very small chance of causing the ceiling to drop).

Notably, Spring Jump cannot be used successfully when the ceiling beam is present; Mario will slam into the ceiling before having a chance to reach his target. This has a whopping 50% chance of making the ceiling fall, though!

Stage Jets

The stage jets (“nozzles” internally) are added to the decor when Mario reaches B-List Star status (level 10), and are probably the most infamous stage hazard. Meant to evoke fog machines and flashy pyrotechnics on real-life concert stages, these function quite similarly, resulting in a fog that covers the stage, or if the jets are pointed towards the stage, a variety of damaging and/or status-inducing effects.

There are four types of stage jets: fog, ice, explosions and fire. These appear in varying proportion based on Mario’s current rank:

RankFogIceExplosionFire
B-List Star (Lv. 10-19)65%35%
A-List Star (Lv. 20-29)35%25%40%
Superstar (Lv. 30+)20%20%25%35%

The type of jet never changes within a single battle, and is determined either at the start of the battle (firing off before combat starts) in fights without a First Strike, or on the first attack in which they are randomly determined to fire otherwise.

After every attack, the jets do two independent random checks: one to toggle between all facing upward, and each facing the left or right side of the stage, and one to determine whether or not they should fire. For instance, performing a normal Hammer attack has a 6% to make the jets turn, and a 2% chance to make them fire.

If the jets are facing the stage when they fire, then attack parameters are constructed dynamically to determine who should be targeted and how strong the effects should be. This is done by taking a base set of parameters for each side with at least one jet facing it (which basically specifies nothing except for which targets to filter out; one for the player’s side that disallows enemies and system actors, and one for the enemies’ that disallows system actors, Mario / Shell Shield, and his partner), and mixing it with parameters for the individual jet types that determine the attack’s damage and damage function, its elemental type, and what statuses should be applied (with the damage and status turn count being multiplied by 1, 2, or 3 based on how many jets are facing that side) as follows:

Jet TypeDamage FunctionBase DamageElementStatus
FogFixed0NormalNone
IceNon-damagingIceFreeze, 1 turn
ExplosionFixed2ExplosionNone
FireFixed1FireBurn, 3 turns

Interestingly, the ice jets are completely non-damaging, meaning that they are incapable of dealing damage, even to enemies weak to the Ice element. By contrast, the fog jets’ attack, which normally does nothing, can cause damage due to P-Up, D-Down.

An especially odd quirk of stage jet attacks is that because of the way they are dynamically constructed and how barebones the base “player side” and “enemy side” attack parameters are, they do not include the “force target only high-priority parts” flag normally included on multi-target attacks. This means that any enemy that has multiple targetable parts tied to the same actor has all of them targeted simultaneously, usually resulting in multiple times the intended damage.

The affected parties include the dragons (head and foot), Grodus (both him and his staff; even though the staff doesn’t make Grodus take damage, both hits count towards the chance of his next attack failing), and both Magnus von Grapple bosses, who as mentioned in the previous article have separate hitboxes for their body and foot to make attacks land more naturally:

Worst of all, Cortez’s first two forms cause his head and and bone pile to both be hit at once, and after attacking his bone pile twice in the second phase, his head, bone pile, and the exposed gem in his chest (which takes an extra point of damage due to elemental weakness) can all take damage simultaneously for up to a whopping 17 damage at once!

Fog Jets

As their name suggests, fog jets’ main effect doesn’t come from their damage (or lack thereof, as the case usually is); their more notable effect is that when they fire, the stage becomes shrouded in fog for 2 turns, which causes most attacks to miss 50% of the time. Unlike the other types, this occurs regardless of whether they are facing the stage. (However, the “attack” part will only occur if they’re facing a side, unless that attack itself misses due to the fog!)

A few attacks, mostly the Special Moves, are able to skip the fog’s miss check, but generally you’re forced to wait the 2 turns out; however, there are a handful of actions that can dispel the fog prematurely:

  • Bobbery Appealing
  • Bobbery using Bomb (happens after the attack, so it can still miss)
  • Bobbery’s Bomb Squad bombs exploding (happens before the attack)
  • Bobbery’s Bob-ombast (happens before the attack)
  • Bobbery’s Hold Fast being triggered
  • Bob-ombs exploding (if attacking, the miss check happens before it hits)
  • Bulky Bob-ombs or Bob-ulks exploding (happens before the attack)
  • Flurrie’s Gale Force
  • Using an Ice Storm item

Of these, Bomb, Bob-ombast and Gale Force all have a chance of bringing the fog back immediately afterward, but if your aim is to clear the fog without that chance, appealing with Bobbery and using Ice Storm can’t cause any stage effects, so go wild!

Falling Objects

The final and most varied group of hazards. Each attack does a single random roll to see if an offscreen prop should fall, and if that chance succeeds, picks one of the eligible prop types for Mario’s current rank with equal probability. Here are the nine types of falling objects in action:

A deluge of decor and debris!

And here are their effects:

Prop TypeMinimum rankDamageStatus
BucketRising Star1Dizzy (3 turns, 50%)
Stage lightRising Star1Electric (3 turns, 25%)
BasinB-List Star1Dizzy (3 turns, 50%)
WaterB-List StarMultiple (clear, 100%)*
Small bug(s)**A-List StarConfuse (3 turns, 50%)
Large bugA-List Star1Confuse (3 turns, 50%)
ForkA-List Star2Sleep (0 turns, 100%)***
MeteorSuperstar3Dizzy (3 turns, 50%)
Bowser statueSuperstar5Confuse (3 turns, 100%)

* The water stage effect clears the following statuses: Sleep, Stop, Dizzy, Poison, Confuse, Electric, Burn, Freeze, Charge, and Payback. (Guarding it will stop all statuses from being cleared.)
** The small bugs come in two sub-varieties, either a single small bug or a swarm of them; they still only count as one type of object.
*** This effectively clears the Sleep status. Note that this does not guarantee it’s cleared if the target has < 100% Sleep vulnerability; if the vulnerability roll fails and the 50% chance of the target waking up from attack fails, the target might remain asleep.

The target of the falling objects is chosen like randomly targeted enemy attacks, choosing randomly from all non-system actors with a 10% bias to the leftmost character (i.e. whoever the player has in the back), except the meteor and Bowser statue, which always hit all characters, or all characters on a randomly chosen side, respectively. There is also a 10% chance of any of the single-target hazards to hit the audience instead, causing all the audience members in a small range around the impact point to leave.

Closing notes / miscellanea

This covers most of the important info on stage hazards, but a couple more notes:

  • Mischief-causing Shy Guy audience members will trigger either a falling object or the stage jets to fire with equal probability (assuming Mario is B-List or higher; otherwise it will always be a falling object). The side of the screen the Shy Guy runs off to has no impact on who the falling objects target.
  • Some types of stage hazards might be disabled during special occasions. Notably, falling objects are disallowed during the Crump prologue fight and all tutorial fights, and the ceiling from the Chapter 5 Crump fight is impossible to destroy (unlike the Rawk Hawk fight).
  • There’s an unused byte after the usual stage hazard rate parameters that takes on similar values to the others, which suggests there might have been another type of stage hazard that was cut from the game earlier in development. Even as early as the July 2004 demo, there isn’t any code that seems to reference the parameter directly, so it’s only speculative what it could have done.
  • The only enemy attacks that are able to cause stage hazards are the dragons’ stomp, which have a 10% chance of causing a falling object. If you feel like winning the lottery, try getting Hooktail’s stomp to self-inflict Electric status (which she has only a 10% susceptibility to)!

If you’re interested in looking at the rates for specific player attacks, you can find a spreadsheet of the hazard chances caused by player attacks in my stats Drive folder, or you can dump the attack parameters and event scripts for all attacks in the game using my ttyd-utils suite of scripts (look for calls to “btlevtcmd_WeaponAftereffect” in the event scripts to see which attacks actually use their parameters!)

Until next time, where I’ll cover the basics of enemy attack AI!