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
- Battle State Machine (
battle_main.c
) - Battle Controllers (
battle_controller_*.c
files) - Animation System (
battle_anim*.c
files) - Script Engine (
battle_script_commands.c
) - User Interface (
battle_interface.c
) - 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 containergBattleMons[MAX_BATTLERS_COUNT]
- Active Pokemon datagBattlerPositions[]
- Battler position trackinggBattleTypeFlags
- 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:
- Initialization - Setup battlers, load graphics
- Turn Selection - Player/AI choose actions
- Action Resolution - Execute moves in priority order
- Turn Cleanup - Apply end-of-turn effects
- 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:
- Move Animation Selection - Choose appropriate animation
- Script Execution - Run animation script commands
- Sprite Management - Create/animate visual elements
- Audio Synchronization - Play sound effects
- 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 graphicscreatesprite
- Create animated spriteplayse
- Play sound effectdelay
- Wait specified framesmonbg
- 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
- Load Phase - Copy party data to battle structure
- Battle Phase - Modify battle data only
- 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:
- Display Moves - Show available moves with PP
- Input Handling - Process directional input
- Move Info - Display type, power, accuracy
- 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:
- Switch Pokemon (highest priority)
- Use Items
- Use Moves (sorted by speed/priority)
- Flee
Move Execution Pipeline
When a move is used:
- Validation - Check if move can be used
- Target Selection - Determine valid targets
- Script Execution - Run move's battle script
- Animation - Play visual/audio effects
- Damage Calculation - Apply damage formulas
- Effect Application - Apply status effects, stat changes
- 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:
- Action Announcement - "Pokemon used Move!"
- Animation Phase - Visual move animation
- Result Messages - Effectiveness, critical hits
- Damage Application - Gradual HP bar drain
- Status Updates - Additional effects
- 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:
- Separation of Concerns - Controllers, animations, scripts, and UI are distinct
- Data-Driven Design - Moves, animations, and effects defined in data tables
- State Management - Clear state machines for battle flow and individual components
- Asynchronous Processing - Multiple concurrent operations with proper coordination
- 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.