Files
oracle-of-secrets/Docs/Sprites/Enemies/Keese.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.1 KiB

Keese

Overview

The Keese sprite (!SPRID = $11) is a versatile enemy that encompasses multiple variations: Ice Keese, Fire Keese, and Vampire Bat. Its behavior is dynamically determined by its SprSubtype.

Subtypes

  • 00 - Ice Keese
  • 01 - Fire Keese
  • 02 - Vampire Bat

Sprite Properties

  • !SPRID: $11 (Vanilla sprite ID for Keese/Vampire Bat)
  • !NbrTiles: 08
  • !Harmless: 00
  • !HVelocity: 00
  • !Health: 00 (Dynamically set in _Prep based on subtype)
  • !Damage: 00 (Damage is handled by projectiles or specific attack logic)
  • !DeathAnimation: 00
  • !ImperviousAll: 00
  • !SmallShadow: 00
  • !Shadow: 01
  • !Palette: 00
  • !Hitbox: 00
  • !Persist: 00
  • !Statis: 00
  • !CollisionLayer: 00
  • !CanFall: 00
  • !DeflectArrow: 00
  • !WaterSprite: 00
  • !Blockable: 00
  • !Prize: 00 (Dynamically set in _Prep based on subtype)
  • !Sound: 00
  • !Interaction: 00
  • !Statue: 00
  • !DeflectProjectiles: 00
  • !ImperviousArrow: 00
  • !ImpervSwordHammer: 00
  • !Boss: 00

Main Structure (Sprite_Keese_Long)

This routine dispatches to different drawing and main logic routines based on the sprite's SprSubtype.

Sprite_Keese_Long:
{
  PHB : PHK : PLB
  LDA.w SprSubtype, X : CMP.b #$02 : BEQ +
    JSR Sprite_Keese_Draw
    JSL Sprite_DrawShadow
    JSL Sprite_CheckActive : BCC .SpriteIsNotActive
      JSR Sprite_Keese_Main
    .SpriteIsNotActive
    JMP ++
  +
  JSR Sprite_VampireBat_Draw
  JSL Sprite_DrawShadow
  JSL Sprite_CheckActive : BCC ++
    JSR Sprite_VampireBat_Main
  ++
  PLB
  RTL
}

Initialization (Sprite_Keese_Prep)

This routine initializes sprite properties upon spawning, including health and prize, based on its subtype.

Sprite_Keese_Prep:
{
  PHB : PHK : PLB
  LDA.b #$80 : STA.w SprDefl, X
  LDA.b #$30 : STA.w SprTimerC, X
  LDA.w SprSubtype, X : CMP.b #$02 : BNE +
    LDA.b #$20 : STA.w SprHealth, X
    BRA ++
  +
  LDA.b #$03 : STA.w SprNbrOAM, X
  LDA.b #$03 : STA.w SprPrize, X
  ++
  PLB
  RTL
}

Main Logic & State Machine (Sprite_Keese_Main)

The Keese's behavior is managed by a state machine with Keese_Idle and Keese_FlyAround states.

  • Keese_Idle: The sprite remains stationary until Link is within a certain distance, then transitions to Keese_FlyAround.
  • Keese_FlyAround: The Keese flies around, plays an animation, checks for collisions with Link and tiles, and can initiate an attack. It uses GetRandomInt for varied movement and Sprite_ProjectSpeedTowardsPlayer to move towards Link.
Sprite_Keese_Main:
{
  LDA.w SprAction, X
  JSL JumpTableLocal

  dw Keese_Idle
  dw Keese_FlyAround

  Keese_Idle:
  {
    STZ.w SprFrame, X
    ; Wait til the player is nearby then fly around
    LDA.w SprTimerC, X : BEQ .move
    JSL GetDistance8bit_Long : CMP.b #$20 : BCS +
      .move
      INC.w SprAction, X
      JSL GetRandomInt
      STA.w SprTimerA, X
    +
    RTS
  }

  Keese_FlyAround:
  {
    %PlayAnimation(0,5,8)
    JSL Sprite_CheckDamageToPlayer
    JSL Sprite_CheckDamageFromPlayer : BCC +
      JSL ForcePrizeDrop_long
    +
    JSL Sprite_DamageFlash_Long
    JSL Sprite_BounceFromTileCollision

    JSL GetRandomInt : AND.b #$3F : BNE +
      LDA.b #$10 : STA.w SprTimerC, X
    +
    JSR Sprite_Keese_Attack

    LDA.w SprTimerA, X : AND.b #$10 : BNE +
      LDA.b #$40
      JSL Sprite_ProjectSpeedTowardsPlayer
    +

    JSL Sprite_SelectNewDirection
    JSL Sprite_Move

    LDA.w SprTimerA, X : BNE +
      STZ.w SprAction, X
    +
    RTS
  }
}

Attack Logic (Sprite_Keese_Attack)

This routine handles the Keese's attack, which varies by subtype:

  • Ice Keese (SprSubtype = 0): Spawns sparkle garnish and a blind laser trail.
  • Fire Keese (SprSubtype = 1): Utilizes Sprite_Twinrova_FireAttack.
Sprite_Keese_Attack:
{
  LDA.w SprTimerC, X : BEQ +
    LDA.w SprSubtype, X : BEQ ++
      JSL Sprite_Twinrova_FireAttack
      JMP +
    ++
    JSL Sprite_SpawnSparkleGarnish
    JSL BlindLaser_SpawnTrailGarnish
  +
  RTS
}

Drawing (Sprite_Keese_Draw)

The drawing routine handles OAM allocation, animation, and palette adjustments based on the sprite's subtype. It explicitly uses REP #$20 and SEP #$20 for 16-bit coordinate calculations.

Sprite_Keese_Draw:
{
  JSL Sprite_PrepOamCoord
  JSL Sprite_OAM_AllocateDeferToPlayer

  LDA.w SprFrame, X : TAY ;Animation Frame
  LDA .start_index, Y : STA $06
  LDA.w SprFlash, X : STA $08
  LDA.w SprMiscB, X : STA $09
  LDA.w SprSubtype, X : CMP.b #$01 : BNE +
    LDA.b #$0A : EOR $08 : STA $08
  +

  PHX
  LDX .nbr_of_tiles, Y ;amount of tiles -1
  LDY.b #$00
  .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

  REP #$20

  LDA $00 : CLC : ADC .x_offsets, X : STA ($90), Y
  AND.w #$0100 : STA $0E
  INY
  LDA $02 : CLC : ADC .y_offsets, X : STA ($90), Y
  CLC : ADC #$0010 : CMP.w #$0100
  SEP #$20
  BCC .on_screen_y

  LDA.b #$F0 : STA ($90), Y ;Put the sprite out of the way
  STA $0E
  .on_screen_y

  PLX ; Pullback Animation Index Offset (without the *2 not 16bit anymore)
  INY

  ; If SprMiscA != 0, then use 4th sheet
  LDA.b $09 : BEQ +
    LDA .chr_2, X : STA ($90), Y
    JMP ++
  +
  LDA .chr, X : STA ($90), Y
  ++
  INY
  LDA .properties, X : ORA $08 : STA ($90), Y

  PHY

  TYA : LSR #2 : TAY

  LDA .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer

  PLY : INY

  PLX : DEX : BPL .nextTile

  PLX

  RTS

  .start_index
  db $00, $01, $03, $04, $06, $08
  .nbr_of_tiles
  db 0, 1, 0, 1, 1, 0
  .x_offsets
  dw 0
  dw -4, 4
  dw 0
  dw -4, 4
  dw -4, 4
  dw 0
  .y_offsets
  dw 0
  dw 0, 0
  dw 0
  dw 0, 0
  dw 0, 0
  dw 0
  .chr
  db $80
  db $A2, $A2
  db $82
  db $84, $84
  db $A4, $A4
  db $A0
  .chr_2
  db $C0
  db $E2, $E2
  db $C2
  db $C4, $C4
  db $E4, $E4
  db $E0
  .properties
  db $35
  db $35, $75
  db $35
  db $35, $75
  db $35, $75
  db $35
  .sizes
  db $02
  db $02, $02
  db $02
  db $02, $02
  db $02, $02
  db $02
}

Design Patterns

  • Subtype-based Behavior: The sprite uses SprSubtype to implement distinct behaviors and appearances for Ice Keese, Fire Keese, and Vampire Bat, all under a single !SPRID.
  • Dynamic Property Initialization: Health and prize values are set dynamically during the _Prep routine based on the sprite's subtype.
  • Conditional Drawing and Palette: The drawing routine adjusts the sprite's palette and potentially its graphics based on its subtype, allowing for visual differentiation.
  • Randomized Movement: Utilizes GetRandomInt to introduce variability in movement patterns, making the enemy less predictable.
  • Projectile Attacks: Implements different projectile attacks based on subtype, showcasing varied offensive capabilities.
  • 16-bit OAM Calculations: Demonstrates explicit use of REP #$20 and SEP #$20 for precise 16-bit OAM coordinate calculations, which is crucial for accurate sprite rendering.