Tag Archives: 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-damaging—IceFreeze, 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 Star—Multiple (clear, 100%)*
Small bug(s)**A-List Star—Confuse (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!

TTYD’s Attack Parameters: Becoming a Wizard of Weaponry

Paper Mario: The Thousand-Year Door pits Mario and his companions against a wealth of different enemies. In combat, both sides have access to a wide variety of different moves, with different attack patterns (single-target, multi-hit, spread…) and performing different functions, from direct damage to buffing allies with status effects. On the face of it, these moves have several pretty easily distinguishable features, such as their FP or SP cost, which status they inflict, or whether they pierce defense. However, there’s a lot of subtlety hidden past these surface-level attributes — What exact targets can the move reach? Does it make direct contact (i.e., can it be stopped by spikes or elemental hazards)? Do enemies prefer to use an attack on some targets more than others?

This article will be an overview of what parameters the game uses to define these properties of attacks, and more.

What is an “attack”, exactly?

There’s a lot of different ways any given action taken by a player or enemy can be classified and subdivided; here’s my attempt to briefly clarify my terminology for the components of these actions, and what the game internally refers to things as for context.

As described in the article on turn structure I wrote last year, both player characters and enemy characters generally perform their primary combat actions in their respective “active phase”. In the case of player, they choose their commands from a menu, whereas an enemy will perform an action based their AI (specifically, their “attack script”). Both the player and enemies (collectively, “actors”) may choose to do several sorts of actions that don’t really classify as “attacks”, for instance:

  • Restoring HP, FP, or SP (via items, or special enemy abilities like Magikoopas’ healing magic or Bonetail’s heals).
  • Changing between states or forms (e.g. the player Defending, a Koopa Troopa un-flipping itself, a Dark Boo rising into an aerial state).
  • Other specialized actions, such as fleeing from battle, spawning extra allies, etc.

These sorts of actions, and how enemies choose what actions to perform are largely out of the scope of this article (and the latter will definitely be covered at a later date).

We can consider any action where an actor targets one or more actors in the battle (including itself) with the intent to deal damage or apply status effects an “attack”. Generally, the execution of an attack proceeds as follows:

  • Find all the possible targets of an attack (this is done before the player is able to select a command; if there are no possible targets, that option will be greyed out).
  • Determine which targets will be affected (possibly by the player / enemy’s choice).
  • Start executing the event script associated with the specific attack (to handle movement, Action Commands, etc.)
  • Before hitting each affected target:
    • Check to see if the hit succeeds (Did the attacker miss due to invis / evasion / accuracy, etc.? Were they countered by a hazard or Superguard?)
    • If the attack did successfully connect, apply the appropriate damage and/or status.

The main data structure specific to each attack that determines its properties is called a “weapon” internally. To some degree this sort of terminology makes sense (one could imagine an actor holding a physical ‘weapon’ that has an inherent range, damage, and status…), but generally players will attribute those properties to the attack itself, so I might use the term “attack” to interchangeably mean the entire attacking action, as well the “weapon” (or weapons, e.g. the different hits of a Spin Jump or Tornado Jump) used as a part of it from now on. Notably, weapons can also be used for some non-attacking moves for determining targets, even though the rest of the parameters won’t apply.

Incidentally, the concept of “attacks” can be extended to non-combatant-initiated moves as well; e.g. Bomb Squad bombs’ explosion, falling stage objects (or jets, walls, ceilings…), and audience Boos’ invisibility-granting swoop.

Attack parameters

Now that we have a baseline for what counts as an attack, let’s take a look at the data that makes up a “weapon”. If you want to look at the raw definition of the weapon structure, as well as many other structures used in TTYD’s battle system, I suggest reading through this resource on my “ttyd-utils” GitHub repository (look for the “BattleWeapon” struct).

Here’s what the raw weapon data looks like for Vivian’s Shade Fist; I’ve broken it up into bytes and color-coded which bytes pertain to various types of information:

The raw bytes making up the WeaponData struct used by Vivian's Shade Fist attack.

Let’s do a deep dive through each of these categories, and describe all the possible values they can take on.

Attack Script Pointer

A pointer to the event script that is used to execute the attack from beginning to end. This is only generally used by player attacks and items, since enemies’ behavior and attacks are all baked into a single “attack script”.

Damage functions & parameters

All weapons that deal damage have to specify a function that determines their base damage, which can take up to eight parameters. (Similarly, attacks that deal FP damage have a separate function & set of parameters for that). The following is a list of all the functions used:

  • Fixed damage – Deals the damage specified by the first parameter. This is used by pretty much all enemy and item attacks.
  • Attack level + AC success-based – Deals a different amount of damage based on the rank of the jump/hammer/partner, and whether or not the Action Command was performed successfully.
  • Jump/Hammer + Badge Stacking – Same as the above, but adds an additional N damage per copy of the move’s associated badge equipped.
  • Action Command-based – Deals base damage + additional damage based on the output value from an Action Command (that value’s usage varies based on the type of AC).
  • Partner rank level – Deals damage based on the partner’s rank only (used for Doopliss’s partners).
  • HP / FP halved – Deals 1/2 the target’s HP or FP (there are variants to round up or down for HP).
  • Tornado Jump tornadoes – Deals fixed base damage, plus an additional N per copy of Tornado Jump equipped.
  • Hooktail FX R scaling – Deals base damage, minus the number of hits taken where Attack FX R activated.
  • Special functions based on the internals of Earth Tremor, Art Attack and Supernova.
  • Null, i.e. no damage is dealt by this move.

Notably, attacks can have a non-null damage function but still deal 0 base damage; such attacks can deal damage if the weapon can be affected by badges or statuses (e.g. Flower Fuzzies’ FP draining attack with a Power Punch), and/or if the target has P-Up, D-Down badges equipped (e.g. the fog stage effect).

Status Effects

Determines which status effects the attack causes, and their success rate, strength and turn count when applicable. Shade Fist, for example, causes Burn with a 100% base rate for 2 turns. The base success rate is multiplied by the enemy’s vulnerability to the status to determine how often it works, in most cases.

Stage Effects

Determines the chances of the move causing stage hazards: background falling, stage jets turning and/or firing, the ceiling falling, and props falling. There’s an extra parameter that seems to have been intended for use with another type of stage hazard in the past, but no code seems to remain that references it.

Other basic attack info

Miscellaneous parameters that aren’t part of the specialized ‘flags’ sections:

  • Item type (for attack items)
  • Base accuracy (always 100%, but theoretically a move could be given a natural miss rate, lol)
  • Base FP / SP (in full 1.00 units) cost
  • Superguard-ability (can the attack be Superguarded, and does it cause the enemy to recoil?)
  • Stylish command SP strength (always 1 in the US version, but some attacks in the Japanese release have 2x or 3x Stylish SP; see this video for more info!)
  • BINGO card chance on success
  • Element (normal, fire, ice, explosion, or electric)
  • On-hit effects (Mostly visual effects like Love Slap spinning the enemy around, but also includes the knockback effects from Gulp and the Hammer spin moves).
  • Action Command difficulty (always 3 by default; can range from 0 to 6, which would normally be the equivalent of 3 Simplifiers or 3 Unsimplifiers)

Allowed Target types

A set of flags that if set, can enable / disable certain types of actors from being targeted:

  • Cannot target Mario (or Shell Shield, if he’s currently shielded)
  • Cannot target partners
  • Cannot target enemies
  • Cannot target “system” units (the “system” actor itself, or Bomb-Squad bombs; pretty much all attacks disallow this)
  • Cannot target opposite alliance
  • Cannot target own alliance (Most attacks use alliance-based restrictions rather than Mario/party/enemy ones; these restrictions are flipped if the attack is used when Confused).
  • Cannot target self
  • Cannot target same species of actor
  • Only target self
  • Only target Mario
  • Single-target / Multi-target
  • Cannot target anything (probably only set dynamically)

There are also two flags that narrow down which parts of enemies with multiple targets can be targeted. In some cases, the difference between targetable parts can be mechanically interesting (Grodus vs. his staff, Cortez’s bone pile / rib cage) or a matter of timing preference (the dragons’ nose vs. their foot), but some enemies, like the Magnus von Grapple bosses, have two functionally identical but different target points for aerial and grounded attacks, just so the point of impact looks more natural.

  • “Force target highest-priority part” – Will force the attack to target the main part of an enemy if within range, and a secondary one if not. This is generally used for picking the more natural hitbox for a certain range of move to target if there are multiple equivalent options, but this does also mean some attacks with otherwise unrestricted range, such as Shade Fist, don’t have the choice to target Grodus’ staff or Cortez’s ribcage. Of note, Fire Drive is the only multitarget move besides Bomb Squad to not have this option set, meaning that all reachable parts are hit at once (making it hit Grodus and his staff).
  • “Only target certain high-priority parts” – Used only by Bomb Squad bomb explosions; the details are kind of fiddly but in practice this is generally used to make sure only one target can be hit at once on the same enemy (though Cortez notably can be hit on his open ribcage and bone pile at the same time).

If neither of those flags is set, the attack can freely target anything that is allowed by the enemy’s class / part properties, and the “archetype” of attack being used, as described in the next section:

Allowed Target properties / Attack Archetypes

Flags that control the range of an attack, as well as what “archetype” an attack belongs to.

  • Archetypes:
    • “Jump-like” – Generally doesn’t restrict anything, aside from a couple of cases where you’re forced to target a higher hitbox rather than a functionally identical lower one (e.g. Magnus and Magnus 2.0), and the special case in Cortez phase 2 where his head is too high to reach.
    • “Hammer-like” – Generally, disallows hitting aerial targets. Used by Gulp, Bomb, Love Slap, most grounded enemy attacks, etc.
    • “Shell Toss-like” – Functionally identical to Hammer-like, but used by Koops’ and enemies’ shell attacks.
    • “Tattle-like” – Generally forces you to target a specific part of each enemy for Tattles.
    • (Note that attacks can belong to none of the above archetypes, in which case they don’t have any of their specific target restrictions, e.g. Quake Hammer, all Special Moves, …)
  • Other properties:
    • Cannot target ceiling (used by jumps, Koops’ attacks, etc.)
    • Cannot target floating (used by Quake Hammer, Magnus’ earthquake, etc.)
    • Cannot target grounded (used by Tornado Jump’s tornadoes)
    • Recoil-type attack – prevents self-targeting if knocked back by Gulp or the spin Hammers, since those attacks are considered to be done by the actor being flung.
    • Target same / opposite alliance direction – Determines which direction should be considered the “front”; for the player, the “same” direction would mean treating the rightmost target on the stage as the front, where “opposite” would mean treating the leftmost as the front. Generally, attacks against the opposing team use the “opposite” direction, but buffs use the “same” direction.
    • Can only target front targets – Using the above definition of “front”, prevents the attack from being able to reach enemies behind the frontmost grounded target. This does still allow for targeting aerial enemies in front of it (provided the attack can normally hit them), though. There’s also a barely-used feature where enemies sufficiently far in the background (-30 or more units on the Z-axis) count as being on a different plane; this is what allows attacks like Shell Toss to be able to target the Shadow Queen or her right hand, but is basically only used in that fight (enemies in standard fights span from -20 to +20 units from the origin on the Z-axis).

Random target weighting schemes

After narrowing down the valid targets using the parameters above, if the attack being used doesn’t have the means of selecting a target manually (such as Mini-Egg’s eggs or the vast majority of enemy attacks), each valid target is assigned a weight, and those values are used to either select a target randomly, or based on the highest weight.

By default, these weights are 110 for the “frontmost” target, and 100 for all others, but there are a number of schemes used to modify these weights:

  • Prefer Mario – Make Mario’s weight 50% higher.
  • Prefer Partner – Make Mario’s partner’s weight 50% higher.
  • Prefer Front – Multiply the weights by 0.5, 0.9, 1.3… from back to front. This is used pretty frequently on enemy attacks, essentially making the front player character about twice as likely (99 : 50) to be selected.
  • Prefer Back – Multiply the weights by 0.5, 0.9, 1.3… from front to back. This is only used by Cortez’s second attack in his first form, and Blooper’s right tentacle (which has separate attacks for hitting the front and back party member!)
  • Prefer Same Alliance – Make teammates’ weights 50% higher.
  • Prefer Opposite Alliance – Make opposing actors’ weights 50% higher.
  • Prefer Less Healthy – Multiplies weights by (2.0 – target’s current fraction of Max HP).
  • Greatly Prefer Less Healthy – Multiplies weights by (1.0 – target’s current fraction of Max HP).
  • Prefer Lower HP – Multiplies weights by (1.5 – target’s current HP / 20). Effectively makes weights negative if their HP is over 30; this would cause unintended behavior if there were any such moves with random target selection used in battles with enemies that have 30+ HP, but no such battles exist in the vanilla game. This is also why Kammy Koopa prioritizes healing Bowser over herself if both are at 30+ HP, even if Bowser is at full health.
  • Prefer Higher HP – Multiplies weights by (1.0 + target’s current HP / 20).
  • Prefer in Peril – Multiplies the weights of actors in Peril by 50%. Humorously, this occasionally makes Dull Bones the preferred targets for healing items even if they’re at “full health”.
  • Choose Randomly – Picks from the final weights randomly in a weighted fashion; otherwise, chooses the target with the highest weight (preferring targets closer to the front in the case of a tie).

Enemies may rarely use other means to determine targets; e.g. Kammy explicitly chooses who to target with her magic buffs in her attack script, independently of the weighting system. In addition, enemies have specialized logic for determining when and on whom they should use their held items; that will be covered in a future post.

Counter-attack resistance

These flags determine whether the attack is immune to certain contact hazards:

  • Top-Spiky
  • Front-Spiky
  • Pre-emptive Spiky – Spikes that activate when getting near the enemy; most notably Bristles, but X-Punch and all of Cortez’s weapons except the Hook also have this property.
  • Fiery
  • Icy
  • Electric
  • Poisonous
  • Explosive – Used for unlit Bob-ombs; only checked if the move has an element that will make them explode on contact.
  • Volatile Explosive – Used for lit Bob-ombs.
  • Payback / Return Postage
  • Hold Fast – Interestingly, shares the same values as the explosion-type counter immunity for all attacks, but not necessarily Payback immunity (for instance, most shell attacks get countered by Payback, but nothing else).

Other special properties

Finally, there are a handful of flag-based parameters that don’t fall under the previous categories.

  • Badge-mutable power – Can have its damage boosted by badges.
  • Status-mutable power – Can have its damage altered by status effects (includes Merlee’s +ATK boost, for attacks that happen on Mario’s turn).
  • Chargeable – Can be boosted by / expend the Charge status.
  • Cannot miss – Ignores evasion / invisibility / accuracy (e.g. Special Moves, most buffs).
  • Diminishing returns per hit (e.g. Power Bounce, Atomic Boo breath attack)
  • Diminishing returns per target (e.g. Fire Drive, Atomic Boo surprise attack)
  • Defense-piercing
  • Can break Freeze status – A dummied-out status mechanic, where some attacks would have a chance to end Freeze early.
  • Ignores status vulnerability – Makes statuses guaranteed to hit if the target is not immune to them (e.g. cooking items that heal but inflict guaranteed negative statuses).
  • Ignites if burned – Turns attacks fire-elemental if the attacker is Burned (not used).
  • Flips shelled enemies (e.g. Jump)
  • Flips Bomb-flippable enemies (e.g. Bomb against Clefts)
  • Grounds winged enemies
  • Usable when Confused
  • Unguardable – Cannot be guarded or Superguarded.

Non-Weapon Parameters

We’ve covered basically every known parameter that can be assigned to an weapon now, but there are of a handful of notable additional flags that are only handled in the attack’s script code (most of which are set dynamically) that bear mentioning:

  • “Lethal” – Surprisingly, a flag has to be passed explicitly for a hit to be able to knock out the target at 0 HP; this is turned off for the early hits of most multi-hit attacks so the death animation isn’t interrupted by another hit. This is why some multi-hit attacks either hit or miss the entire string of attacks, or are forced to land the last hit if any prior ones connect (e.g. Hammer Bros.). Notably, some attacks never have this set, and rely on a “ghost hit” after the attack ends to kill units that reached 0 HP (e.g. Rawk Hawk’s prop drop).
  • “Action Command succeeded” – Has to be passed into the damage-dealing routine to treat an AC as successful; the AC result is fetched by a separate function call earlier in the script.
  • “Suppress status application” – Disables status ailments; used for Shade Fist with a failed action command.
  • “Allow status application” – Generally, statuses can only be applied by lethal hits; this is used on Mini-Egg to allow any of the hits to apply Tiny status.
  • “Achilles’ heel weakness” – For dealing with Iron Clefts’ unique defense type. This is only ever applied to Gulp, but is only passed in through the event script rather than being an inherent property of the weapon; in theory any attack could have that flag set.

Concluding Remarks

This should hopefully give a decent overview on all the stuff that goes into specifying how every attack in the game behaves; this only scratches the surface of the complexity of how attacks are implemented, though, as every attack has dozens of lines of script code dedicated to selecting targets, moving characters around the screen, visual effects, and so forth.

I might do a deep dive on some particular aspects of the script side of things in the future, and I definitely intend to give an overview on particular things specific to enemy AI sooner than later. In the meantime, if you’d like to get a hands-on look at all the raw weapon data in the game, as well as what various attack scripts look like as a whole, my ttyd-utils GitHub repo has tools for exporting the all the weapon structures and scripts from the game data as CSV / text files.