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!

Leave a comment

  1. Pingback: TTYD Battle Bits #006: It’s Just a Phase | The Super Mario Files

  2. Pingback: Status Pro: A comprehensive overview of TTYD’s status effects | The Super Mario Files