Add documentation for custom enemy sprites: Eon Scrub, Keese, Leever, and Octorok

- Created detailed markdown files for Eon Scrub, Keese, Leever, and Octorok sprites.
- Included sprite properties, main logic, drawing routines, and design patterns for each sprite.
- Highlighted unique behaviors, state machines, and interactions with the player for each enemy type.
- Ensured clarity in the implementation details and provided assembly code snippets for reference.
This commit is contained in:
scawful
2025-10-03 00:31:19 -04:00
parent 1cc7d84782
commit 8c3bf9d95b
6 changed files with 1342 additions and 2 deletions

View File

@@ -125,6 +125,8 @@ Sprite_MyNewEnemy_Main:
This routine renders your sprite's graphics. The easiest method is to use the `%DrawSprite()` macro, which reads from a set of data tables you define. However, for highly customized drawing logic, such as dynamic tile selection, complex animation sequences, or precise OAM manipulation (as demonstrated in the Booki sprite's `Sprite_Booki_Draw` routine, which uses `REP`/`SEP` for 16-bit coordinate calculations), you may need to implement a custom drawing routine. This routine renders your sprite's graphics. The easiest method is to use the `%DrawSprite()` macro, which reads from a set of data tables you define. However, for highly customized drawing logic, such as dynamic tile selection, complex animation sequences, or precise OAM manipulation (as demonstrated in the Booki sprite's `Sprite_Booki_Draw` routine, which uses `REP`/`SEP` for 16-bit coordinate calculations), you may need to implement a custom drawing routine.
**Important Note on 16-bit OAM Calculations:** When performing OAM (Object Attribute Memory) calculations, especially for sprite positioning and offsets, it is crucial to explicitly manage the Processor Status Register (P) flags. Use `REP #$20` to set the accumulator to 16-bit mode before performing 16-bit arithmetic operations (e.g., adding `x_offsets` or `y_offsets` to sprite coordinates), and `SEP #$20` to restore it to 8-bit mode afterward if necessary. This ensures accurate positioning and prevents unexpected graphical glitches or crashes.
```asm ```asm
Sprite_MyNewEnemy_Draw: Sprite_MyNewEnemy_Draw:
{ {
@@ -221,11 +223,23 @@ For more engaging and adaptive enemies, consider implementing dynamic behaviors:
* **Dynamic Appearance and Conditional Vulnerability:** Sprites can dynamically change their appearance and vulnerability based on internal states or player actions. For instance, the Helmet Chuchu (`Sprites/Enemies/helmet_chuchu.asm`) changes its graphics and becomes vulnerable only after its helmet/mask is removed, often by a specific item like the Hookshot. * **Dynamic Appearance and Conditional Vulnerability:** Sprites can dynamically change their appearance and vulnerability based on internal states or player actions. For instance, the Helmet Chuchu (`Sprites/Enemies/helmet_chuchu.asm`) changes its graphics and becomes vulnerable only after its helmet/mask is removed, often by a specific item like the Hookshot.
* **Stunned State and Counter-Attack:** Some sprites react to damage by entering a stunned state, then performing a counter-attack (e.g., Puffstool spawning spores or transforming into a bomb). * **Stunned State and Counter-Attack:** Some sprites react to damage by entering a stunned state, then performing a counter-attack (e.g., Puffstool spawning spores or transforming into a bomb).
* **Interaction with Thrown Objects:** Sprites can be designed to interact with objects thrown by Link (e.g., `ThrownSprite_TileAndSpriteInteraction_long` used by Puffstool), allowing for environmental puzzles or unique combat strategies. * **Interaction with Thrown Objects:** Sprites can be designed to interact with objects thrown by Link (e.g., `ThrownSprite_TileAndSpriteInteraction_long` used by Puffstool), allowing for environmental puzzles or unique combat strategies.
* **Interaction with Non-Combat Player Actions:** Sprites can react to specific player actions beyond direct attacks, such as playing a musical instrument. The Pols Voice (`Sprites/Enemies/pols_voice.asm`) despawns and drops a prize if Link plays the flute. * **Interaction with Non-Combat Player Actions:** Sprites can react to specific player actions beyond direct attacks, suchs as playing a musical instrument. The Pols Voice (`Sprites/Enemies/pols_voice.asm`) despawns and drops a prize if Link plays the flute.
* **Specific Movement Routines for Attacks:** During attack animations, sprites may utilize specialized movement routines like `Sprite_MoveLong` (seen in Thunder Ghost) to control their position and trajectory precisely. * **Specific Movement Routines for Attacks:** During attack animations, sprites may utilize specialized movement routines like `Sprite_MoveLong` (seen in Thunder Ghost) to control their position and trajectory precisely.
* **Damage Reaction (Invert Speed):** A common dynamic behavior is for a sprite to invert its speed or change its movement pattern upon taking damage, as seen in Pols Voice. * **Damage Reaction (Invert Speed):** A common dynamic behavior is for a sprite to invert its speed or change its movement pattern upon taking damage, as seen in Pols Voice.
* **Global State-Dependent Behavior:** Sprite properties and behaviors can be dynamically altered based on global game state variables (e.g., `WORLDFLAG` in the Sea Urchin sprite), allowing for different enemy characteristics in various areas or game progression points. * **Global State-Dependent Behavior:** Sprite properties and behaviors can be dynamically altered based on global game state variables (e.g., `WORLDFLAG` in the Sea Urchin sprite), allowing for different enemy characteristics in various areas or game progression points.
* **Simple Movement/Idle:** Not all sprites require complex movement. Some may have simple idle animations and primarily interact through contact damage or being pushable, as exemplified by the Sea Urchin. * **Simple Movement/Idle:** Not all sprites require complex movement. Some may have simple idle animations and primarily interact through contact damage or being pushable, as exemplified by the Sea Urchin.
* **Timed Attack and Stun States:** Sprites can have distinct attack and stunned states that are governed by timers, allowing for predictable attack patterns and temporary incapacitation (e.g., Poltergeist's `Poltergeist_Attack` and `Poltergeist_Stunned` states). * **Timed Attack and Stun States:** Sprites can have distinct attack and stunned states that are governed by timers, allowing for predictable attack patterns and temporary incapacitation (e.g., Poltergeist's `Poltergeist_Attack` and `Poltergeist_Stunned` states).
* **Conditional Invulnerability:** Invulnerability can be dynamic, changing based on the sprite's state. For example, a sprite might be impervious to certain attacks only when in a specific state, or its `SprDefl` flags might be manipulated to reflect temporary invulnerability (as suggested by the Poltergeist's properties). * **Conditional Invulnerability:** Invulnerability can be dynamic, changing based on the sprite's state. For example, a sprite might be impervious to certain attacks only when in a specific state, or its `SprDefl` flags might be manipulated to reflect temporary invulnerability (as suggested by the Poltergeist's properties).
* **Direct SRAM Interaction:** Implement mechanics that directly interact with Link's inventory or status in SRAM (e.g., stealing items, modifying rupee count). Remember to explicitly manage processor status flags (`REP`/`SEP`) when performing mixed 8-bit/16-bit operations on SRAM addresses to prevent unexpected behavior or crashes. * **Direct SRAM Interaction:** Implement mechanics that directly interact with Link's inventory or status in SRAM (e.g., stealing items, modifying rupee count). Remember to explicitly manage processor status flags (`REP`/`SEP`) when performing mixed 8-bit/16-bit operations on SRAM addresses to prevent unexpected behavior or crashes.
### 8.7. Subtype-based Behavior and Dynamic Transformations
Leverage `SprSubtype` to create diverse enemy variations from a single sprite ID and enable dynamic transformations based on in-game conditions.
* **Multiple Variations per `!SPRID`**: A single `!SPRID` can represent several distinct enemy types (e.g., Ice Keese, Fire Keese, Vampire Bat) by assigning unique `SprSubtype` values. This allows for efficient reuse of sprite slots and base logic while providing varied challenges.
* **Dynamic Environmental Transformation**: Sprites can change their `SprSubtype` (and thus their behavior/appearance) in response to environmental factors. For example, an Octorok might transform from a land-based to a water-based variant upon entering water tiles. This adds depth and realism to enemy interactions with the environment.
### 8.8. Vanilla Sprite Overrides and Conditional Logic
When modifying existing enemies or creating new boss sequences, it's common to override vanilla sprite behavior. This allows for custom implementations while retaining the original sprite ID.
* **Hooking Entry Points**: Use `pushpc`/`pullpc` and `org` directives to redirect execution from vanilla sprite routines to your custom code. This is crucial for replacing or extending existing behaviors.
* **Contextual Checks**: Implement checks (e.g., using custom flags or game state variables) within your custom routines to determine whether to execute your custom logic or fall back to the original vanilla behavior. This provides flexibility and allows for conditional modifications.

View File

@@ -0,0 +1,122 @@
# Business Scrub
## Overview
The Business Scrub is a custom enemy sprite that likely overrides a vanilla sprite ID. It is characterized by its low health and harmless contact damage, suggesting its primary threat comes from projectiles or other interactions defined within its main logic.
## Sprite Properties
* **`!SPRID`**: `$00` (Vanilla sprite ID, likely overridden)
* **`!NbrTiles`**: `$02`
* **`!Health`**: `$01`
* **`!Damage`**: `$00` (Harmless contact)
* **`!Harmless`**: `$00`
* **`!Hitbox`**: `$08`
* **`!ImperviousAll`**: `$00`
* **`!Statue`**: `$00`
* **`!Prize`**: `$00`
* **`!Boss`**: `$00`
* **Collision Properties**: All collision-related properties (`!Defl`, `!SprColl`, etc.) are set to `$00`, indicating that direct contact damage and knockback are not handled by these properties. Interaction and damage are likely managed within the sprite's main logic.
## Main Structure (`Sprite_BusinessScrub_Long`)
This routine is the main entry point for the Business Scrub, executed every frame. It sets up bank registers, calls the drawing routine, and then executes the main logic if the sprite is active.
```asm
Sprite_BusinessScrub_Long:
{
PHB : PHK : PLB
JSR Sprite_BusinessScrub_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_BusinessScrub_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_BusinessScrub_Prep`)
This routine runs once when the Business Scrub is spawned. It initializes the sprite's action state to `0` and sets a general-purpose timer (`SprTimerA`) to `120` frames (2 seconds).
```asm
Sprite_BusinessScrub_Prep:
{
PHB : PHK : PLB
%GotoAction(0)
%SetTimerA(120)
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_BusinessScrub_Main`)
The core behavior of the Business Scrub is managed by a state machine using `%SpriteJumpTable`. The current states are:
* **`State_Idle`**: The initial state where the scrub is idle. It plays an animation and checks for player proximity. If Link is within 80 pixels, it transitions to `State_Attacking`.
* **`State_Attacking`**: In this state, the scrub plays an attack animation, moves towards the player, and deals damage on contact. It also checks if it has been hit by the player and transitions to `State_Hurt` if so.
* **`State_Hurt`**: This state handles the sprite being hit, causing it to flash and be knocked back.
```asm
Sprite_BusinessScrub_Main:
{
%SpriteJumpTable(State_Idle, State_Attacking, State_Hurt)
State_Idle:
{
%PlayAnimation(0, 1, 15)
JSL GetDistance8bit_Long : CMP.b #$50 : BCS .player_is_far
%GotoAction(1)
.player_is_far
RTS
}
State_Attacking:
{
%PlayAnimation(2, 3, 8)
%MoveTowardPlayer(12)
%DoDamageToPlayerSameLayerOnContact()
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
%GotoAction(2)
.no_damage
RTS
}
State_Hurt:
{
JSL Sprite_DamageFlash_Long
RTS
}
}
```
## Drawing (`Sprite_BusinessScrub_Draw`)
The drawing routine uses the `%DrawSprite()` macro to render the sprite's graphics based on defined OAM data tables.
```asm
Sprite_BusinessScrub_Draw:
{
%DrawSprite()
.start_index
db $00, $02, $04, $06
.nbr_of_tiles
db 1, 1, 1, 1
.x_offsets
dw -8, 8, -8, 8, -8, 8, -8, 8
.y_offsets
dw -8, -8, -8, -8, -8, -8, -8, -8
.chr
db $C0, $C2, $C4, $C6, $C8, $CA, $CC, $CE
.properties
db $3B, $7B, $3B, $7B, $3B, $7B, $3B, $7B
}
```
## Design Patterns
* **State Machine**: Utilizes a clear state machine (`%SpriteJumpTable`) for managing different behaviors (Idle, Attacking, Hurt).
* **Player Interaction**: Incorporates distance checks (`GetDistance8bit_Long`) to trigger state changes and direct damage on contact (`DoDamageToPlayerSameLayerOnContact`).
* **Damage Handling**: Includes a basic damage reaction (`Sprite_CheckDamageFromPlayer`, `Sprite_DamageFlash_Long`).
* **Vanilla ID Override**: The use of `!SPRID = $00` suggests this sprite is intended to replace or modify the behavior of a vanilla sprite with ID $00.

122
Docs/Sprites/EonScrub.md Normal file
View File

@@ -0,0 +1,122 @@
# Eon Scrub
## Overview
The Eon Scrub is a custom enemy sprite, likely a variation of the Business Scrub, that overrides a vanilla sprite ID. It shares similar characteristics with the Business Scrub, including low health and harmless contact damage, implying its primary threat comes from projectiles or other interactions defined within its main logic.
## Sprite Properties
* **`!SPRID`**: `$00` (Vanilla sprite ID, likely overridden)
* **`!NbrTiles`**: `$02`
* **`!Health`**: `$01`
* **`!Damage`**: `$00` (Harmless contact)
* **`!Harmless`**: `$00`
* **`!Hitbox`**: `$08`
* **`!ImperviousAll`**: `$00`
* **`!Statue`**: `$00`
* **`!Prize`**: `$00`
* **`!Boss`**: `$00`
* **Collision Properties**: All collision-related properties (`!Defl`, `!SprColl`, etc.) are set to `$00`, indicating that direct contact damage and knockback are not handled by these properties. Interaction and damage are likely managed within the sprite's main logic.
## Main Structure (`Sprite_EonScrub_Long`)
This routine is the main entry point for the Eon Scrub, executed every frame. It sets up bank registers, calls the drawing routine, and then executes the main logic if the sprite is active.
```asm
Sprite_EonScrub_Long:
{
PHB : PHK : PLB
JSR Sprite_EonScrub_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_EonScrub_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_EonScrub_Prep`)
This routine runs once when the Eon Scrub is spawned. It initializes the sprite's action state to `0` and sets a general-purpose timer (`SprTimerA`) to `120` frames (2 seconds).
```asm
Sprite_EonScrub_Prep:
{
PHB : PHK : PLB
%GotoAction(0)
%SetTimerA(120)
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_EonScrub_Main`)
The core behavior of the Eon Scrub is managed by a state machine using `%SpriteJumpTable`. The current states are:
* **`State_Idle`**: The initial state where the scrub is idle. It plays an animation and checks for player proximity. If Link is within 80 pixels, it transitions to `State_Attacking`.
* **`State_Attacking`**: In this state, the scrub plays an attack animation, moves towards the player, and deals damage on contact. It also checks if it has been hit by the player and transitions to `State_Hurt` if so.
* **`State_Hurt`**: This state handles the sprite being hit, causing it to flash and be knocked back.
```asm
Sprite_EonScrub_Main:
{
%SpriteJumpTable(State_Idle, State_Attacking, State_Hurt)
State_Idle:
{
%PlayAnimation(0, 1, 15)
JSL GetDistance8bit_Long : CMP.b #$50 : BCS .player_is_far
%GotoAction(1)
.player_is_far
RTS
}
State_Attacking:
{
%PlayAnimation(2, 3, 8)
%MoveTowardPlayer(12)
%DoDamageToPlayerSameLayerOnContact()
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
%GotoAction(2)
.no_damage
RTS
}
State_Hurt:
{
JSL Sprite_DamageFlash_Long
RTS
}
}
```
## Drawing (`Sprite_EonScrub_Draw`)
The drawing routine uses the `%DrawSprite()` macro to render the sprite's graphics based on defined OAM data tables.
```asm
Sprite_EonScrub_Draw:
{
%DrawSprite()
.start_index
db $00, $02, $04, $06
.nbr_of_tiles
db 1, 1, 1, 1
.x_offsets
dw -8, 8, -8, 8, -8, 8, -8, 8
.y_offsets
dw -8, -8, -8, -8, -8, -8, -8, -8
.chr
db $C0, $C2, $C4, $C6, $C8, $CA, $CC, $CE
.properties
db $3B, $7B, $3B, $7B, $3B, $7B, $3B, $7B
}
```
## Design Patterns
* **State Machine**: Utilizes a clear state machine (`%SpriteJumpTable`) for managing different behaviors (Idle, Attacking, Hurt).
* **Player Interaction**: Incorporates distance checks (`GetDistance8bit_Long`) to trigger state changes and direct damage on contact (`DoDamageToPlayerSameLayerOnContact`).
* **Damage Handling**: Includes a basic damage reaction (`Sprite_CheckDamageFromPlayer`, `Sprite_DamageFlash_Long`).
* **Vanilla ID Override**: The use of `!SPRID = $00` suggests this sprite is intended to replace or modify the behavior of a vanilla sprite with ID $00.

293
Docs/Sprites/Keese.md Normal file
View File

@@ -0,0 +1,293 @@
# 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`.
```asm
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.
```asm
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.
```asm
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`.
```asm
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.
```asm
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.

242
Docs/Sprites/Leever.md Normal file
View File

@@ -0,0 +1,242 @@
# Leever
## Overview
The Leever sprite is a custom implementation that overrides the vanilla Leever behavior (`Sprite_71_Leever`). It features distinct states for being underground, emerging, attacking, and digging back down, with randomized timers controlling its transitions.
## Vanilla Override
This custom Leever implementation hooks into the vanilla sprite ID $71. It uses a custom flag at `$0FFF` to determine whether to execute its custom logic (`Sprite_Leever_Long`) or fall back to the original vanilla Leever behavior (`Sprite_71_Leever`).
```asm
pushpc
Sprite_71_Leever = $06CBA2
org $069365 : dw Sprite_71_Leever_Alt
Sprite_71_Leever_Alt:
{
LDA.w $0FFF : BEQ +
JSL Sprite_Leever_Long
JMP ++
+
JSR Sprite_71_Leever
++
RTS
}
assert pc() <= $06A5C0
pullpc
```
## Sprite Properties
Explicit sprite properties (`!SPRID`, `!Health`, etc.) are not defined within this file, suggesting it either inherits vanilla properties for sprite ID $71 or these are defined in a separate configuration file.
## Main Structure (`Sprite_Leever_Long`)
This routine is the main entry point for the custom Leever logic, executed every frame. It handles bank setup, conditional drawing (skipping drawing when underground), and dispatches to the main logic if the sprite is active.
```asm
Sprite_Leever_Long:
{
PHB : PHK : PLB
LDA.w SprAction, X : BEQ +
JSR Sprite_Leever_Draw
+
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Leever_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Movement Routine (`Sprite_Leever_Move`)
A shared routine for handling the Leever's movement, including applying speed towards the player, moving the sprite, and bouncing off tiles.
```asm
Sprite_Leever_Move:
{
JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_Move
JSL Sprite_BounceFromTileCollision
RTS
}
```
## Main Logic & State Machine (`Sprite_Leever_Main`)
The Leever's core behavior is managed by a state machine with four distinct states:
* **`Leever_Underground`**: The Leever moves underground. After a timer (`SprTimerA`) expires, it transitions to `Leever_Emerge`.
* **`Leever_Emerge`**: The Leever plays a backwards animation as it emerges. After a randomized timer, it transitions to `Leever_Attack`.
* **`Leever_Attack`**: The Leever plays an attack animation, checks for damage to/from Link, and moves. After a timer, it transitions to `Leever_Dig`.
* **`Leever_Dig`**: The Leever plays an animation as it digs back into the ground. After a randomized timer, it transitions back to `Leever_Underground`.
```asm
Sprite_Leever_Main:
{
JSL Sprite_DamageFlash_Long
LDA.w SprAction, X
JSL JumpTableLocal
dw Leever_Underground
dw Leever_Emerge
dw Leever_Attack
dw Leever_Dig
Leever_Underground:
{
LDA.w SprTimerA, X : BNE +
LDA.b #$40 : STA.w SprTimerA, X
INC.w SprAction, X
+
LDA.b #$10
JSR Sprite_Leever_Move
RTS
}
Leever_Emerge:
{
%PlayAnimBackwards(3, 2, 10)
LDA.w SprTimerA, X : BNE +
JSL GetRandomInt
AND.b #$3F
ADC.b #$A0
STA.w $0DF0,X
INC.w SprAction, X
STZ.w SprXSpeed, X : STZ.w SprYSpeed, X
+
RTS
}
Leever_Attack:
{
%PlayAnimation(0, 1, 10)
LDA.w SprTimerA, X : BNE +
LDA.b #$7F : STA.w SprTimerA, X
INC.w SprAction, X
+
PHX
JSL Sprite_CheckIfRecoiling
JSL Sprite_CheckDamageToPlayerSameLayer
JSL Sprite_CheckDamageFromPlayer
PLX
LDA.b #$0C
JSR Sprite_Leever_Move
RTS
}
Leever_Dig:
{
%PlayAnimation(2, 3, 10)
LDA.w SprTimerA, X : BNE +
JSL GetRandomInt
AND.b #$1F
ADC.b #$40
STA.w $0DF0,X
STZ.w SprAction, X
+
LDA.b #$08
JSR Sprite_Leever_Move
RTS
}
}
```
## Drawing (`Sprite_Leever_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_Leever_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA $0DC0, X : CLC : ADC $0D90, X : TAY;Animation Frame
LDA .start_index, Y : STA $06
LDA.w SprFlash, X : 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
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, $02, $03
.nbr_of_tiles
db 0, 0, 0, 0
.x_offsets
dw 0
dw 0
dw 0
dw 0
.y_offsets
dw 0
dw 0
dw 0
dw 0
.chr
db $C4
db $C6
db $C2
db $C0
.properties
db $33
db $33
db $33
db $33
.sizes
db $02
db $02
db $02
db $02
}
```
## Design Patterns
* **Vanilla Override**: Explicitly overrides a vanilla sprite's behavior, demonstrating how to replace existing game logic with custom implementations.
* **Conditional Logic**: Uses a custom flag (`$0FFF`) to dynamically switch between vanilla and custom behaviors, offering flexibility in game design.
* **Emerging/Digging State Machine**: Implements a robust state machine to manage the Leever's characteristic emerging from and digging back into the ground, with randomized timers for unpredictable transitions.
* **Animation Control**: Utilizes `%PlayAnimBackwards` for specific animation effects, such as the Leever emerging from the ground.
* **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.

547
Docs/Sprites/Octorok.md Normal file
View File

@@ -0,0 +1,547 @@
# Octorok
## Overview
The Octorok sprite (`!SPRID = $08`) is a complex enemy implementation that supports both land-based and water-based variations. It features dynamic behavior, including transformation between forms, distinct movement patterns, and projectile attacks.
## Sprite Properties
* **`!SPRID`**: `$08` (Vanilla sprite ID for Octorok)
* **`!NbrTiles`**: `05`
* **`!Harmless`**: `00`
* **`!HVelocity`**: `00`
* **`!Health`**: `00` (Health is likely set dynamically or is vanilla)
* **`!Damage`**: `00` (Damage is likely from projectiles)
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00` (Shadow is drawn conditionally in `_Long`)
* **`!Palette`**: `00`
* **`!Hitbox`**: `00`
* **`!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_Octorok_Long`)
This routine acts as a dispatcher, determining whether to execute Land Octorok or Water Octorok logic based on `SprSubtype`.
```asm
Sprite_Octorok_Long:
{
PHB : PHK : PLB
JSR Sprite_Octorok_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
LDA.w SprSubtype, X : BEQ +
JSL Sprite_DrawWaterRipple
JSR Sprite_WaterOctorok_Main
JMP ++
+
JSL Sprite_DrawShadow
JSR Sprite_Octorok_Main
++
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Octorok_Prep`)
This routine is currently empty, indicating that initial setup is minimal or handled by vanilla routines.
## Land Octorok Main Logic (`Sprite_Octorok_Main`)
This routine handles the behavior of a land-based Octorok, including movement and potential transformation into a Water Octorok.
* **Movement**: Calls `Sprite_Octorok_Move` for general movement.
* **Transformation**: Checks the tile type the Octorok is on. If it's a water tile, the Octorok transforms into a Water Octorok by setting `SprSubtype, X` to `01`.
* **Directional States**: Uses a jump table to manage animations for moving in different directions (Down, Up, Left, Right).
```asm
Sprite_Octorok_Main:
{
JSR Sprite_Octorok_Move
; TILETYPE 08
LDA.l $7FF9C2,X : CMP.b #$08 : BEQ .water_tile
; TILETYPE 09
CMP.b #$09 : BNE .not_water_tile
.water_tile
LDA.b #$01 : STA.w SprSubtype, X
STZ.w SprAction, X
STZ.w SprMiscG, X
RTS
.not_water_tile
LDA.w SprAction, X
JSL JumpTableLocal
dw Octorok_MoveDown
dw Octorok_MoveUp
dw Octorok_MoveLeft
dw Octorok_MoveRight
Octorok_MoveDown:
{
%PlayAnimation(0,1,10)
RTS
}
Octorok_MoveUp:
{
%StartOnFrame(2)
%PlayAnimation(2,3,10)
RTS
}
Octorok_MoveLeft:
{
%StartOnFrame(4)
%PlayAnimation(4,5,10)
RTS
}
Octorok_MoveRight:
{
%StartOnFrame(6)
%PlayAnimation(6,7,10)
RTS
}
}
```
## Octorok Movement (`Sprite_Octorok_Move`)
This shared routine handles the Octorok's general movement, damage reactions, and tile collision.
* **Damage & Collision**: Handles damage flash (`Sprite_DamageFlash_Long`), movement (`Sprite_Move`), and checks for damage to/from Link.
* **Directional Logic**: Sets the sprite's action based on its direction (`SprMiscC, X`).
* **Tile Collision**: Detects tile collisions and changes the Octorok's direction accordingly.
* **Barrage Logic**: Contains logic related to a potential projectile barrage (`octorok_used_barrage`).
```asm
Sprite_Octorok_Move:
{
JSL Sprite_DamageFlash_Long
JSL Sprite_Move
JSL Sprite_CheckDamageFromPlayer
JSL Sprite_CheckDamageToPlayer
; Set the SprAction based on the direction
LDA.w SprMiscC, X : AND.b #$03 : TAY
LDA.w .direction, Y : STA.w SprAction, X
LDA.w SprMiscF, X : AND.b #$01 : BNE .octorok_used_barrage
LDA.w SprMiscC, X : AND.b #$02 : ASL A : STA.b $00
INC.w SprDelay, X
LDA.w SprDelay, X
LSR A
LSR A
LSR A
AND.b #$03
ORA.b $00
STA.w SprGfx, X
LDA.w SprTimerA, X : BNE .wait
INC.w SprMiscF,X
LDY.w SprType,X
LDA.w .timer-8,Y : STA.w SprTimerA,X
RTS
.wait
LDY.w SprMiscC, X
LDA.w .speed_x, Y : STA.w SprXSpeed, X
LDA.w .speed_y, Y : STA.w SprYSpeed, X
JSL Sprite_CheckTileCollision
LDA.w $0E70, X : BEQ .no_collision
LDA.w SprMiscC,X : EOR.b #$01 : STA.w SprMiscC,X
BRA .exit
.no_collision
RTS
.octorok_used_barrage
STZ.w SprXSpeed, X : STZ.w SprYSpeed,X
LDA.w SprTimerA, X : BNE Octorock_ShootEmUp
INC.w SprMiscF, X
LDA.w SprMiscC, X
PHA
JSL GetRandomInt : AND.b #$3F : ADC.b #$30 : STA.w SprTimerA, X
AND.b #$03 : STA.w SprMiscC, X
PLA
CMP.w SprMiscC, X : BEQ .exit
EOR.w SprMiscC, X : BNE .exit
LDA.b #$08 : STA.w SprTimerB,X
.exit
RTS
.direction
db 3, 2, 0, 1
.speed_x
db 24, -24, 0, 0
.speed_y
db 0, 0, 24, -24
.timer
db 60, 128, 160, 128
}
```
## Octorok Projectile Logic (`Octorock_ShootEmUp`)
This routine determines the Octorok's shooting behavior, allowing for both single-shot and four-way attacks.
```asm
Octorock_ShootEmUp:
{
; Use SprMiscD as a flag to shoot 4 ways for awhile before going back to single shot
LDA.w SprMiscD, X : BEQ .continue
LDA.w SprTimerD, X : BNE .four_ways
LDA.b #$01 : STA.w SprMiscD, X
.continue
JSL GetRandomInt : AND.b #$1F : BNE .single_shot
.four_ways
LDA.b #$01 : STA.w SprMiscD, X
LDA.b #$20 : STA.w SprTimerD, X
JSR Octorok_Shoot4Ways
RTS
.single_shot
JSR Octorok_ShootSingle
RTS
}
```
## Water Octorok Main Logic (`Sprite_WaterOctorok_Main`)
This routine governs the behavior of a water-based Octorok, including its attack patterns and states.
* **Attack**: Calls `Sprite_WaterOctorok_Attack`.
* **Facing Directions**: Uses a jump table to manage animations for facing different directions (Down, Up, Left, Right) and a hidden state.
```asm
Sprite_WaterOctorok_Main:
{
JSR Sprite_WaterOctorok_Attack
LDA.w SprAction, X
JSL JumpTableLocal
dw WaterOctorok_FaceDown
dw WaterOctorok_FaceUp
dw WaterOctorok_FaceLeft
dw WaterOctorok_FaceRight
dw WaterOctorok_FaceHidden
WaterOctorok_FaceDown:
{
%PlayAnimation(0,1,10)
RTS
}
WaterOctorok_FaceUp:
{
%StartOnFrame(2)
%PlayAnimation(2,3,10)
RTS
}
WaterOctorok_FaceLeft:
{
%StartOnFrame(4)
%PlayAnimation(4,5,10)
RTS
}
WaterOctorok_FaceRight:
{
%StartOnFrame(6)
%PlayAnimation(6,7,10)
RTS
}
WaterOctorok_FaceHidden:
{
%StartOnFrame(8)
%PlayAnimation(8,8,10)
RTS
}
}
```
## Water Octorok Attack Logic (`Sprite_WaterOctorok_Attack`)
This routine manages the Water Octorok's attack states, including hiding, emerging, attacking, and re-hiding.
* **States**: Uses `SprMiscG, X` as a state machine for `WaterOctorok_Hidden`, `WaterOctorok_PoppingUp`, `WaterOctorok_Attacking`, and `WaterOctorok_Hiding`.
* **`WaterOctorok_Hidden`**: Remains hidden until Link is within a certain distance, then transitions to `WaterOctorok_PoppingUp`.
* **`WaterOctorok_PoppingUp`**: Emerges from the water, faces Link, and then transitions to `WaterOctorok_Attacking`.
* **`WaterOctorok_Attacking`**: Shoots a single projectile (`Octorok_ShootSingle`) after a timer, then transitions to `WaterOctorok_Hiding`.
* **`WaterOctorok_Hiding`**: Hides back in the water and transitions to `WaterOctorok_Hidden`.
```asm
Sprite_WaterOctorok_Attack:
{
JSL Sprite_DamageFlash_Long
JSL Sprite_CheckDamageToPlayer
LDA.w SprMiscG, X
JSL JumpTableLocal
dw WaterOctorok_Hidden
dw WaterOctorok_PoppingUp
dw WaterOctorok_Attacking
dw WaterOctorok_Hiding
WaterOctorok_Hidden:
{
LDA.w SprTimerA, X : BEQ +
RTS
+
JSL GetDistance8bit_Long
CMP.b #$40 : BCC .not_close_enough ; LD < 64
INC.w SprMiscG, X
%SetTimerA($10)
.not_close_enough
RTS
}
WaterOctorok_PoppingUp:
{
JSL Sprite_CheckDamageFromPlayer
LDA.w SprTimerA, X : BNE +
INC.w SprMiscG, X
%SetTimerA($20)
JSL Sprite_DirectionToFacePlayer
; LDA.w SprMiscC, X : AND.b #$03 : TAY
; LDA.w Sprite_Octorok_Move_direction, Y : STA.w SprAction, X
+
RTS
}
WaterOctorok_Attacking:
{
JSL Sprite_CheckDamageFromPlayer
LDA.w SprTimerA, X : BNE +
INC.w SprMiscG, X
%SetTimerA($10)
RTS
+
JSR Octorok_ShootSingle
RTS
}
WaterOctorok_Hiding:
{
LDA.w SprTimerA, X : BNE +
LDA.b #$04 : STA.w SprAction, X
STZ.w SprMiscG, X
%SetTimerA($40)
+
RTS
}
}
```
## Projectile Spawning (`Octorok_ShootSingle`, `Octorok_Shoot4Ways`, `Octorok_SpawnRock`)
These routines handle the spawning and animation of Octorok projectiles.
* **`Octorok_ShootSingle`**: Manages the animation and timing for shooting a single rock projectile.
* **`Octorok_Shoot4Ways`**: Manages the animation, timing, and direction changes for shooting rock projectiles in four cardinal directions.
* **`Octorok_SpawnRock`**: Spawns a rock projectile (sprite ID `$0C`) with specific initial offsets and speeds based on the Octorok's current direction.
```asm
Octorok_ShootSingle:
{
LDA.w SprTimerA, X : CMP.b #$1C : BNE .bide_time
PHA
JSR Octorok_SpawnRock
PLA
.bide_time
LSR #3
TAY
LDA.w .mouth_anim_step, Y : STA.w SprMiscB, X
RTS
.mouth_anim_step
db $00, $02, $02, $02
db $01, $01, $01, $00
db $00, $00, $00, $00
db $02, $02, $02, $02
db $02, $01, $01, $00
}
Octorok_Shoot4Ways:
{
LDA.w SprTimerA, X
PHA
CMP.b #$80 : BCS .animate
AND.b #$0F : BNE .delay_turn
PHA
LDY.w SprMiscC, X
LDA.w .next_direction, Y : STA.w SprMiscC, X
PLA
.delay_turn
CMP.b #$08 : BNE .animate
JSR Octorok_SpawnRock
.animate
PLA
LSR #4
TAY
LDA.w .mouth_anim_step, Y : STA.w SprMiscB, X
RTS
.next_direction
db $02, $03, $01, $00
.mouth_anim_step
db $02, $02, $02, $02
db $02, $02, $02, $02
db $01, $00
}
Octorok_SpawnRock:
{
LDA.b #$07 : JSL SpriteSFX_QueueSFX2WithPan
LDA.b #$0C : JSL Sprite_SpawnDynamically : BMI .fired_a_blank
PHX
LDA.w SprMiscC,X
TAX
LDA.b $00 : CLC : ADC.w .offset_x_low,X : STA.w SprX,Y
LDA.b $01 : ADC.w .offset_x_high,X : STA.w SprXH,Y
LDA.b $02 : CLC : ADC.w .offset_y_low,X : STA.w SprY,Y
LDA.b $03 : ADC.w .offset_y_high,X : STA.w SprYH,Y
LDA.w SprMiscC,Y
TAX
LDA.w .rock_speed_x,X : STA.w SprXSpeed,Y
LDA.w .rock_speed_y,X : STA.w SprYSpeed,Y
PLX
.fired_a_blank
RTS
.offset_x_low
db 12, -12, 0, 0
.offset_x_high
db 0, -1, 0, 0
.offset_y_low
db 4, 4, 12, -12
.offset_y_high
db 0, 0, 0, -1
.rock_speed_x
db 44, -44, 0, 0
.rock_speed_y
db 0, 0, 44, -44
}
```
## Drawing (`Sprite_Octorok_Draw`)
The drawing routine handles OAM allocation, animation, and palette adjustments. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_Octorok_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, X : TAY ;Animation Frame
LDA .start_index, Y : STA $06
LDA.w SprFlash : 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 : STA ($90), Y
AND.w #$0100 : STA $0E
INY
LDA $02 : 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
INY
LDA .chr, X : STA ($90), Y
INY
LDA .properties, X : ORA $08 : STA ($90), Y
PHY
TYA : LSR #2 : TAY
LDA.b #$02 : ORA $0F : STA ($92), Y ; store size in oam buffer
PLY : INY
PLX : DEX : BPL .nextTile
PLX
RTS
; =========================================================
.start_index
db $00, $01, $02, $03, $04, $05, $06, $07, $08
.nbr_of_tiles
db 0, 0, 0, 0, 0, 0, 0, 0, 0
.chr
db $80
db $80
db $82
db $82
db $A0
db $A2
db $A0
db $A2
db $AA ; Water Octorok
.properties
db $0D
db $4D
db $0D
db $4D
db $0D
db $0D
db $4D
db $4D
db $3D ; Water Octorok
}
```
## Design Patterns
* **Subtype-based Behavior**: The Octorok utilizes `SprSubtype` to implement distinct behaviors for Land and Water Octoroks, including different main logic routines and conditional drawing (shadow vs. water ripple).
* **Dynamic Transformation**: A Land Octorok can dynamically transform into a Water Octorok if it moves onto a water tile, showcasing a unique environmental interaction.
* **Complex State Machines**: Both Land and Water Octoroks employ intricate state machines to manage their movement, attack patterns, and emerging/hiding behaviors, making them engaging enemies.
* **Projectile Attacks**: The Octorok can perform both single-shot and four-way projectile attacks, adding variety to its offensive capabilities.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering and positioning.