piclets / pokemon_emerald_docs /battle_system_architecture.md
Fraser's picture
Remove problematic public/assets/logo.PNG to fix deployment
50135bd

Pokemon Battle System Architecture Analysis

Overview

The Pokemon Emerald battle system is a complex, state-driven architecture that orchestrates combat between multiple Pokemon. Rather than being a single monolithic system, it's composed of several interconnected subsystems that handle different aspects of battle functionality.

High-Level Architecture

Core Components

  1. Battle State Machine (battle_main.c)
  2. Battle Controllers (battle_controller_*.c files)
  3. Animation System (battle_anim*.c files)
  4. Script Engine (battle_script_commands.c)
  5. User Interface (battle_interface.c)
  6. Data Management (Pokemon storage and battle state)

1. Battle State Management

Main Battle Loop

The battle system is built around a main function pointer (gBattleMainFunc) that controls the overall battle flow:

// Central battle state function pointer
void (*gBattleMainFunc)(void);

Key State Variables

  • gBattleStruct - Central battle state container
  • gBattleMons[MAX_BATTLERS_COUNT] - Active Pokemon data
  • gBattlerPositions[] - Battler position tracking
  • gBattleTypeFlags - Battle mode flags (wild, trainer, double, etc.)

gBattleStruct definition:

struct BattleStruct
{
    u8 turnEffectsTracker;
    u8 turnEffectsBattlerId;
    u8 unused_0;
    u8 turnCountersTracker;
    u8 wrappedMove[MAX_BATTLERS_COUNT * 2]; // Leftover from Ruby's ewram access.
    u8 moveTarget[MAX_BATTLERS_COUNT];
    u8 expGetterMonId;
    u8 unused_1;
    u8 wildVictorySong;
    u8 dynamicMoveType;
    u8 wrappedBy[MAX_BATTLERS_COUNT];
    u16 assistPossibleMoves[PARTY_SIZE * MAX_MON_MOVES]; // Each of mons can know max 4 moves.
    u8 focusPunchBattlerId;
    u8 battlerPreventingSwitchout;
    u8 moneyMultiplier;
    u8 savedTurnActionNumber;
    u8 switchInAbilitiesCounter;
    u8 faintedActionsState;
    u8 faintedActionsBattlerId;
    u16 expValue;
    u8 scriptPartyIdx; // for printing the nickname
    u8 sentInPokes;
    bool8 selectionScriptFinished[MAX_BATTLERS_COUNT];
    u8 battlerPartyIndexes[MAX_BATTLERS_COUNT];
    u8 monToSwitchIntoId[MAX_BATTLERS_COUNT];
    u8 battlerPartyOrders[MAX_BATTLERS_COUNT][PARTY_SIZE / 2];
    u8 runTries;
    u8 caughtMonNick[POKEMON_NAME_LENGTH + 1];
    u8 unused_2;
    u8 safariGoNearCounter;
    u8 safariPkblThrowCounter;
    u8 safariEscapeFactor;
    u8 safariCatchFactor;
    u8 linkBattleVsSpriteId_V; // The letter "V"
    u8 linkBattleVsSpriteId_S; // The letter "S"
    u8 formToChangeInto;
    u8 chosenMovePositions[MAX_BATTLERS_COUNT];
    u8 stateIdAfterSelScript[MAX_BATTLERS_COUNT];
    u8 unused_3[3];
    u8 prevSelectedPartySlot;
    u8 unused_4[2];
    u8 stringMoveType;
    u8 expGetterBattlerId;
    u8 unused_5;
    u8 absentBattlerFlags;
    u8 palaceFlags; // First 4 bits are "is <= 50% HP and not asleep" for each battler, last 4 bits are selected moves to pass to AI
    u8 field_93; // related to choosing pokemon?
    u8 wallyBattleState;
    u8 wallyMovesState;
    u8 wallyWaitFrames;
    u8 wallyMoveFrames;
    u8 lastTakenMove[MAX_BATTLERS_COUNT * 2 * 2]; // Last move that a battler was hit with. This field seems to erroneously take 16 bytes instead of 8.
    u16 hpOnSwitchout[NUM_BATTLE_SIDES];
    u32 savedBattleTypeFlags;
    u8 abilityPreventingSwitchout;
    u8 hpScale;
    u8 synchronizeMoveEffect;
    bool8 anyMonHasTransformed;
    void (*savedCallback)(void);
    u16 usedHeldItems[MAX_BATTLERS_COUNT];
    u8 chosenItem[MAX_BATTLERS_COUNT]; // why is this an u8?
    u8 AI_itemType[2];
    u8 AI_itemFlags[2];
    u16 choicedMove[MAX_BATTLERS_COUNT];
    u16 changedItems[MAX_BATTLERS_COUNT];
    u8 intimidateBattler;
    u8 switchInItemsCounter;
    u8 arenaTurnCounter;
    u8 turnSideTracker;
    u8 unused_6[3];
    u8 givenExpMons; // Bits for enemy party's Pokémon that gave exp to player's party.
    u8 lastTakenMoveFrom[MAX_BATTLERS_COUNT * MAX_BATTLERS_COUNT * 2]; // a 3-D array [target][attacker][byte]
    u16 castformPalette[NUM_CASTFORM_FORMS][16];
    union {
        struct LinkBattlerHeader linkBattlerHeader;
        u32 battleVideo[2];
    } multiBuffer;
    u8 wishPerishSongState;
    u8 wishPerishSongBattlerId;
    bool8 overworldWeatherDone;
    u8 atkCancellerTracker;
    struct BattleTvMovePoints tvMovePoints;
    struct BattleTv tv;
    u8 unused_7[0x28];
    u8 AI_monToSwitchIntoId[MAX_BATTLERS_COUNT];
    s8 arenaMindPoints[2];
    s8 arenaSkillPoints[2];
    u16 arenaStartHp[2];
    u8 arenaLostPlayerMons; // Bits for party member, lost as in referee's decision, not by fainting.
    u8 arenaLostOpponentMons;
    u8 alreadyStatusedMoveAttempt; // As bits for battlers; For example when using Thunder Wave on an already paralyzed Pokémon.
};

Battle Flow States

The battle progresses through distinct phases:

  1. Initialization - Setup battlers, load graphics
  2. Turn Selection - Player/AI choose actions
  3. Action Resolution - Execute moves in priority order
  4. Turn Cleanup - Apply end-of-turn effects
  5. Battle End - Victory/defeat/capture resolution

2. Controller Architecture

Battler Controllers

Each battler (player, opponent, partner, etc.) has its own controller that handles:

  • Input processing
  • Animation requests
  • Data updates
  • UI management
// Controller function pointer array
void (*gBattlerControllerFuncs[MAX_BATTLERS_COUNT])(void);

Controller Types

  • Player Controller (battle_controller_player.c) - Human input
  • Opponent Controller (battle_controller_opponent.c) - AI decisions
  • Link Controllers - Network battlers
  • Special Controllers - Safari Zone, Wally, etc.

Command System

Controllers communicate via command buffers:

// Controller commands (simplified)
CONTROLLER_GETMONDATA      // Request Pokemon data
CONTROLLER_CHOOSEACTION    // Select battle action
CONTROLLER_MOVEANIMATION   // Play move animation
CONTROLLER_HEALTHBARUPDATE // Update HP display

3. Animation System

Animation Pipeline

Animations are script-driven and hierarchical:

  1. Move Animation Selection - Choose appropriate animation
  2. Script Execution - Run animation script commands
  3. Sprite Management - Create/animate visual elements
  4. Audio Synchronization - Play sound effects
  5. Cleanup - Remove temporary sprites

Animation Scripts

Each move has an associated animation script:

const u8 *const gBattleAnims_Moves[];

Animation Commands

Scripts use specialized commands:

  • loadspritegfx - Load sprite graphics
  • createsprite - Create animated sprite
  • playse - Play sound effect
  • delay - Wait specified frames
  • monbg - Modify Pokemon background

Visual Task System

Complex animations use visual tasks for frame-by-frame control:

u8 gAnimVisualTaskCount;  // Active visual task counter

4. Pokemon Data Storage and Retrieval

Battle Pokemon Structure

Active Pokemon are stored in gBattleMons[]:

struct BattlePokemon {
    u16 species;
    u16 attack;
    u16 defense;
    u16 speed;
    u16 spAttack;
    u16 spDefense;
    u16 moves[MAX_MON_MOVES];
    u32 hp;
    u8 level;
    u8 pp[MAX_MON_MOVES];
    // ... status, abilities, etc.
};

Data Synchronization

The system maintains consistency between:

  • Party Data - Full Pokemon in party array
  • Battle Data - Reduced battle-specific data
  • Display Data - UI representation

Data Flow

  1. Load Phase - Copy party data to battle structure
  2. Battle Phase - Modify battle data only
  3. Update Phase - Sync changes back to party

5. Menu Systems and Input Handling

Action Selection Menu

The main battle menu uses a state-driven approach:

static void PlayerHandleChooseAction(void) {
    // Present: Fight, Pokemon, Bag, Run options
    // Handle input and set action type
}

Move Selection Interface

Move selection involves:

  1. Display Moves - Show available moves with PP
  2. Input Handling - Process directional input
  3. Move Info - Display type, power, accuracy
  4. Target Selection - Choose targets for multi-target moves

Menu State Management

  • Cursor position tracking
  • Input validation
  • Visual feedback (highlighting, animations)
  • Transition between menu levels

6. Action Processing and Effect Application

Turn Order Resolution

Actions are sorted by priority:

  1. Switch Pokemon (highest priority)
  2. Use Items
  3. Use Moves (sorted by speed/priority)
  4. Flee

Move Execution Pipeline

When a move is used:

  1. Validation - Check if move can be used
  2. Target Selection - Determine valid targets
  3. Script Execution - Run move's battle script
  4. Animation - Play visual/audio effects
  5. Damage Calculation - Apply damage formulas
  6. Effect Application - Apply status effects, stat changes
  7. Cleanup - Update battle state

Battle Script System

Moves use script commands for effects:

static void Cmd_attackstring(void);    // Display attack message
static void Cmd_attackanimation(void); // Play attack animation
static void Cmd_damagecalc(void);      // Calculate damage
static void Cmd_healthbarupdate(void); // Update HP bar

Gradual Information Delivery

The system delivers information to players progressively:

  1. Action Announcement - "Pokemon used Move!"
  2. Animation Phase - Visual move animation
  3. Result Messages - Effectiveness, critical hits
  4. Damage Application - Gradual HP bar drain
  5. Status Updates - Additional effects
  6. Turn Cleanup - End-of-turn effects

7. Key Programming Patterns

State Machine Architecture

The battle system extensively uses function pointers for state management:

void (*gBattleMainFunc)(void);                    // Main battle state
void (*gBattlerControllerFuncs[])(void);         // Controller states
void (*gAnimScriptCallback)(void);               // Animation states

Command-Driven Controllers

Controllers process commands via lookup tables:

static void (*const sPlayerBufferCommands[])(void) = {
    [CONTROLLER_GETMONDATA] = PlayerHandleGetMonData,
    [CONTROLLER_CHOOSEACTION] = PlayerHandleChooseAction,
    // ...
};

Script-Based Effects

Both animations and move effects use bytecode scripts for flexibility:

  • Animation scripts control visual presentation
  • Battle scripts control game logic and effects

Asynchronous Processing

The system handles multiple concurrent operations:

  • Animations can run while processing user input
  • Multiple battlers can be in different states
  • Background tasks handle gradual effects (HP drain, etc.)

8. Memory Management

Battle Resources Structure

struct BattleResources {
    struct SecretBase *secretBase;
    struct ResourceFlags *flags;
    struct BattleScriptsStack *battleScriptsStack;
    struct AI_ThinkingStruct *ai;
    struct BattleHistory *battleHistory;
};

Dynamic Allocation

  • Battle resources are allocated at battle start
  • Freed when battle ends
  • Sprites and animations use temporary allocation

9. Extension Points

The architecture provides several extension mechanisms:

  • New Move Effects - Add scripts to move effect table
  • Custom Animations - Create new animation scripts
  • Battle Types - Add flags and specialized controllers
  • AI Behaviors - Extend AI decision trees

Summary

The Pokemon battle system demonstrates sophisticated game architecture through:

  1. Separation of Concerns - Controllers, animations, scripts, and UI are distinct
  2. Data-Driven Design - Moves, animations, and effects defined in data tables
  3. State Management - Clear state machines for battle flow and individual components
  4. Asynchronous Processing - Multiple concurrent operations with proper coordination
  5. Extensibility - Modular design allows for easy addition of new content

This architecture allows complex battle interactions while maintaining code organization and enabling the rich, interactive experience that defines Pokemon battles.