Files
oracle-of-secrets/Docs/Sprites/Enemies/Poltergeist.md
scawful aede7551a3 Add new sprite documentation for Minecart, Pedestal, Portal, and Switch Track
- Created detailed documentation for the Minecart sprite, outlining its properties, constants, collision setup, main logic, and design patterns.
- Added documentation for the Pedestal sprite, including its vanilla overrides, custom logic for item interaction, and event triggering based on area context.
- Introduced documentation for the Portal sprite, detailing its two-way warping system, initialization, main logic, and helper routines for seamless transitions.
- Documented the Switch Track sprite, explaining its interactive behavior, state-based animation, and integration with external switches for dynamic track manipulation.
2025-10-03 01:52:48 -04:00

7.9 KiB

Poltergeist Sprite Analysis

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

1. Sprite Properties

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

!SPRID              = Sprite_PolsVoice
!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             = 10  ; Number of Health the sprite have
!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 is set to 10 and is dynamically determined during initialization based on Link's sword level.

2. Core Routines

2.1. Sprite_Poltergeist_Long (Main Loop)

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

Sprite_Poltergeist_Long:
{
  PHB : PHK : PLB
  JSR Sprite_Poltergeist_Draw
  JSL Sprite_DrawShadow
  JSL Sprite_CheckActive : BCC .SpriteIsNotActive
    JSR Sprite_Poltergeist_Main
  .SpriteIsNotActive
  PLB
  RTL
}

2.2. Sprite_Poltergeist_Prep (Initialization)

This routine is executed once when Poltergeist is first spawned. It sets its health based on Link's sword level and initializes SprTimerA and SprTimerB.

Sprite_Poltergeist_Prep:
{
  PHB : PHK : PLB
  LDA.l Sword : DEC A : TAY
  LDA.w .health, Y : STA.w SprHealth, X
  LDA.b #$80 : STA.w SprTimerA, X
  LDA.b #$80 : STA.w SprTimerB, X
  PLB
  RTL

  .health
    db $06, $0A, $0C, $10
}

2.3. Sprite_Poltergeist_Main (Behavioral State Machine)

This routine manages Poltergeist's AI through a state machine, using SprAction, X to determine its current behavior. It includes states for moving, attacking, and being stunned.

Sprite_Poltergeist_Main:
{
  JSL Sprite_DamageFlash_Long

  %SpriteJumpTable(Poltergeist_Move,
                   Poltergeist_Attack,
                   Poltergeist_Stunned)

  Poltergeist_Move:
  {
    %PlayAnimation(0, 1, 16)
    JSR Sprite_Poltergeist_Move
    RTS
  }

  Poltergeist_Attack:
  {
    %PlayAnimation(2, 3, 16)
    JSL Sprite_Move
    JSL Sprite_CheckDamageToPlayer
    LDA.w SprTimerA, X : BNE + ; If timer A is not 0
      %GotoAction(0) ; Transition back to Poltergeist_Move
    +
    RTS
  }

  Poltergeist_Stunned:
  {
    %PlayAnimation(4, 5, 16)
    JSL Sprite_Move
    JSL Sprite_CheckDamageToPlayer
    LDA.w SprTimerA, X : BNE + ; If timer A is not 0
      %GotoAction(0) ; Transition back to Poltergeist_Move
    +
    RTS
  }
}

2.4. Sprite_Poltergeist_Move (Movement Logic)

This routine handles Poltergeist's movement patterns, including moving towards Link, bouncing from tile collisions, and changing direction randomly.

Sprite_Poltergeist_Move:
{
  JSL Sprite_Move
  JSL Sprite_BounceFromTileCollision
  JSL Sprite_PlayerCantPassThrough
  JSL Sprite_CheckIfRecoiling

  LDA.w SprTimerC, X : BNE ++ ; Check timer C
    JSL GetRandomInt : AND #$3F : BNE ++ ; Random chance to change direction
      LDA.b #$40 : STA.w SprTimerC, X
      JSL Sprite_SelectNewDirection
  ++

  LDA.w SprTimerA, X : BNE + ; Check timer A
    JSL Sprite_IsToRightOfPlayer : CPY.b #$01 : BNE .ToRight
      %GotoAction(1) ; Transition to Poltergeist_Attack
      JMP .Continue
    .ToRight
    %GotoAction(1) ; Transition to Poltergeist_Attack
    LDA.b #$20 : STA.w SprTimerA, X
    JMP .Continue
  +
  %GotoAction(0) ; Transition to Poltergeist_Move
  .Continue

  LDA.w SprMiscB, X
  JSL JumpTableLocal

  dw PoltergeistMove

  PoltergeistMove:
  {
    JSL GetRandomInt : AND.b #$03
    JSL Sprite_ApplySpeedTowardsPlayer
    JSL Sprite_CheckTileCollision

    JSL Sprite_CheckDamageFromPlayer
    JSL Sprite_CheckDamageToPlayer

    RTS
  }
}

2.5. Sprite_Poltergeist_Draw (Drawing Routine)

This routine is responsible for rendering Poltergeist's graphics. It uses the %DrawSprite() macro, which reads from a set of data tables to handle its multi-tile appearance and animation.

Sprite_Poltergeist_Draw:
{
  %DrawSprite()

  .start_index
    db $00, $03, $06, $09, $0C, $0F
  .nbr_of_tiles
    db 2, 2, 2, 2, 2, 2
  .x_offsets
    dw 0, 0, 8
    dw 8, 0, 0
    dw 0, 0, 8
    dw 0, 0, 8
    dw 0, 8, 0
    dw 0, 8, 0
  .y_offsets
    dw -8, 0, -8
    dw -8, 0, -8
    dw 0, -8, -8
    dw 0, -8, -8
    dw 0, -8, -8
    dw 0, -8, -8
  .chr
    db $3A, $02, $3B
    db $3A, $02, $3B
    db $20, $00, $01
    db $22, $10, $11
    db $20, $00, $01
    db $22, $10, $11
  .properties
    db $2B, $2B, $2B
    db $6B, $6B, $6B
    db $2B, $2B, $2B
    db $2B, $2B, $2B
    db $6B, $6B, $6B
    db $6B, $6B, $6B
  .sizes
    db $00, $02, $00
    db $00, $02, $00
    db $02, $00, $00
    db $02, $00, $00
    db $02, $00, $00
    db $02, $00, $00
}

3. Key Behaviors and Implementation Details

  • Dynamic Health: Poltergeist's health is determined at spawn time based on Link's current sword level, allowing for dynamic difficulty scaling.
  • State Management: Poltergeist uses SprAction, X and %SpriteJumpTable to manage its Poltergeist_Move, Poltergeist_Attack, and Poltergeist_Stunned states. Transitions between these states are triggered by timers and player proximity.
  • Movement Patterns: Poltergeist moves towards Link (Sprite_ApplySpeedTowardsPlayer) with random direction changes (Sprite_SelectNewDirection). It also handles bouncing from tile collisions and cannot be passed through by Link.
  • Attack Behavior: Poltergeist transitions to an Poltergeist_Attack state, which likely involves a direct contact attack or a projectile, and then returns to its movement state after a timer.
  • Stunned State: When damaged, Poltergeist enters a Poltergeist_Stunned state, during which it is temporarily incapacitated. It recovers from this state after a timer.
  • Conditional Invulnerability: The sprite properties indicate !ImpervSwordHammer = 00, but the code does not explicitly set it to 01 when stunned. This might be an oversight or handled by a global routine. However, the presence of SprDefl in _Prep suggests some form of deflection is intended.
  • Custom OAM Drawing: Poltergeist uses the %DrawSprite() macro with detailed OAM data tables to render its multi-tile appearance and animations across its different states.
  • SprTimerA, SprTimerB, SprTimerC Usage: These timers control the duration of attack and stunned states, and the frequency of direction changes.