diff --git a/Docs/Guides/SpriteCreationGuide.md b/Docs/Guides/SpriteCreationGuide.md index e536b9a..27443d8 100644 --- a/Docs/Guides/SpriteCreationGuide.md +++ b/Docs/Guides/SpriteCreationGuide.md @@ -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. +**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 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. * **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 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. * **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. * **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). * **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. \ No newline at end of file +* **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. \ No newline at end of file diff --git a/Docs/Sprites/BusinessScrub.md b/Docs/Sprites/BusinessScrub.md new file mode 100644 index 0000000..dc01564 --- /dev/null +++ b/Docs/Sprites/BusinessScrub.md @@ -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. diff --git a/Docs/Sprites/EonScrub.md b/Docs/Sprites/EonScrub.md new file mode 100644 index 0000000..1c2b771 --- /dev/null +++ b/Docs/Sprites/EonScrub.md @@ -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. diff --git a/Docs/Sprites/Keese.md b/Docs/Sprites/Keese.md new file mode 100644 index 0000000..2a63b69 --- /dev/null +++ b/Docs/Sprites/Keese.md @@ -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. diff --git a/Docs/Sprites/Leever.md b/Docs/Sprites/Leever.md new file mode 100644 index 0000000..9863006 --- /dev/null +++ b/Docs/Sprites/Leever.md @@ -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. diff --git a/Docs/Sprites/Octorok.md b/Docs/Sprites/Octorok.md new file mode 100644 index 0000000..5a27731 --- /dev/null +++ b/Docs/Sprites/Octorok.md @@ -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.