Files
oracle-of-secrets/Docs/Sprites/Booki.md
scawful 1cc7d84782 Add detailed sprite analysis for Puffstool, Sea Urchin, Thunder Ghost and more
- Introduced comprehensive documentation for the Puffstool sprite, covering properties, core routines, and key behaviors.
- Added analysis for the Sea Urchin sprite, detailing its initialization, state management, and drawing routines.
- Included a thorough examination of the Thunder Ghost sprite, highlighting its dynamic health, lightning attack mechanics, and movement patterns.
2025-10-02 23:55:31 -04:00

12 KiB

Booki Sprite Analysis

This document provides a detailed analysis of the booki.asm sprite, outlining its properties, core routines, and behavioral patterns.

1. Sprite Properties

The following !SPRID constants define Booki's fundamental characteristics:

!SPRID              = Sprite_Booki
!NbrTiles           = 02  ; Number of tiles used in a frame
!Harmless           = 00  ; 00 = Sprite is Harmful,  01 = Sprite is Harmless
!HVelocity          = 00  ; Is your sprite going super fast? put 01 if it is
!Health             = 00  ; Number of Health the sprite have (dynamically set in _Prep)
!Damage             = 00  ; (08 is a whole heart), 04 is half heart
!DeathAnimation     = 00  ; 00 = normal death, 01 = no death animation
!ImperviousAll      = 00  ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow        = 00  ; 01 = small shadow, 00 = no shadow
!Shadow             = 00  ; 00 = don't draw shadow, 01 = draw a shadow
!Palette            = 00  ; Unused in this template (can be 0 to 7)
!Hitbox             = 00  ; 00 to 31, can be viewed in sprite draw tool
!Persist            = 00  ; 01 = your sprite continue to live offscreen
!Statis             = 00  ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer     = 00  ; 01 = will check both layer for collision
!CanFall            = 00  ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow       = 00  ; 01 = deflect arrows
!WaterSprite        = 00  ; 01 = can only walk shallow water
!Blockable          = 00  ; 01 = can be blocked by link's shield?
!Prize              = 00  ; 00-15 = the prize pack the sprite will drop from
!Sound              = 00  ; 01 = Play different sound when taking damage
!Interaction        = 00  ; ?? No documentation
!Statue             = 00  ; 01 = Sprite is statue
!DeflectProjectiles = 00  ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow    = 00  ; 01 = Impervious to arrows
!ImpervSwordHammer  = 00  ; 01 = Impervious to sword and hammer attacks
!Boss               = 00  ; 00 = normal sprite, 01 = sprite is a boss

Note: !Health and !Damage are initially set to 00 but are dynamically determined during initialization.

2. Core Routines

2.1. Sprite_Booki_Long (Main Loop)

This is the primary entry point for Booki's per-frame execution, called by the game engine. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active.

Sprite_Booki_Long:
{
  PHB : PHK : PLB             ; Set up bank registers
  JSR Sprite_Booki_Draw       ; Call drawing routine
  JSL Sprite_DrawShadow       ; Draw a shadow (if !Shadow is 01)
  JSL Sprite_CheckActive : BCC .SpriteIsNotActive ; Check if sprite is active
    JSR Sprite_Booki_Main     ; If active, run main logic
  .SpriteIsNotActive
  PLB                         ; Restore bank register
  RTL                         ; Return from long routine
}

2.2. Sprite_Booki_Prep (Initialization)

This routine is executed once when Booki is first spawned. It dynamically sets Booki's health based on Link's current sword level and initializes SprMiscB.

Sprite_Booki_Prep:
{
  PHB : PHK : PLB
  LDA.l Sword : DEC A : TAY   ; Get Link's sword level (0-3), adjust to 0-indexed
  LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level
  STZ.w SprMiscB, X           ; Initialize SprMiscB to 0
  PLB
  RTL

  .health                     ; Health values for each sword level
    db $04, $08, $10, $18     ; 4, 8, 16, 24 HP
}

2.3. Sprite_Booki_Main (Behavioral State Machine)

This routine manages Booki's AI through a state machine, using SprAction, X to determine the current behavior. It utilizes JumpTableLocal for efficient state transitions.

Sprite_Booki_Main:
{
  LDA.w SprAction, X
  JSL JumpTableLocal          ; Jump to the routine specified by SprAction

  dw StalkPlayer              ; State 0
  dw HideFromPlayer           ; State 1
  dw HiddenFromPlayer         ; State 2
  dw ApproachPlayer           ; State 3

  StalkPlayer:
  {
    %PlayAnimation(0,1,16)    ; Animate frames 0-1 every 16 frames
    JSR Sprite_Booki_Move     ; Handle movement
    RTS
  }

  HideFromPlayer:
  {
    %PlayAnimation(0,4,16)    ; Animate frames 0-4 every 16 frames
    LDA.w SprTimerA, X : BNE + ; Check timer
      INC.w SprAction, X      ; If timer is 0, transition to HiddenFromPlayer
    +
    RTS
  }

  HiddenFromPlayer:
  {
    %PlayAnimation(4,4,16)    ; Animate frame 4 every 16 frames (static)
    JSR Sprite_Booki_Move     ; Handle movement
    JSL GetRandomInt : AND.b #$03 : BEQ + ; Random chance to transition
      INC.w SprAction, X      ; If random condition met, transition to ApproachPlayer
    +
    RTS
  }

  ApproachPlayer:
  {
    %PlayAnimation(5,9,16)    ; Animate frames 5-9 every 16 frames
    JSR Sprite_Booki_Move     ; Handle movement
    RTS
  }
}

2.4. Sprite_Booki_Move (Movement and Interaction Logic)

This routine is called by the various states in Sprite_Booki_Main to handle Booki's physical interactions and movement. It also manages Booki's "float" behavior (SlowFloat or FloatAway) based on SprMiscB.

Sprite_Booki_Move:
{
  JSL Sprite_Move                     ; Apply velocity
  JSL Sprite_BounceFromTileCollision  ; Handle collision with tiles
  JSL Sprite_PlayerCantPassThrough    ; Prevent player from passing through Booki
  JSL Sprite_DamageFlash_Long         ; Handle damage flashing
  JSL Sprite_CheckIfRecoiling         ; Check for recoil state

  JSL Sprite_IsToRightOfPlayer : CPY.b #$01 : BNE .ToRight ; Determine if Booki is to the right of Link
    LDA.b #$01 : STA.w SprMiscC, X     ; Set SprMiscC to 1 (for horizontal flip)
    JMP .Continue
  .ToRight
  STZ.w SprMiscC, X                   ; Set SprMiscC to 0 (no flip)
  .Continue

  JSL Sprite_CheckDamageToPlayer      ; Check if Booki damages Link
  JSL Sprite_CheckDamageFromPlayer : BCC .no_damage ; Check if Link damages Booki
    LDA.b #$01 : STA.w SprMiscB, X     ; If damaged, set SprMiscB to 1 (FloatAway state)
  .no_damage

  LDA.w SprMiscB, X
  JSL JumpTableLocal                  ; Jump to movement routine based on SprMiscB

  dw SlowFloat                        ; SprMiscB = 0
  dw FloatAway                        ; SprMiscB = 1

  SlowFloat:
  {
    LDY #$04
    JSL GetRandomInt : AND.b #$04     ; Introduce some randomness to movement
    JSL Sprite_FloatTowardPlayer      ; Float towards Link

    PHX
    JSL Sprite_DirectionToFacePlayer  ; Update facing direction
    ; Check if too close to player
    LDA.b $0E : CMP.b #$1A : BCS .NotTooClose
    LDA.b $0F : CMP.b #$1A : BCS .NotTooClose
      LDA.b #$01 : STA.w SprMiscB, X   ; If too close, switch to FloatAway
      LDA.b #$20 : STA.w SprTimerA, X  ; Set timer
      %GotoAction(1)                  ; Transition to HideFromPlayer state
    .NotTooClose
    PLX

    RTS
  }

  FloatAway:
  {
    JSL GetRandomInt : AND.b #$04     ; Introduce some randomness to movement
    JSL Sprite_FloatAwayFromPlayer    ; Float away from Link

    PHX
    JSL Sprite_DirectionToFacePlayer  ; Update facing direction
    ; Check if far enough from player
    LDA.b $0E : CMP.b #$1B : BCC .NotTooClose
    LDA.b #$1B : CMP.b $0F : BCC .NotTooClose ; Corrected comparison for $0F
      LDA.b #$00 : STA.w SprMiscB, X   ; If far enough, switch to SlowFloat
      %GotoAction(0)                  ; Transition to StalkPlayer state
    .NotTooClose
    PLX

    RTS
  }
}

2.5. Sprite_Booki_Draw (Drawing Routine)

This routine is responsible for rendering Booki's graphics. It uses a custom OAM (Object Attribute Memory) allocation and manipulation logic rather than the %DrawSprite() macro. It dynamically determines the animation frame and applies horizontal flipping based on SprMiscC.

Sprite_Booki_Draw:
{
  JSL Sprite_PrepOamCoord             ; Prepare OAM coordinates
  JSL Sprite_OAM_AllocateDeferToPlayer ; Allocate OAM slots, deferring to player

  LDA.w SprGfx, X : CLC : ADC.w SprFrame, X : TAY ; Calculate animation frame index
  LDA .start_index, Y : STA $06       ; Store start index for tiles

  LDA.w SprFlash, X : STA $08         ; Store flash status
  LDA.w SprMiscC, X : STA $09         ; Store horizontal flip status (0 or 1)

  PHX
  LDX .nbr_of_tiles, Y                ; Load number of tiles for current frame (minus 1)
  LDY.b #$00                          ; Initialize Y for OAM buffer offset
  .nextTile

  PHX                                 ; Save current Tile Index
  TXA : CLC : ADC $06                 ; Add Animation Index Offset
  PHA                                 ; Keep the value with animation index offset

  ASL A : TAX                         ; Multiply by 2 for word access

  REP #$20                            ; Set A to 16-bit mode

  LDA $00 : STA ($90), Y              ; Store Y-coordinate
  AND.w #$0100 : STA $0E              ; Check if Y-coord is off-screen (high bit)
  INY
  LDA $02 : STA ($90), Y              ; Store X-coordinate
  CLC : ADC #$0010 : CMP.w #$0100     ; Check if X-coord is off-screen
  SEP #$20                            ; Set A to 8-bit mode
  BCC .on_screen_y                    ; If on screen, continue

  LDA.b #$F0 : STA ($90), Y           ; If off-screen, move sprite off-screen
  STA $0E
  .on_screen_y

  PLX                                 ; Restore Tile Index
  INY
  LDA .chr, X : STA ($90), Y          ; Store character (tile) number
  INY

  LDA.b $09 : BEQ .ToRight            ; Check SprMiscC for horizontal flip
  LDA.b #$29 : JMP .Prop              ; If 1, use properties for flipped
  .ToRight
  LDA.b #$69                          ; If 0, use properties for normal
  .Prop
  ORA $08 : STA ($90), Y              ; Apply flash and store OAM properties

  PHY

  TYA : LSR #2 : TAY                  ; Calculate OAM buffer index for size
  LDA.b #$02 : ORA $0F : STA ($92), Y ; Store size (16x16) in OAM buffer

  PLY : INY

  PLX : DEX : BPL .nextTile           ; Loop for next tile

  PLX                                 ; Restore X (sprite index)

  RTS

  ; =========================================================
  ; OAM Data Tables
  .start_index
  db $00, $01, $02, $03, $04, $05, $06, $07, $08, $09
  .nbr_of_tiles
  db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; All frames use 1 tile (0-indexed)
  .chr
  db $0E, $0C, $0A, $2C, $2E, $2E, $0A, $2C, $0C, $0E ; Tile numbers for each frame
}

3. Key Behaviors and Implementation Details

  • Dynamic Health: Booki's health is not a fixed property but is determined at spawn time based on Link's current sword level. This allows for dynamic difficulty scaling.
  • State Management: Booki employs a robust state machine using SprAction, X and JumpTableLocal to manage its behaviors: StalkPlayer, HideFromPlayer, HiddenFromPlayer, and ApproachPlayer. Transitions between these states are triggered by timers, random chance, or player proximity.
  • Player Interaction:
    • Stalking/Approaching: Booki uses Sprite_FloatTowardPlayer to move towards Link.
    • Hiding/Floating Away: Booki uses Sprite_FloatAwayFromPlayer to retreat from Link, often triggered by taking damage or getting too close.
    • Damage: Booki can damage Link on contact (Sprite_CheckDamageToPlayer) and reacts to damage from Link by transitioning to a FloatAway state.
  • Directional Facing: SprMiscC, X is used as a flag to control horizontal flipping in the drawing routine, ensuring Booki always faces Link.
  • Custom OAM Drawing: Unlike many sprites that might use the %DrawSprite() macro, Booki implements its OAM drawing logic directly. This provides fine-grained control over its appearance, including dynamic tile selection and horizontal flipping. The REP #$20 and SEP #$20 instructions are used to temporarily switch the accumulator to 16-bit mode for coordinate calculations, demonstrating careful management of the Processor Status Register.
  • Randomness: GetRandomInt is used to introduce variability in Booki's movement patterns and state transitions, making its behavior less predictable.
  • SprMiscB Usage: This variable acts as a sub-state for movement, toggling between SlowFloat (approaching) and FloatAway (retreating) behaviors.
  • SprTimerA Usage: Used in the HideFromPlayer state to control how long Booki remains in that state before transitioning.
  • Sprite_PlayerCantPassThrough: Ensures Booki acts as a solid object that Link cannot simply walk through.