Files
oracle-of-secrets/Docs/Sprites/NPCs/DekuScrub.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

8.8 KiB

Deku Scrub

Overview

The Deku Scrub sprite is a highly versatile NPC implementation capable of representing multiple distinct characters, including a Withered Deku Scrub, Deku Butler, and Deku Princess. Its behavior is intricately tied to game progression, player actions, and specific in-game locations.

Sprite Properties

  • !SPRID: Sprite_DekuScrubNPCs (Custom symbol, likely a remapped vanilla ID)
  • !NbrTiles: 06
  • !Harmless: 01
  • !HVelocity: 00
  • !Health: 00
  • !Damage: 00
  • !DeathAnimation: 00
  • !ImperviousAll: 00
  • !SmallShadow: 00
  • !Shadow: 00
  • !Palette: 00
  • !Hitbox: 03
  • !Persist: 00
  • !Statis: 00
  • !CollisionLayer: 00
  • !CanFall: 00
  • !DeflectArrow: 00
  • !WaterSprite: 00
  • !Blockable: 00
  • !Prize: 00
  • !Sound: 00
  • !Interaction: 00
  • !Statue: 00
  • !DeflectProjectiles: 00
  • !ImperviousArrow: 00
  • !ImpervSwordHammer: 00
  • !Boss: 00

Main Structure (Sprite_DekuScrub_Long)

This routine is the main entry point for the Deku Scrub, executed every frame. It handles drawing and dispatches to the main logic if the sprite is active.

Sprite_DekuScrub_Long:
{
  PHB : PHK : PLB
  JSR Sprite_DekuScrub_Draw
  JSL Sprite_CheckActive : BCC .SpriteIsNotActive
    JSR Sprite_DekuScrub_Main
  .SpriteIsNotActive
  PLB
  RTL
}

Initialization (Sprite_DekuScrub_Prep)

This routine runs once when the Deku Scrub is spawned. It sets SprDefl, X and then determines the initial SprAction, X based on the current AreaIndex, SprSubtype, X, and whether the Deku Mask has been obtained ($7EF301). It also checks if Tail Palace is cleared (Crystals) to potentially set SprState, X to 0.

Sprite_DekuScrub_Prep:
{
  PHB : PHK : PLB
  LDA.b #$80 : STA.w SprDefl, X

  ; Peacetime Deku Scrub NPCs
  LDA.b AreaIndex : CMP.b #$2E : BNE .check_next
    ; Deku Butler
    LDA.b #$07 : STA.w SprAction, X
    JMP +
  .check_next
  CMP.b #$2F : BNE .continue
    LDA.b #$08 : STA.w SprAction, X
    JMP +
  .continue

  LDA.w SprSubtype, X : CMP.b #$01 : BEQ .DekuButler
                        CMP.b #$02 : BEQ .DekuPrincess
  LDA.l $7EF301 : BEQ +
    LDA.b #$04 : STA.w SprAction, X
    JMP +
  .DekuButler
    LDA.b #$05 : STA.w SprAction, X
    JMP ++
  .DekuPrincess
    LDA.b #$06 : STA.w SprAction, X
    ++
    ; Check if tail palace is cleared
    LDA.l Crystals : AND #$10 : BEQ +
      STZ.w SprState, X
  +
  PLB
  RTL
}

Main Logic & State Machine (Sprite_DekuScrub_Main)

The Deku Scrub's core behavior is managed by a complex state machine with several states, many of which are named in Spanish:

  • EstadoInactivo (Inactive State): The scrub plays an idle animation, prevents player passage, and transitions to QuiereCuracion upon player interaction.
  • QuiereCuracion (Wants Healing): Plays an animation and checks if the "Song of Healing" (SongFlag) has been played. If so, it clears the flag, sets a timer, and transitions to DarMascara.
  • DarMascara (Give Mask): Plays an animation, displays a message after a timer, and then transitions to Regalo.
  • Regalo (Gift): After a timer, grants Link the Deku Mask ($11) and updates the Deku Mask flag ($7EF301), then transitions to Withered.
  • Withered: Plays a withered animation.
  • DekuButler: Plays a specific animation, prevents player passage, and displays a message.
  • DekuPrincess: Plays a specific animation, prevents player passage, and displays a message.
  • DekuButler_Peacetime: Plays a specific animation, prevents player passage, and displays a message. If the message is dismissed, it sets MapIcon to $02.
  • DekuPrinces_Peacetime: Plays a specific animation, prevents player passage, and displays a message. If the message is dismissed, it sets MapIcon to $02.
Sprite_DekuScrub_Main:
{
  LDA.w SprAction, X
  JSL   JumpTableLocal

  dw EstadoInactivo
  dw QuiereCuracion
  dw DarMascara
  dw Regalo
  dw Withered
  dw DekuButler
  dw DekuPrincess

  dw DekuButler_Peacetime
  dw DekuPrinces_Peacetime

  EstadoInactivo:
  {
    %PlayAnimation(0, 1, 16)
    JSL Sprite_PlayerCantPassThrough
    %ShowSolicitedMessage($140) : BCC .no_hablaba
      %GotoAction(1)
    .no_hablaba
    RTS
  }

  QuiereCuracion:
  {
    %PlayAnimation(0, 1, 16)
    LDA.b SongFlag : CMP.b #$01 : BNE .ninguna_cancion
      STZ.b SongFlag
      LDA.b #$C0 : STA.w SprTimerD, X
      %GotoAction(2)
    .ninguna_cancion
    RTS
  }

  DarMascara:
  {
    %PlayAnimation(0, 1, 16)
    LDA.w SprTimerD, X : BNE +
      %ShowUnconditionalMessage($141)
      LDA.b #$C0 : STA.w SprTimerD, X
      %GotoAction(3)
    +
    RTS
  }

  Regalo:
  {
    LDA.w SprTimerD, X : BNE +
      LDY   #$11 : STZ $02E9     ; Give the Deku Mask
      JSL   Link_ReceiveItem
      LDA.b #$01 : STA.l $7EF301
      %GotoAction(4)
    +
    RTS
  }

  Withered:
  {
    %PlayAnimation(2, 2, 10)
    RTS
  }

  DekuButler:
  {
    %PlayAnimation(3, 3, 10)
    JSL Sprite_PlayerCantPassThrough
    %ShowSolicitedMessage($080)
    RTS
  }

  DekuPrincess:
  {
    %PlayAnimation(4, 4, 10)
    JSL Sprite_PlayerCantPassThrough
    %ShowSolicitedMessage($0C3)
    RTS
  }

  DekuButler_Peacetime:
  {
    %StartOnFrame(3)
    %PlayAnimation(3, 3, 10)
    JSL Sprite_PlayerCantPassThrough
    %ShowSolicitedMessage($1B9) : BCC +
      LDA.b #$02 : STA.l MapIcon
    +
    RTS
  }

  DekuPrinces_Peacetime:
  {
    %StartOnFrame(4)
    %PlayAnimation(4, 4, 10)
    JSL Sprite_PlayerCantPassThrough
    %ShowSolicitedMessage($1BA) : BCC +
      LDA.b #$02 : STA.l MapIcon
    +
    RTS
  }
}

Drawing (Sprite_DekuScrub_Draw)

The drawing routine handles OAM allocation and animation. It explicitly uses REP #$20 and SEP #$20 for 16-bit coordinate calculations.

Sprite_DekuScrub_Draw:
{
  JSL Sprite_PrepOamCoord
  JSL Sprite_OAM_AllocateDeferToPlayer

  LDA $0DC0, X : CLC : ADC $0D90, X : TAY;Animation Frame
  LDA .start_index, Y : STA $06

  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
  LDA .chr, X : STA ($90), Y
  INY
  LDA .properties, X : 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, $04, $08, $0C, $10
  .nbr_of_tiles
    db 3, 3, 3, 3, 3
  .x_offsets
    dw 4, 4, -4, -4
    dw 4, -4, -4, 4
    dw -8, -8, 8, 8
    dw -4, 4, -4, 4
    dw -4, -4, 4, 4
  .y_offsets
    dw 4, -4, -4, 4
    dw 4, 4, -4, -4
    dw 4, -12, -12, 4
    dw -12, -12, 4, 4
    dw 4, -12, 4, -12
  .chr
    db $2E, $0E, $0E, $2E
    db $2C, $2C, $0C, $0C
    db $20, $00, $02, $22
    db $04, $05, $24, $25
    db $27, $07, $27, $07
  .properties
    db $3B, $7B, $3B, $7B
    db $3B, $7B, $3B, $7B
    db $3B, $3B, $3B, $3B
    db $3B, $3B, $3B, $3B
    db $3B, $3B, $7B, $7B
  .sizes
    db $02, $02, $02, $02
    db $02, $02, $02, $02
    db $02, $02, $02, $02
    db $02, $02, $02, $02
    db $02, $02, $02, $02
}

Design Patterns

  • Multi-Character NPC: A single sprite definition is used to represent multiple distinct NPC characters (Withered Deku Scrub, Deku Butler, Deku Princess), with their specific roles determined by SprSubtype and AreaIndex.
  • Quest Progression Integration: The sprite's behavior is deeply integrated with various quest elements, checking for specific items (Deku Mask), songs (Song of Healing), and cleared dungeons (Tail Palace) to determine its current state and interactions.
  • Conditional Behavior: Extensive use of conditional logic based on AreaIndex, SprSubtype, and global game state flags allows for dynamic changes in the NPC's role, dialogue, and actions.
  • NPC Interaction: Provides rich interaction with the player through dialogue (%ShowSolicitedMessage, %ShowUnconditionalMessage) and the granting of key items (Link_ReceiveItem).
  • Player Collision: Implements Sprite_PlayerCantPassThrough to make the NPC a solid object that Link cannot walk through.
  • 16-bit OAM Calculations: Demonstrates explicit use of REP #$20 and SEP #$20 for precise 16-bit OAM coordinate calculations, essential for accurate sprite rendering.