From 1cc7d84782b11efb65e4f581c51f4c6d4f8be039 Mon Sep 17 00:00:00 2001 From: scawful Date: Thu, 2 Oct 2025 23:55:31 -0400 Subject: [PATCH] Add detailed sprite analysis for Puffstool, Sea Urchin, Thunder Ghost and more - Introduced comprehensive documentation for the Puffstool sprite, covering properties, core routines, and key behaviors. - Added analysis for the Sea Urchin sprite, detailing its initialization, state management, and drawing routines. - Included a thorough examination of the Thunder Ghost sprite, highlighting its dynamic health, lightning attack mechanics, and movement patterns. --- Docs/Guides/SpriteCreationGuide.md | 45 ++- Docs/Sprites/AntiKirby.md | 254 ++++++++++++++++ Docs/Sprites/Booki.md | 290 ++++++++++++++++++ Docs/Sprites/Darknut.md | 328 ++++++++++++++++++++ Docs/Sprites/Goriya.md | 469 +++++++++++++++++++++++++++++ Docs/Sprites/HelmetChuchu.md | 433 ++++++++++++++++++++++++++ Docs/Sprites/PolsVoice.md | 221 ++++++++++++++ Docs/Sprites/Poltergeist.md | 233 ++++++++++++++ Docs/Sprites/Puffstool.md | 287 ++++++++++++++++++ Docs/Sprites/SeaUrchin.md | 221 ++++++++++++++ Docs/Sprites/ThunderGhost.md | 313 +++++++++++++++++++ 11 files changed, 3083 insertions(+), 11 deletions(-) create mode 100644 Docs/Sprites/AntiKirby.md create mode 100644 Docs/Sprites/Booki.md create mode 100644 Docs/Sprites/Darknut.md create mode 100644 Docs/Sprites/Goriya.md create mode 100644 Docs/Sprites/HelmetChuchu.md create mode 100644 Docs/Sprites/PolsVoice.md create mode 100644 Docs/Sprites/Poltergeist.md create mode 100644 Docs/Sprites/Puffstool.md create mode 100644 Docs/Sprites/SeaUrchin.md create mode 100644 Docs/Sprites/ThunderGhost.md diff --git a/Docs/Guides/SpriteCreationGuide.md b/Docs/Guides/SpriteCreationGuide.md index 6c72904..e536b9a 100644 --- a/Docs/Guides/SpriteCreationGuide.md +++ b/Docs/Guides/SpriteCreationGuide.md @@ -21,7 +21,7 @@ At the top of your new sprite file, define its core properties using the provide ```asm ; Properties for MyNewEnemy !SPRID = $XX ; CHOOSE AN UNUSED SPRITE ID! -!NbrTiles = 02 ; Number of 8x8 tiles used in the largest frame +!NbrTiles = 02 ; Number of 8x8 tiles used in the largest frame (e.g., Darknut uses 03 for its multi-tile body) !Health = 10 ; Health points !Damage = 04 ; Damage dealt to Link on contact (04 = half a heart) !Harmless = 00 ; 00 = Harmful, 01 = Harmless @@ -33,8 +33,9 @@ At the top of your new sprite file, define its core properties using the provide ; --- Design Considerations --- ; * **Multi-purpose Sprite IDs:** A single `!SPRID` can be used for multiple distinct behaviors (e.g., Dark Link and Ganon) through the use of `SprSubtype`. This is a powerful technique for reusing sprite slots and creating multi-phase bosses or variations of enemies. -; * **Damage Handling for Bosses:** For boss sprites, `!Damage = 0` is acceptable if damage is applied through other means, such as spawned projectiles or direct contact logic within the main routine. -; * **Custom Boss Logic:** Setting `!Boss = 00` for a boss sprite indicates that custom boss logic is being used, rather than relying on vanilla boss flags. This is important for understanding how boss-specific behaviors are implemented. +* **Damage Handling for Bosses:** For boss sprites, `!Damage = 0` is acceptable if damage is applied through other means, such as spawned projectiles or direct contact logic within the main routine. +* **Fixed vs. Dynamic Health:** While many sprites (like Booki) have dynamic health based on Link's sword level, some sprites (like Pols Voice) may have a fixed `!Health` value, simplifying their damage model. +* **Custom Boss Logic:** Setting `!Boss = 00` for a boss sprite indicates that custom boss logic is being used, rather than relying on vanilla boss flags. This is important for understanding how boss-specific behaviors are implemented. ; This macro MUST be called after the properties %Set_Sprite_Properties(Sprite_MyNewEnemy_Prep, Sprite_MyNewEnemy_Long) @@ -42,7 +43,7 @@ At the top of your new sprite file, define its core properties using the provide ## 3. Main Structure (`_Long` routine) -This is the main entry point for your sprite, called by the game engine every frame. Its primary job is to call the drawing and logic routines. +This is the main entry point for your sprite, called by the game engine every frame. Its primary job is to call the drawing and logic routines. Sometimes, shadow drawing might be conditional based on the sprite's current action or state, as seen in the Thunder Ghost sprite (`Sprites/Enemies/thunder_ghost.asm`). ```asm Sprite_MyNewEnemy_Long: @@ -117,12 +118,12 @@ Sprite_MyNewEnemy_Main: ; } ; ; --- Code Readability Note --- -; For improved readability and maintainability, always prefer using named constants for hardcoded values (e.g., timers, speeds, health, magic numbers) and named labels for `JSL` calls to project-specific functions instead of direct numerical addresses. +; For improved readability and maintainability, always prefer using named constants for hardcoded values (e.g., timers, speeds, health, magic numbers) and named labels for `JSL` calls to project-specific functions instead of direct numerical addresses. Additionally, be mindful of the Processor Status Register (P) flags (M and X) and use `REP #$20`/`SEP #$20` to explicitly set the accumulator/index register size when necessary, especially when interacting with memory or calling routines that expect a specific state. ``` ## 6. Drawing (`_Draw` 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. +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. ```asm Sprite_MyNewEnemy_Draw: @@ -148,7 +149,7 @@ Sprite_MyNewEnemy_Draw: ; } ; ; --- Code Readability Note --- -; For improved readability and maintainability, always prefer using named constants for hardcoded values (e.g., OAM properties, tile numbers) and named labels for `JSL` calls to project-specific functions instead of direct numerical addresses. +; For improved readability and maintainability, always prefer using named constants for hardcoded values (e.g., OAM properties, tile numbers) and named labels for `JSL` calls to project-specific functions instead of direct numerical addresses. Additionally, be mindful of the Processor Status Register (P) flags (M and X) and use `REP #$20`/`SEP #$20` to explicitly set the accumulator/index register size when necessary, especially when interacting with memory or calling routines that expect a specific state. ``` ## 7. Final Integration @@ -161,14 +162,14 @@ The %Set_Sprite_Properties() macro you added in Step 2 handles the final integra For complex bosses or entities, you can break them down into a main parent sprite and multiple child sprites. * **Parent Sprite Responsibilities:** - * Spawns and manages its child sprites (e.g., Kydreeok spawning `kydreeok_head` instances). + * Spawns and manages its child sprites (e.g., Kydreeok spawning `kydreeok_head` instances, Darknut spawning probes, Goriya spawning its boomerang, Helmet Chuchu detaching its helmet/mask, Thunder Ghost spawning lightning attacks with directional logic, or Puffstool transforming into a bomb). * Monitors the state/health of its child sprites to determine its own phases, actions, or defeat conditions. * Often handles overall movement, phase transitions, and global effects. * Uses global variables (e.g., `Offspring1_Id`, `Offspring2_Id`) to store the sprite IDs of its children for easy referencing. * **Child Sprite Responsibilities:** * Handles its own independent logic, movement, and attacks. * May be positioned relative to the parent sprite. - * Can use `SprSubtype` to differentiate between multiple instances of the same child sprite ID (e.g., left head vs. right head). + * Can use `SprSubtype` to differentiate between multiple instances of the same child sprite ID (e.g., left head vs. right head, Goriya vs. its Boomerang, or Helmet Chuchu's detached helmet/mask). * **Shared Sprite IDs for Variations:** A single `!SPRID` can also be used for different enemy variations (e.g., Keese, Fire Keese, Ice Keese, Vampire Bat) by assigning unique `SprSubtype` values. This is an efficient way to reuse sprite slots and base logic. ### 8.2. Multi-Phase Bosses and Dynamic Health Management @@ -187,7 +188,7 @@ Boss fights can be made more engaging by implementing multiple phases and dynami ### 8.3. Code Reusability and Modularity When designing multiple sprites, especially bosses, look for opportunities to reuse code and create modular components. -* **Shared Logic:** If multiple sprites perform similar actions (e.g., spawning stalfos, specific movement patterns), consider creating common functions or macros that can be called by different sprites. This reduces code duplication and improves maintainability. +* **Shared Logic:** If multiple sprites perform similar actions (e.g., spawning stalfos, specific movement patterns, or tile collision handling like `Goriya_HandleTileCollision` used by Darknut), consider creating common functions or macros that can be called by different sprites. This reduces code duplication and improves maintainability. * **Refactoring:** Regularly refactor duplicated code into reusable components. This makes it easier to apply changes consistently across related sprites. ### 8.4. Configurability and Avoiding Hardcoded Values @@ -199,10 +200,32 @@ To improve sprite reusability and ease of placement in different maps, avoid har * Relative positioning or distance checks to Link. * **Timers, Speeds, Offsets:** Always prefer using named constants (`!CONSTANT_NAME = value`) for numerical values that control behavior (timers, speeds, offsets, health thresholds). This significantly improves readability and makes it easier to adjust parameters without searching through code. * **Function Calls:** Use named labels for `JSL` calls to project-specific functions instead of direct numerical addresses. +* **Conditional Drawing/Flipping:** When drawing, use sprite-specific variables (e.g., `SprMiscC` in the Booki sprite) to store directional information and conditionally adjust OAM offsets or flip bits to achieve horizontal or vertical flipping without hardcoding. +* **State Machine Implementation:** For complex AI, utilize `JumpTableLocal` with `SprAction` to create robust state machines, as seen in the Booki sprite's `_Main` routine. +* **Randomness:** Incorporate `JSL GetRandomInt` to introduce variability into sprite behaviors, such as movement patterns or state transitions, making enemies less predictable. ### 8.5. Overriding Vanilla Behavior When creating new boss sequences or modifying existing enemies, it's common to override vanilla sprite behavior. * **Hooking Entry Points:** Identify the entry points of vanilla sprites (e.g., `Sprite_Blind_Long`, `Sprite_Blind_Prep`) and use `org` directives to redirect execution to your custom routines. * **Contextual Checks:** Within your custom routines, perform checks (e.g., `LDA.l $7EF3CC : CMP.b #$06`) to determine if the vanilla behavior should be executed or if your custom logic should take over. -* **SRAM Flags:** Utilize SRAM flags (`$7EF3XX`) to track quest progression or conditions that dictate whether vanilla or custom behavior is active. \ No newline at end of file +* **SRAM Flags:** Utilize SRAM flags (`$7EF3XX`) to track quest progression or conditions that dictate whether vanilla or custom behavior is active. + +### 8.6. Dynamic Enemy Behavior +For more engaging and adaptive enemies, consider implementing dynamic behaviors: + +* **Dynamic Difficulty Scaling:** Adjust enemy properties (health, damage, prize drops) based on player progression (e.g., Link's sword level, number of collected items). This can be done in the `_Prep` routine by indexing into data tables. For example, the Booki sprite (`Sprites/Enemies/booki.asm`) sets its health based on Link's current sword level. +* **Dynamic State Management:** Utilize `SprMisc` variables (e.g., `SprMiscB` for sub-states) and timers (e.g., `SprTimerA` for timed transitions) to create complex and adaptive behaviors. The Booki sprite demonstrates this with its `SlowFloat` and `FloatAway` sub-states and timed transitions. +* **Advanced Interaction/Guard Mechanics:** Implement behaviors like parrying (`Guard_ParrySwordAttacks` in Darknut) or chasing Link along a single axis (`Guard_ChaseLinkOnOneAxis` in Darknut) to create more sophisticated enemy encounters. +* **Random Movement with Collision Response:** Implement enemies that move in random directions and change their behavior upon collision with tiles, as demonstrated by the Goriya sprite's `Goriya_HandleTileCollision` routine. +* **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. +* **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 diff --git a/Docs/Sprites/AntiKirby.md b/Docs/Sprites/AntiKirby.md new file mode 100644 index 0000000..0a32f95 --- /dev/null +++ b/Docs/Sprites/AntiKirby.md @@ -0,0 +1,254 @@ +# Anti Kirby Sprite Analysis + +## 1. Overview +The Anti Kirby sprite (`Sprite_AntiKirby`) is an enemy that exhibits unique behaviors, including a "suck" attack that can steal Link's items (bombs, arrows, rupees, or shield). It has distinct states for walking, sucking, being full (after stealing an item), and being "hatted" (presumably after Link gets his item back or a specific condition is met). + +## 2. Sprite Properties +The sprite properties are defined at the beginning of `Sprites/Enemies/anti_kirby.asm`: + +```asm +!SPRID = Sprite_AntiKirby +!NbrTiles = 02 +!Harmless = 00 +!HVelocity = 00 +!Health = $08 +!Damage = 04 +!DeathAnimation = 00 +!ImperviousAll = 00 +!SmallShadow = 00 +!Shadow = 01 +!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 +``` + +**Key Observations:** +* `!SPRID = Sprite_AntiKirby`: This uses a named constant for the sprite ID, which is good practice. +* `!Health = $08`: Anti Kirby has 8 health points. +* `!Damage = 04`: Deals half a heart of damage to Link. +* `!Hitbox = 03`: A relatively small hitbox. +* `!Shadow = 01`: It draws a shadow. +* `!Boss = 00`: It is not classified as a boss sprite, despite its complex behavior. + +## 3. Main Structure (`Sprite_AntiKirby_Long`) +This routine follows the standard structure for sprites, calling the draw routine, shadow routine, and then the main logic if the sprite is active. + +```asm +Sprite_AntiKirby_Long: +{ + PHB : PHK : PLB + JSR Sprite_AntiKirby_Draw + JSL Sprite_DrawShadow + JSL Sprite_CheckActive : BCC .SpriteIsNotActive + JSR Sprite_AntiKirby_Main + .SpriteIsNotActive + PLB + RTL +} +``` + +## 4. Initialization (`Sprite_AntiKirby_Prep`) +The `_Prep` routine initializes several sprite-specific variables and sets its `SprBump`, `SprHealth`, and `SprPrize` based on Link's current sword level (or a similar progression metric, inferred from `LDA.l Sword : DEC : TAY`). This is an interesting way to scale enemy difficulty. + +```asm +Sprite_AntiKirby_Prep: +{ + PHB : PHK : PLB + STZ.w SprDefl, X + STZ.w SprTileDie, X + STZ.w SprMiscB, X + LDA.l Sword : DEC : TAY + LDA .bump_damage, Y : STA.w SprBump, X + LDA .health, Y : STA.w SprHealth, X + LDA .prize_pack, Y : STA.w SprPrize, X + PLB + RTL + + .bump_damage + db $81, $88, $88, $88 + .health + db 06, 10, 20, 20 + .prize_pack + db 6, 3, 3, 3 +} +``` +**Insight:** The use of `LDA.l Sword : DEC : TAY` to index into `.bump_damage`, `.health`, and `.prize_pack` tables demonstrates a dynamic difficulty scaling mechanism based on player progression (likely sword upgrades). This is a valuable pattern for making enemies adapt to the player's power level. + +## 5. Main Logic & State Machine (`Sprite_AntiKirby_Main`) +The `_Main` routine implements a complex state machine using `JSL JumpTableLocal` and a series of `dw` (define word) entries pointing to different states. + +```asm +Sprite_AntiKirby_Main: +{ + JSL Sprite_IsToRightOfPlayer + TYA : CMP #$01 : BNE .WalkRight + .WalkLeft + LDA.b #$40 : STA.w SprMiscC, X + JMP + + .WalkRight + STZ.w SprMiscC, X + + + + JSL Sprite_DamageFlash_Long + JSL Sprite_CheckIfRecoiling + + LDA.w SprAction, X + JSL JumpTableLocal + + dw AntiKirby_Main ; State 0: Normal movement/attack + dw AntiKirby_Hurt ; State 1: Recoiling from damage + dw AntiKirby_BeginSuck ; State 2: Initiating suck attack + dw AntiKirby_Sucking ; State 3: Actively sucking Link + dw AntiKirby_Full ; State 4: Full after stealing item + dw AntiKirby_Hatted ; State 5: Hatted (after Link gets item back?) + dw AntiKirby_HattedHurt ; State 6: Hatted and hurt + dw AntiKirby_Death ; State 7: Death animation + + ; ... (State implementations below) ... +} +``` + +**State Breakdown:** +* **`AntiKirby_Main` (State 0):** + * Checks health and transitions to `AntiKirby_Full` if health is low (this seems like a bug, should probably be `AntiKirby_Death`). + * Randomly initiates the `AntiKirby_BeginSuck` state. + * Plays walking animation (`%PlayAnimation(0, 2, 10)`). + * Handles damage from player and transitions to `AntiKirby_Hurt`. + * Deals damage to Link on contact (`%DoDamageToPlayerSameLayerOnContact()`). + * Moves toward Link (`%MoveTowardPlayer(8)`) and bounces from tile collisions. +* **`AntiKirby_Hurt` (State 1):** Plays a hurt animation and waits for a timer (`SprTimerA`) to expire before returning to `AntiKirby_Main`. +* **`AntiKirby_BeginSuck` (State 2):** + * Plays a "suck" animation (`%PlayAnimation(4, 5, 10)`). + * Checks for damage from player. + * Checks Link's proximity (`$0E`, `$0F` are likely relative X/Y coordinates to Link). If Link is close enough, it transitions to `AntiKirby_Sucking` and sets up a projectile speed towards Link. +* **`AntiKirby_Sucking` (State 3):** + * Plays a "sucking" animation (`%PlayAnimation(5, 5, 10)`). + * Uses `JSL Sprite_DirectionToFacePlayer` and `JSL DragPlayer` to pull Link towards it if he's close enough. + * If Link is very close, it "consumes" Link, storing Link's position in `SprMiscB` and `SprMiscA`, sets a timer, and transitions to `AntiKirby_Full`. +* **`AntiKirby_Full` (State 4):** + * Plays a "full" animation (`%PlayAnimation(10, 10, 10)`). + * Sets Link's position to the stored `SprMiscA`/`SprMiscB` (effectively "spitting" Link out). + * Transitions to `AntiKirby_Hatted` after a timer. +* **`AntiKirby_Hatted` (State 5):** + * Plays a "hatted" animation (`%PlayAnimation(6, 8, 10)`). + * Moves toward Link, deals damage, and handles damage from player (transitions to `AntiKirby_HattedHurt`). +* **`AntiKirby_HattedHurt` (State 6):** Plays a hurt animation for the "hatted" state and returns to `AntiKirby_Hatted`. +* **`AntiKirby_Death` (State 7):** Sets `SprState` to `$06` (likely a death state) and plays a sound effect. + +**Insight:** The `AntiKirby_Main` state's health check `LDA.w SprHealth, X : CMP.b #$01 : BCS .NotDead : %GotoAction(4)` seems to incorrectly transition to `AntiKirby_Full` (State 4) instead of `AntiKirby_Death` (State 7) when health is 0. This might be a bug or an intentional design choice for a specific game mechanic. + +## 6. Item Stealing Logic (`AntiKirby_StealItem`) +This is a separate routine that is likely called when Anti Kirby successfully "sucks" Link. It checks Link's inventory and steals a random item (bomb, arrow, rupee, or shield). + +```asm +AntiKirby_StealItem: +{ + REP #$20 + ; ... (collision checks) ... + SEP #$20 + LDA.w SprTimerA, X : CMP.b #$2E : BCS .exit ; Timer check + JSL GetRandomInt + AND.b #$03 + INC A + STA.w SprMiscG, X + STA.w SprMiscE, X + + CMP.b #$01 : BNE .dont_steal_bomb + LDA.l $7EF343 : BEQ .dont_steal_anything ; Check bombs + DEC A + STA.l $7EF343 + RTS + .dont_steal_anything + SEP #$20 + STZ.w SprMiscG,X + RTS + .dont_steal_bomb + + CMP.b #$02 : BNE .dont_steal_arrow + LDA.l $7EF377 : BEQ .dont_steal_anything ; Check arrows + DEC A + STA.l $7EF377 + RTS + .dont_steal_arrow + + CMP.b #$03 : BNE .dont_steal_rupee + REP #$20 + LDA.l $7EF360 : BEQ .dont_steal_anything ; Check rupees + DEC A + STA.l $7EF360 + .exit + SEP #$20 + RTS + ; ----------------------------------------------------- + + .dont_steal_rupee + LDA.l $7EF35A : STA.w SprSubtype, X : BEQ .dont_steal_anything ; Check shield + CMP.b #$03 : BEQ .dont_steal_anything + LDA.b #$00 + STA.l $7EF35A + RTS +} +``` +**Key Observations:** +* Uses `REP #$20` and `SEP #$20` to explicitly control the accumulator size (16-bit for address calculations, 8-bit for item counts). This is crucial for correct memory access. +* Randomly selects an item to steal using `JSL GetRandomInt : AND.b #$03 : INC A`. +* Directly modifies SRAM addresses (`$7EF343` for bombs, `$7EF377` for arrows, `$7EF360` for rupees, `$7EF35A` for shield) to decrement item counts or remove the shield. +* The shield stealing logic (`LDA.l $7EF35A : STA.w SprSubtype, X : BEQ .dont_steal_anything : CMP.b #$03 : BEQ .dont_steal_anything : LDA.b #$00 : STA.l $7EF35A`) is a bit convoluted. It seems to check the shield type and only steals if it's not a specific type (possibly the Mirror Shield, which is type 3). + +**Insight:** The `AntiKirby_StealItem` routine is a good example of how to interact directly with Link's inventory in SRAM. It also highlights the importance of explicitly managing the processor status flags (`REP`/`SEP`) when dealing with mixed 8-bit and 16-bit operations, especially when accessing memory. + +## 7. Drawing (`Sprite_AntiKirby_Draw`) +The drawing routine uses `JSL Sprite_PrepOamCoord` and `JSL Sprite_OAM_AllocateDeferToPlayer` for OAM management. It then uses a series of tables (`.start_index`, `.nbr_of_tiles`, `.x_offsets`, `.y_offsets`, `.chr`, `.properties`, `.sizes`) to define the sprite's animation frames and tile data. + +```asm +Sprite_AntiKirby_Draw: +{ + JSL Sprite_PrepOamCoord + JSL Sprite_OAM_AllocateDeferToPlayer + + LDA.w SprGfx, X : CLC : ADC.w SprFrame, X : TAY;Animation Frame + LDA .start_index, Y : STA $06 + + LDA.w SprFlash, X : STA $08 + LDA.w SprMiscC, X : STA $09 + + PHX + LDX .nbr_of_tiles, Y ;amount of tiles -1 + LDY.b #$00 + .nextTile + + ; ... (OAM manipulation logic) ... + + .start_index + db $00, $01, $02, $03, $04, $05, $06, $08, $0A, $0C, $0E, $10 + .nbr_of_tiles + db 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 + ; ... (other OAM tables) ... +} +``` +**Key Observations:** +* The drawing logic includes a check for `SprMiscC, X` to determine if the sprite is facing left or right, and uses different `.x_offsets` tables (`.x_offsets` vs `.x_offsets_2`) accordingly. This is a common pattern for horizontal flipping. +* The `.properties` table defines the palette, priority, and flip bits for each tile. +* The `.sizes` table defines the size of each tile (e.g., `$02` for 16x16). + +## 8. Advanced Design Patterns Demonstrated + +* **Dynamic Difficulty Scaling:** The `_Prep` routine adjusts health, bump damage, and prize based on `Link's Sword` level. +* **Complex State Machine:** The `_Main` routine uses a jump table to manage multiple distinct behaviors (walking, sucking, full, hatted, hurt, death). +* **Direct SRAM Interaction:** The `AntiKirby_StealItem` routine directly modifies Link's inventory in SRAM, demonstrating how to implement item-related mechanics. +* **Explicit Processor Status Management:** The `AntiKirby_StealItem` routine explicitly uses `REP #$20` and `SEP #$20` to ensure correct 8-bit/16-bit operations when accessing SRAM. +* **Conditional Drawing/Flipping:** The `_Draw` routine uses `SprMiscC, X` to conditionally flip the sprite horizontally. diff --git a/Docs/Sprites/Booki.md b/Docs/Sprites/Booki.md new file mode 100644 index 0000000..780f095 --- /dev/null +++ b/Docs/Sprites/Booki.md @@ -0,0 +1,290 @@ +# Booki Sprite Analysis + +This document provides a detailed analysis of the `booki.asm` sprite, outlining its properties, core routines, and behavioral patterns. + +## 1. Sprite Properties + +The following `!SPRID` constants define Booki's fundamental characteristics: + +```asm +!SPRID = Sprite_Booki +!NbrTiles = 02 ; Number of tiles used in a frame +!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless +!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is +!Health = 00 ; Number of Health the sprite have (dynamically set in _Prep) +!Damage = 00 ; (08 is a whole heart), 04 is half heart +!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation +!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it +!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow +!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow +!Palette = 00 ; Unused in this template (can be 0 to 7) +!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool +!Persist = 00 ; 01 = your sprite continue to live offscreen +!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room) +!CollisionLayer = 00 ; 01 = will check both layer for collision +!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall +!DeflectArrow = 00 ; 01 = deflect arrows +!WaterSprite = 00 ; 01 = can only walk shallow water +!Blockable = 00 ; 01 = can be blocked by link's shield? +!Prize = 00 ; 00-15 = the prize pack the sprite will drop from +!Sound = 00 ; 01 = Play different sound when taking damage +!Interaction = 00 ; ?? No documentation +!Statue = 00 ; 01 = Sprite is statue +!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles +!ImperviousArrow = 00 ; 01 = Impervious to arrows +!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks +!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss +``` +**Note:** `!Health` and `!Damage` are initially set to `00` but are dynamically determined during initialization. + +## 2. Core Routines + +### 2.1. `Sprite_Booki_Long` (Main Loop) + +This is the primary entry point for Booki's per-frame execution, called by the game engine. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active. + +```asm +Sprite_Booki_Long: +{ + PHB : PHK : PLB ; Set up bank registers + JSR Sprite_Booki_Draw ; Call drawing routine + JSL Sprite_DrawShadow ; Draw a shadow (if !Shadow is 01) + JSL Sprite_CheckActive : BCC .SpriteIsNotActive ; Check if sprite is active + JSR Sprite_Booki_Main ; If active, run main logic + .SpriteIsNotActive + PLB ; Restore bank register + RTL ; Return from long routine +} +``` + +### 2.2. `Sprite_Booki_Prep` (Initialization) + +This routine is executed once when Booki is first spawned. It dynamically sets Booki's health based on Link's current sword level and initializes `SprMiscB`. + +```asm +Sprite_Booki_Prep: +{ + PHB : PHK : PLB + LDA.l Sword : DEC A : TAY ; Get Link's sword level (0-3), adjust to 0-indexed + LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level + STZ.w SprMiscB, X ; Initialize SprMiscB to 0 + PLB + RTL + + .health ; Health values for each sword level + db $04, $08, $10, $18 ; 4, 8, 16, 24 HP +} +``` + +### 2.3. `Sprite_Booki_Main` (Behavioral State Machine) + +This routine manages Booki's AI through a state machine, using `SprAction, X` to determine the current behavior. It utilizes `JumpTableLocal` for efficient state transitions. + +```asm +Sprite_Booki_Main: +{ + LDA.w SprAction, X + JSL JumpTableLocal ; Jump to the routine specified by SprAction + + dw StalkPlayer ; State 0 + dw HideFromPlayer ; State 1 + dw HiddenFromPlayer ; State 2 + dw ApproachPlayer ; State 3 + + StalkPlayer: + { + %PlayAnimation(0,1,16) ; Animate frames 0-1 every 16 frames + JSR Sprite_Booki_Move ; Handle movement + RTS + } + + HideFromPlayer: + { + %PlayAnimation(0,4,16) ; Animate frames 0-4 every 16 frames + LDA.w SprTimerA, X : BNE + ; Check timer + INC.w SprAction, X ; If timer is 0, transition to HiddenFromPlayer + + + RTS + } + + HiddenFromPlayer: + { + %PlayAnimation(4,4,16) ; Animate frame 4 every 16 frames (static) + JSR Sprite_Booki_Move ; Handle movement + JSL GetRandomInt : AND.b #$03 : BEQ + ; Random chance to transition + INC.w SprAction, X ; If random condition met, transition to ApproachPlayer + + + RTS + } + + ApproachPlayer: + { + %PlayAnimation(5,9,16) ; Animate frames 5-9 every 16 frames + JSR Sprite_Booki_Move ; Handle movement + RTS + } +} +``` + +### 2.4. `Sprite_Booki_Move` (Movement and Interaction Logic) + +This routine is called by the various states in `Sprite_Booki_Main` to handle Booki's physical interactions and movement. It also manages Booki's "float" behavior (`SlowFloat` or `FloatAway`) based on `SprMiscB`. + +```asm +Sprite_Booki_Move: +{ + JSL Sprite_Move ; Apply velocity + JSL Sprite_BounceFromTileCollision ; Handle collision with tiles + JSL Sprite_PlayerCantPassThrough ; Prevent player from passing through Booki + JSL Sprite_DamageFlash_Long ; Handle damage flashing + JSL Sprite_CheckIfRecoiling ; Check for recoil state + + JSL Sprite_IsToRightOfPlayer : CPY.b #$01 : BNE .ToRight ; Determine if Booki is to the right of Link + LDA.b #$01 : STA.w SprMiscC, X ; Set SprMiscC to 1 (for horizontal flip) + JMP .Continue + .ToRight + STZ.w SprMiscC, X ; Set SprMiscC to 0 (no flip) + .Continue + + JSL Sprite_CheckDamageToPlayer ; Check if Booki damages Link + JSL Sprite_CheckDamageFromPlayer : BCC .no_damage ; Check if Link damages Booki + LDA.b #$01 : STA.w SprMiscB, X ; If damaged, set SprMiscB to 1 (FloatAway state) + .no_damage + + LDA.w SprMiscB, X + JSL JumpTableLocal ; Jump to movement routine based on SprMiscB + + dw SlowFloat ; SprMiscB = 0 + dw FloatAway ; SprMiscB = 1 + + SlowFloat: + { + LDY #$04 + JSL GetRandomInt : AND.b #$04 ; Introduce some randomness to movement + JSL Sprite_FloatTowardPlayer ; Float towards Link + + PHX + JSL Sprite_DirectionToFacePlayer ; Update facing direction + ; Check if too close to player + LDA.b $0E : CMP.b #$1A : BCS .NotTooClose + LDA.b $0F : CMP.b #$1A : BCS .NotTooClose + LDA.b #$01 : STA.w SprMiscB, X ; If too close, switch to FloatAway + LDA.b #$20 : STA.w SprTimerA, X ; Set timer + %GotoAction(1) ; Transition to HideFromPlayer state + .NotTooClose + PLX + + RTS + } + + FloatAway: + { + JSL GetRandomInt : AND.b #$04 ; Introduce some randomness to movement + JSL Sprite_FloatAwayFromPlayer ; Float away from Link + + PHX + JSL Sprite_DirectionToFacePlayer ; Update facing direction + ; Check if far enough from player + LDA.b $0E : CMP.b #$1B : BCC .NotTooClose + LDA.b #$1B : CMP.b $0F : BCC .NotTooClose ; Corrected comparison for $0F + LDA.b #$00 : STA.w SprMiscB, X ; If far enough, switch to SlowFloat + %GotoAction(0) ; Transition to StalkPlayer state + .NotTooClose + PLX + + RTS + } +} +``` + +### 2.5. `Sprite_Booki_Draw` (Drawing Routine) + +This routine is responsible for rendering Booki's graphics. It uses a custom OAM (Object Attribute Memory) allocation and manipulation logic rather than the `%DrawSprite()` macro. It dynamically determines the animation frame and applies horizontal flipping based on `SprMiscC`. + +```asm +Sprite_Booki_Draw: +{ + JSL Sprite_PrepOamCoord ; Prepare OAM coordinates + JSL Sprite_OAM_AllocateDeferToPlayer ; Allocate OAM slots, deferring to player + + LDA.w SprGfx, X : CLC : ADC.w SprFrame, X : TAY ; Calculate animation frame index + LDA .start_index, Y : STA $06 ; Store start index for tiles + + LDA.w SprFlash, X : STA $08 ; Store flash status + LDA.w SprMiscC, X : STA $09 ; Store horizontal flip status (0 or 1) + + PHX + LDX .nbr_of_tiles, Y ; Load number of tiles for current frame (minus 1) + LDY.b #$00 ; Initialize Y for OAM buffer offset + .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 ; Multiply by 2 for word access + + REP #$20 ; Set A to 16-bit mode + + LDA $00 : STA ($90), Y ; Store Y-coordinate + AND.w #$0100 : STA $0E ; Check if Y-coord is off-screen (high bit) + INY + LDA $02 : STA ($90), Y ; Store X-coordinate + CLC : ADC #$0010 : CMP.w #$0100 ; Check if X-coord is off-screen + SEP #$20 ; Set A to 8-bit mode + BCC .on_screen_y ; If on screen, continue + + LDA.b #$F0 : STA ($90), Y ; If off-screen, move sprite off-screen + STA $0E + .on_screen_y + + PLX ; Restore Tile Index + INY + LDA .chr, X : STA ($90), Y ; Store character (tile) number + INY + + LDA.b $09 : BEQ .ToRight ; Check SprMiscC for horizontal flip + LDA.b #$29 : JMP .Prop ; If 1, use properties for flipped + .ToRight + LDA.b #$69 ; If 0, use properties for normal + .Prop + ORA $08 : STA ($90), Y ; Apply flash and store OAM properties + + PHY + + TYA : LSR #2 : TAY ; Calculate OAM buffer index for size + LDA.b #$02 : ORA $0F : STA ($92), Y ; Store size (16x16) in OAM buffer + + PLY : INY + + PLX : DEX : BPL .nextTile ; Loop for next tile + + PLX ; Restore X (sprite index) + + RTS + + ; ========================================================= + ; OAM Data Tables + .start_index + db $00, $01, $02, $03, $04, $05, $06, $07, $08, $09 + .nbr_of_tiles + db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; All frames use 1 tile (0-indexed) + .chr + db $0E, $0C, $0A, $2C, $2E, $2E, $0A, $2C, $0C, $0E ; Tile numbers for each frame +} +``` + +## 3. Key Behaviors and Implementation Details + +* **Dynamic Health:** Booki's health is not a fixed property but is determined at spawn time based on Link's current sword level. This allows for dynamic difficulty scaling. +* **State Management:** Booki employs a robust state machine using `SprAction, X` and `JumpTableLocal` to manage its behaviors: `StalkPlayer`, `HideFromPlayer`, `HiddenFromPlayer`, and `ApproachPlayer`. Transitions between these states are triggered by timers, random chance, or player proximity. +* **Player Interaction:** + * **Stalking/Approaching:** Booki uses `Sprite_FloatTowardPlayer` to move towards Link. + * **Hiding/Floating Away:** Booki uses `Sprite_FloatAwayFromPlayer` to retreat from Link, often triggered by taking damage or getting too close. + * **Damage:** Booki can damage Link on contact (`Sprite_CheckDamageToPlayer`) and reacts to damage from Link by transitioning to a `FloatAway` state. +* **Directional Facing:** `SprMiscC, X` is used as a flag to control horizontal flipping in the drawing routine, ensuring Booki always faces Link. +* **Custom OAM Drawing:** Unlike many sprites that might use the `%DrawSprite()` macro, Booki implements its OAM drawing logic directly. This provides fine-grained control over its appearance, including dynamic tile selection and horizontal flipping. The `REP #$20` and `SEP #$20` instructions are used to temporarily switch the accumulator to 16-bit mode for coordinate calculations, demonstrating careful management of the Processor Status Register. +* **Randomness:** `GetRandomInt` is used to introduce variability in Booki's movement patterns and state transitions, making its behavior less predictable. +* **`SprMiscB` Usage:** This variable acts as a sub-state for movement, toggling between `SlowFloat` (approaching) and `FloatAway` (retreating) behaviors. +* **`SprTimerA` Usage:** Used in the `HideFromPlayer` state to control how long Booki remains in that state before transitioning. +* **`Sprite_PlayerCantPassThrough`:** Ensures Booki acts as a solid object that Link cannot simply walk through. diff --git a/Docs/Sprites/Darknut.md b/Docs/Sprites/Darknut.md new file mode 100644 index 0000000..59f53b6 --- /dev/null +++ b/Docs/Sprites/Darknut.md @@ -0,0 +1,328 @@ +# Darknut Sprite Analysis + +This document provides a detailed analysis of the `darknut.asm` sprite, outlining its properties, core routines, and behavioral patterns. + +## 1. Sprite Properties + +The following `!SPRID` constants define Darknut's fundamental characteristics: + +```asm +!SPRID = Sprite_Darknut +!NbrTiles = 03 ; Number of tiles used in a frame +!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless +!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is +!Health = 12 ; Number of Health the sprite have (dynamically set in _Prep) +!Damage = 00 ; (08 is a whole heart), 04 is half heart +!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation +!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it +!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow +!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow +!Palette = 00 ; Unused in this template (can be 0 to 7) +!Hitbox = 12 ; 00 to 31, can be viewed in sprite draw tool +!Persist = 00 ; 01 = your sprite continue to live offscreen +!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room) +!CollisionLayer = 00 ; 01 = will check both layer for collision +!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall +!DeflectArrow = 00 ; 01 = deflect arrows +!WaterSprite = 00 ; 01 = can only walk shallow water +!Blockable = 00 ; 01 = can be blocked by link's shield? +!Prize = 00 ; 00-15 = the prize pack the sprite will drop from +!Sound = 00 ; 01 = Play different sound when taking damage +!Interaction = 00 ; ?? No documentation +!Statue = 00 ; 01 = Sprite is statue +!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles +!ImperviousArrow = 00 ; 01 = Impervious to arrows +!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks +!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss +``` +**Note:** `!Health` is initially set to `12` but is dynamically determined during initialization based on Link's sword level. `!Damage` is `00`, implying damage is handled through other means (e.g., contact with Link's sword). + +## 2. Core Routines + +### 2.1. `Sprite_Darknut_Long` (Main Loop) + +This is the primary entry point for Darknut's per-frame execution, called by the game engine. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active. + +```asm +Sprite_Darknut_Long: +{ + PHB : PHK : PLB ; Set up bank registers + JSR Sprite_Darknut_Draw ; Call drawing routine + JSL Sprite_DrawShadow ; Draw a shadow (if !Shadow is 01) + JSL Sprite_CheckActive : BCC .SpriteIsNotActive ; Check if sprite is active + JSR Sprite_Darknut_Main ; If active, run main logic + .SpriteIsNotActive + PLB ; Restore bank register + RTL ; Return from long routine +} +``` + +### 2.2. `Sprite_Darknut_Prep` (Initialization) + +This routine is executed once when Darknut is first spawned. It dynamically sets Darknut's health based on Link's current sword level, initializes `SprDefl` (deflection timer), and `SprTileDie` (tile for death animation). + +```asm +Sprite_Darknut_Prep: +{ + PHB : PHK : PLB + LDA.l $7EF359 : TAY ; Get Link's sword level (0-3), adjust to 0-indexed + LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level + LDA.b #$80 : STA.w SprDefl, X ; Initialize deflection timer + LDA.b #%01100000 : STA.w SprTileDie, X ; Set tile for death animation + PLB + RTL + + .health ; Health values for each sword level + db $04, $06, $08, $0A ; 4, 6, 8, 10 HP +} +``` + +### 2.3. `Sprite_Darknut_Main` (Behavioral Logic) + +This routine manages Darknut's AI, including probe spawning, parrying, movement, and animation. It uses `SprAction, X` to control its facing direction and animation. + +```asm +Sprite_Darknut_Main: +{ + JSL GetDistance8bit_Long : CMP.b #$80 : BCS .no_probe ; Check distance to Link + JSL Sprite_SpawnProbeAlways_long ; If close, spawn a probe + .no_probe + + JSL Guard_ParrySwordAttacks ; Handle parrying Link's sword attacks + + JSL Sprite_Move ; Apply velocity + JSL Sprite_BounceFromTileCollision ; Handle collision with tiles + JSL Sprite_DamageFlash_Long ; Handle damage flashing + + JSL Sprite_CheckIfRecoiling ; Check for recoil state + + JSL Sprite_CheckDamageFromPlayer : BCC .no_dano ; Check if Link damages Darknut + LDA.b #$FF : STA.w SprTimerD, X ; If damaged, set timer D + .no_dano + + LDA.w SprTimerA, X : BEQ + ; Check timer A + LDA.b #$90 : STA.w SprTimerD, X ; If timer A is not 0, set timer D + + + LDA.w SprTimerD, X : BEQ ++ ; Check timer D + LDA.b #$08 : JSL Sprite_ApplySpeedTowardsPlayer ; Apply speed towards Link + JSL Sprite_DirectionToFacePlayer ; Update facing direction + TYA + STA.w SprMiscC, X ; Store facing direction in SprMiscC + STA.w SprMiscE, X ; Store facing direction in SprMiscE + STA.w SprAction, X ; Set SprAction to facing direction + JSL Guard_ChaseLinkOnOneAxis ; Chase Link along one axis + JMP +++ + ++ + JSR Sprite_Darknut_BasicMove ; If no timers, use basic movement + +++ + + JSR Goriya_HandleTileCollision ; Handle tile collision (specific to Goriya, but used here) + + LDA.w SprAction, X + JSL JumpTableLocal ; Jump to animation routine based on SprAction + + dw FaceRight + dw FaceLeft + dw FaceDown + dw FaceUp + + FaceUp: + { + %PlayAnimation(0,1,10) ; Animate frames 0-1 every 10 frames + RTS + } + + FaceDown: + { + %StartOnFrame(2) + %PlayAnimation(2,3,10) ; Animate frames 2-3 every 10 frames + RTS + } + + FaceLeft: + { + %StartOnFrame(4) + %PlayAnimation(4,5,10) ; Animate frames 4-5 every 10 frames + RTS + } + + FaceRight: + { + %StartOnFrame(6) + %PlayAnimation(6,7,10) ; Animate frames 6-7 every 10 frames + RTS + } +} +``` + +### 2.4. `Sprite_Darknut_BasicMove` (Basic Movement Logic) + +This routine defines Darknut's basic movement patterns, which are executed when no special conditions (like being damaged or timers) are active. It uses `SprAction, X` to determine the current movement direction. + +```asm +Sprite_Darknut_BasicMove: +{ + LDA.w SprAction, X + JSL JumpTableLocal ; Jump to movement routine based on SprAction + + dw MoveRight + dw MoveLeft + dw MoveDown + dw MoveUp + + MoveUp: + { + LDA.b #-DarknutSpeed : STA.w SprYSpeed, X ; Set Y-speed to negative (move up) + STZ.w SprXSpeed, X ; Clear X-speed + RTS + } + + MoveDown: + { + LDA.b #DarknutSpeed : STA.w SprYSpeed, X ; Set Y-speed to positive (move down) + STZ.w SprXSpeed, X ; Clear X-speed + RTS + } + + MoveLeft: + { + LDA.b #-DarknutSpeed : STA.w SprXSpeed, X ; Set X-speed to negative (move left) + STZ.w SprYSpeed, X ; Clear Y-speed + RTS + } + + MoveRight: + { + LDA.b #DarknutSpeed : STA.w SprXSpeed, X ; Set X-speed to positive (move right) + STZ.w SprYSpeed, X ; Clear Y-speed + RTS + } +} +``` + +### 2.5. `Sprite_Darknut_Draw` (Drawing Routine) + +This routine is responsible for rendering Darknut's graphics. It uses a custom OAM (Object Attribute Memory) allocation and manipulation logic, similar to Booki, to handle its multi-tile appearance and animation. It dynamically determines the animation frame and applies offsets for each tile. + +```asm +Sprite_Darknut_Draw: +{ + JSL Sprite_PrepOamCoord ; Prepare OAM coordinates + JSL Sprite_OAM_AllocateDeferToPlayer ; Allocate OAM slots, deferring to player + + LDA.w SprGfx, X : CLC : ADC $0D90, X : TAY ; Calculate animation frame index + LDA .start_index, Y : STA $06 ; Store start index for tiles + LDA.w SprFlash, X : STA $08 ; Store flash status + + PHX + LDX .nbr_of_tiles, Y ; Load number of tiles for current frame (minus 1) + LDY.b #$00 ; Initialize Y for OAM buffer offset + .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 ; Multiply by 2 for word access + + REP #$20 ; Set A to 16-bit mode + + LDA $00 : CLC : ADC .x_offsets, X : STA ($90), Y ; Store Y-coordinate with X-offset + AND.w #$0100 : STA $0E ; Check if Y-coord is off-screen (high bit) + INY + LDA $02 : CLC : ADC .y_offsets, X : STA ($90), Y ; Store X-coordinate with Y-offset + CLC : ADC #$0010 : CMP.w #$0100 + SEP #$20 ; Set A to 8-bit mode + BCC .on_screen_y + + LDA.b #$F0 : STA ($90), Y ; If off-screen, move sprite off-screen + STA $0E + .on_screen_y + + PLX ; Restore Tile Index + INY + LDA .chr, X : STA ($90), Y ; Store character (tile) number + INY + LDA .properties, X : ORA $08 : STA ($90), Y ; Apply flash and store OAM properties + + PHY + + TYA : LSR #2 : TAY ; Calculate OAM buffer index for size + + LDA.w .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer + + PLY : INY + + PLX : DEX : BPL .nextTile ; Loop for next tile + + PLX ; Restore X (sprite index) + + RTS + + ; ========================================================= + ; OAM Data Tables + .start_index + db $00, $03, $06, $09, $0C, $0E, $10, $12 + .nbr_of_tiles + db 2, 2, 2, 2, 1, 1, 1, 1 + .x_offsets + dw 0, 0, 0 + dw 0, 0, 0 + dw 0, 0, 0 + dw 0, 0, 0 + dw 0, -12 + dw 0, -12 + dw 0, 12 + dw 0, 12 + .y_offsets + dw -4, 0, -12 + dw -4, 0, -12 + dw 0, 12, 20 + dw 0, 12, 20 + dw 0, 8 + dw 0, 8 + dw 0, 8 + dw 0, 8 + .chr + db $EF, $E6, $FF + db $EF, $E6, $FF + db $E2, $EF, $FF + db $E2, $EF, $FF + db $E0, $E8 + db $E4, $E8 + db $E0, $E8 + db $E4, $E8 + .properties + db $B9, $39, $B9 + db $B9, $79, $B9 + db $39, $39, $39 + db $79, $39, $39 + db $39, $79 + db $39, $79 + db $79, $39 + db $79, $39 + .sizes + db $00, $02, $00 + db $00, $02, $00 + db $02, $00, $00 + db $02, $00, $00 + db $02, $02 + db $02, $02 + db $02, $02 + db $02, $02 +} +``` + +## 3. Key Behaviors and Implementation Details + +* **Dynamic Health:** Darknut's health is determined at spawn time based on Link's current sword level, similar to Booki, allowing for dynamic difficulty scaling. +* **Probe Spawning:** Darknut has the ability to spawn a probe (`Sprite_SpawnProbeAlways_long`) when Link is within a certain distance, adding a ranged attack or detection mechanism. +* **Parrying Mechanics:** The `Guard_ParrySwordAttacks` routine suggests Darknut can actively defend against Link's sword attacks, potentially deflecting them or becoming temporarily invulnerable. +* **Chasing on One Axis:** When damaged or under certain timer conditions, Darknut uses `Guard_ChaseLinkOnOneAxis` to pursue Link along either the horizontal or vertical axis, making its movement more predictable but still challenging. +* **Basic Movement:** Darknut has a set of basic directional movements (`MoveUp`, `MoveDown`, `MoveLeft`, `MoveRight`) that it cycles through when not actively chasing or reacting to damage. +* **Custom OAM Drawing:** Darknut utilizes a custom OAM drawing routine, similar to Booki, to handle its multi-tile sprite. This routine precisely positions and animates multiple 8x8 tiles to form the larger Darknut sprite. The use of `REP #$20` and `SEP #$20` for 16-bit coordinate calculations is also present here. +* **`SprDefl` and `SprTileDie`:** `SprDefl` is used as a deflection timer, likely related to the parrying mechanic. `SprTileDie` specifies a custom tile to be used during its death animation. +* **`SprMiscC` and `SprMiscE`:** These variables are used to store Darknut's facing direction, which influences both its movement and animation. `SprMiscC` is likely used for animation frame selection, while `SprMiscE` might be used for other directional logic. +* **`Goriya_HandleTileCollision`:** The use of a collision handler named `Goriya_HandleTileCollision` suggests code reuse from another sprite, indicating a shared collision logic for certain enemy types. diff --git a/Docs/Sprites/Goriya.md b/Docs/Sprites/Goriya.md new file mode 100644 index 0000000..40cbeaa --- /dev/null +++ b/Docs/Sprites/Goriya.md @@ -0,0 +1,469 @@ +# Goriya Sprite Analysis + +This document provides a detailed analysis of the `goriya.asm` sprite, outlining its properties, core routines, and behavioral patterns. + +## 1. Sprite Properties + +The following `!SPRID` constants define Goriya's fundamental characteristics: + +```asm +!SPRID = Sprite_Goriya +!NbrTiles = 03 ; Number of tiles used in a frame +!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless +!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is +!Health = 00 ; Number of Health the sprite have (dynamically set in _Prep) +!Damage = 00 ; (08 is a whole heart), 04 is half heart +!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation +!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it +!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow +!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow +!Palette = 00 ; Unused in this template (can be 0 to 7) +!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool +!Persist = 00 ; 01 = your sprite continue to live offscreen +!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room) +!CollisionLayer = 00 ; 01 = will check both layer for collision +!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall +!DeflectArrow = 00 ; 01 = deflect arrows +!WaterSprite = 00 ; 01 = can only walk shallow water +!Blockable = 00 ; 01 = can be blocked by link's shield? +!Prize = 00 ; 00-15 = the prize pack the sprite will drop from +!Sound = 00 ; 01 = Play different sound when taking damage +!Interaction = 00 ; ?? No documentation +!Statue = 00 ; 01 = Sprite is statue +!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles +!ImperviousArrow = 00 ; 01 = Impervious to arrows +!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks +!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss +``` +**Note:** `!Health` and `!Damage` are initially set to `00` but are dynamically determined during initialization. + +## 2. Core Routines + +### 2.1. `Sprite_Goriya_Long` (Main Loop) + +This is the primary entry point for Goriya's per-frame execution, called by the game engine. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active. Notably, it checks `SprSubtype, X` to determine if it's the main Goriya sprite or a boomerang, and calls the appropriate drawing routine. + +```asm +Sprite_Goriya_Long: +{ + PHB : PHK : PLB + LDA.w SprSubtype, X : BEQ + ; Check SprSubtype + JSR Sprite_Boomerang_Draw ; If SprSubtype is not 0, draw as boomerang + JMP ++ + + + JSR Sprite_Goriya_Draw ; If SprSubtype is 0, draw as Goriya + JSL Sprite_DrawShadow + ++ + JSL Sprite_CheckActive : BCC .SpriteIsNotActive ; Check if sprite is active + JSR Sprite_Goriya_Main ; If active, run main logic + .SpriteIsNotActive + PLB + RTL +} +``` + +### 2.2. `Sprite_Goriya_Prep` (Initialization) + +This routine is executed once when Goriya is first spawned. It sets Goriya's health to `08` (one heart). + +```asm +Sprite_Goriya_Prep: +{ + PHB : PHK : PLB + LDA.b #$08 : STA.w SprHealth, X ; Set health to 8 + PLB + RTL +} +``` + +### 2.3. `Sprite_Goriya_Main` (Behavioral State Machine) + +This routine manages Goriya's AI through a state machine, using `SprAction, X` to determine the current behavior. It utilizes `JumpTableLocal` for efficient state transitions, including walking in different directions and a boomerang attack state. + +```asm +Sprite_Goriya_Main: +{ + LDA.w SprAction, X + JSL JumpTableLocal ; Jump to the routine specified by SprAction + + dw Goriya_WalkingUp + dw Goriya_WalkingDown + dw Goriya_WalkingLeft + dw Goriya_WalkingRight + dw BoomerangAttack + + Goriya_WalkingUp: + { + %PlayAnimation(0, 1, 10) ; Animate frames 0-1 every 10 frames + JSR Sprite_Goriya_Move ; Handle movement + RTS + } + + Goriya_WalkingDown: + { + %PlayAnimation(2, 3, 10) ; Animate frames 2-3 every 10 frames + JSR Sprite_Goriya_Move ; Handle movement + RTS + } + + Goriya_WalkingLeft: + { + %StartOnFrame(4) + %PlayAnimation(4, 5, 10) ; Animate frames 4-5 every 10 frames + JSR Sprite_Goriya_Move ; Handle movement + RTS + } + + Goriya_WalkingRight: + { + %StartOnFrame(6) + %PlayAnimation(6, 7, 10) ; Animate frames 6-7 every 10 frames + JSR Sprite_Goriya_Move ; Handle movement + RTS + } + + BoomerangAttack: + { + %PlayAnimation(0, 3, 6) ; Animate frames 0-3 every 6 frames + + LDA.w SprTimerD, X : BNE + ; Check timer D + LDA.b #$16 + JSL Sprite_ApplySpeedTowardsPlayer ; Apply speed towards Link + %SetTimerD($50) ; Set timer D + + + + JSL Sprite_Move ; Apply velocity + JSL Sprite_SpawnSparkleGarnish ; Spawn sparkle effect + + JSL Sprite_CheckDamageToPlayer : BCC .no_dano ; Check if Goriya damages Link + LDA.b #$FF : STA.w SprTimerD, X ; If damaged, set timer D + JSL Sprite_InvertSpeed_XY ; Invert speed + .no_dano + + JSL Sprite_CheckDamageFromPlayer : BCC + ; Check if Link damages Goriya + JSL Sprite_InvertSpeed_XY ; If damaged, invert speed + + + + JSL Sprite_CheckTileCollision ; Check for tile collision + LDA.w SprCollision, X : BEQ + ; If no collision + STZ.w SprState, X ; Clear sprite state (despawn?) + + + + RTS + } +} +``` + +### 2.4. `Sprite_Goriya_Move` (Movement and Interaction Logic) + +This routine is called by the various walking states in `Sprite_Goriya_Main` to handle Goriya's physical interactions and movement. It also manages the logic for throwing a boomerang and changing movement directions. + +```asm +Sprite_Goriya_Move: +{ + JSL Sprite_Move + JSL Sprite_BounceFromTileCollision + JSL Sprite_PlayerCantPassThrough + + JSL Sprite_DamageFlash_Long + + JSL Sprite_CheckDamageToPlayer + JSL Sprite_CheckDamageFromPlayer + + JSL Sprite_CheckIfRecoiling + + JSR Goriya_HandleTileCollision ; Handle tile collision and change direction + + LDA.w SprTimerD, X : BNE ++ + JSL GetRandomInt : AND.b #$9F : BNE ++ + LDA.b #$04 : STA.w SprMiscB, X ; Set SprMiscB for boomerang attack + %SetTimerD($FF) + JSR Goriya_BoomerangAttack ; Spawn boomerang + JMP + + ++ + + LDA.w SprTimerC, X : BNE + + JSL GetRandomInt : AND.b #$03 + STA.w SprMiscB, X ; Set SprMiscB for new movement direction + %SetTimerC(60) + + + + LDA.w SprMiscB, X + JSL JumpTableLocal ; Jump to movement routine based on SprMiscB + + dw Goriya_MoveUp + dw Goriya_MoveDown + dw Goriya_MoveLeft + dw Goriya_MoveRight + dw Goriya_Wait + + Goriya_MoveUp: + { + LDA.b #-GoriyaMovementSpeed : STA.w SprYSpeed, X + STZ.w SprXSpeed, X + %GotoAction(0) ; Transition to Goriya_WalkingUp + LDA.b #$00 : STA.w SprMiscE, X + RTS + } + + Goriya_MoveDown: + { + LDA.b #GoriyaMovementSpeed : STA.w SprYSpeed, X + STZ.w SprXSpeed, X + %GotoAction(1) ; Transition to Goriya_WalkingDown + LDA.b #$01 : STA.w SprMiscE, X + RTS + } + + Goriya_MoveLeft: + { + STZ.w SprYSpeed, X + LDA.b #-GoriyaMovementSpeed : STA.w SprXSpeed, X + %GotoAction(2) ; Transition to Goriya_WalkingLeft + LDA.b #$02 : STA.w SprMiscE, X + RTS + } + + Goriya_MoveRight: + { + STZ.w SprYSpeed, X + LDA.b #GoriyaMovementSpeed : STA.w SprXSpeed, X + %GotoAction(3) ; Transition to Goriya_WalkingRight + LDA.b #$03 : STA.w SprMiscE, X + RTS + } + + Goriya_Wait: + { + STZ.w SprXSpeed, X + STZ.w SprYSpeed, X + %GotoAction(0) ; Transition to Goriya_WalkingUp (default) + RTS + } +} +``` + +### 2.5. `Goriya_HandleTileCollision` + +This routine is called to handle Goriya's collision with tiles. Upon collision, it randomly selects a new movement direction and sets a timer (`SprTimerC`). + +```asm +Goriya_HandleTileCollision: +{ + JSL Sprite_CheckTileCollision + LDA.w SprCollision, X : BEQ ++ + JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X ; Randomly choose new direction + + + STA.w SprMiscE, X + %SetTimerC(60) ; Set timer C for 60 frames + ++ + RTS +} +``` + +### 2.6. `Goriya_BoomerangAttack` + +This routine is responsible for spawning the boomerang sprite. It sets up the boomerang's initial properties, including its `SprSubtype` (to differentiate it from the main Goriya), action, position, and health. + +```asm +Goriya_BoomerangAttack: +{ + LDA.b #$2C ; Sprite ID for boomerang (assuming $2C is the boomerang sprite ID) + JSL Sprite_SpawnDynamically : BMI + ; Spawn a new sprite dynamically + LDA.b #$01 : STA.w SprSubtype, Y ; Set SprSubtype to 1 for the boomerang + LDA.b #$04 : STA.w SprAction, Y ; Set action for boomerang (e.g., flying) + LDA.w SprX, X : STA.w SprX, Y ; Copy Goriya's X position to boomerang + LDA.w SprY, X : STA.w SprY, Y ; Copy Goriya's Y position to boomerang + LDA.w SprXH, X : STA.w SprXH, Y + LDA.w SprYH, X : STA.w SprYH, Y + LDA.b #$01 : STA.w SprNbrOAM, Y ; Set number of OAM entries + LDA.b #$40 : STA.w SprHealth, Y ; Set boomerang health + LDA.b #$00 : STA.w SprHitbox, Y ; Set boomerang hitbox + + + RTS +} +``` + +### 2.7. `Sprite_Goriya_Draw` (Goriya Drawing Routine) + +This routine is responsible for rendering Goriya's graphics. It uses a custom OAM allocation and manipulation logic to handle its multi-tile appearance and animation. It dynamically determines the animation frame and applies offsets for each tile. + +```asm +Sprite_Goriya_Draw: +{ + JSL Sprite_PrepOamCoord + JSL Sprite_OAM_AllocateDeferToPlayer + + LDA.w SprGfx, X : CLC : ADC.w SprFrame, X : TAY ; Calculate animation frame index + LDA.w .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 + + ; Put the sprite out of the way + LDA.b #$F0 : STA ($90), Y : 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.b #$02 : ORA $0F : STA ($92), Y ; store size in oam buffer + PLY : INY + PLX : DEX : BPL .nextTile + + PLX + + RTS + + .start_index + db $00, $02, $04, $06, $08, $0A, $0C, $0E + .nbr_of_tiles + db 1, 1, 1, 1, 1, 1, 1, 1 + .x_offsets + dw 0, 0 + dw 0, 0 + dw 0, 0 + dw 0, 0 + dw 0, 0 + dw 0, 0 + dw 0, 0 + dw 0, 0 + .y_offsets + dw 0, -9 + dw 0, -9 + dw 0, -10 + dw 0, -10 + dw 0, -10 + dw 0, -9 + dw 0, -9 + dw -1, -10 + .chr + ; Body Head + db $E4, $C0 + db $E4, $C0 + db $E6, $C2 + db $E6, $C2 + db $E2, $C4 + db $E0, $C4 + db $E2, $C4 + db $E0, $C4 + .properties + db $2D, $2D + db $6D, $2D + db $2D, $2D + db $6D, $2D + db $2D, $2D + db $2D, $2D + db $6D, $6D + db $6D, $6D +} +``` + +### 2.8. `Sprite_Boomerang_Draw` (Boomerang Drawing Routine) + +This routine is responsible for rendering the boomerang's graphics. It also uses a custom OAM allocation and manipulation logic. + +```asm +Sprite_Boomerang_Draw: +{ + JSL Sprite_PrepOamCoord + JSL Sprite_OAM_AllocateDeferToPlayer + + LDA.w SprGfx, X : CLC : ADC.w SprFrame, 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 : 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 (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 #$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 + .nbr_of_tiles + db 0, 0, 0, 0 + .chr + db $26 + db $26 + db $26 + db $26 + .properties + db $22 + db $A2 + db $E2 + db $62 +} +``` + +## 3. Key Behaviors and Implementation Details + +* **Dual Role (Goriya and Boomerang):** The `Sprite_Goriya_Long` routine uses `SprSubtype, X` to determine if the current sprite instance is the main Goriya or a boomerang it has thrown. This allows a single sprite ID to manage two distinct entities. +* **Boomerang Attack:** Goriya actively throws a boomerang (`Goriya_BoomerangAttack`) at Link, which then becomes an independent sprite with its own drawing and movement logic (`BoomerangAttack` state in `Sprite_Goriya_Main` and `Sprite_Boomerang_Draw`). +* **Dynamic Health:** Goriya's health is set to a fixed value of `08` (one heart) during initialization. +* **State Management:** Goriya uses `SprAction, X` and `JumpTableLocal` to manage its walking states (`Goriya_WalkingUp`, `Goriya_WalkingDown`, `Goriya_WalkingLeft`, `Goriya_WalkingRight`) and its `BoomerangAttack` state. +* **Movement Patterns:** Goriya moves in random directions, with `Goriya_HandleTileCollision` triggering a new random direction upon hitting a tile. It also has a `Goriya_Wait` state. +* **Custom OAM Drawing:** Both the Goriya and its boomerang utilize custom OAM drawing routines (`Sprite_Goriya_Draw` and `Sprite_Boomerang_Draw`) for precise control over their multi-tile graphics and animation. The use of `REP`/`SEP` for 16-bit coordinate calculations is present in both. +* **Code Reuse:** The `Goriya_HandleTileCollision` routine is notably reused by the Darknut sprite, indicating a shared and modular approach to tile collision handling for certain enemy types. +* **`SprMiscB` Usage:** This variable is used to store the current movement direction (0-4) for Goriya's random movement. +* **`SprMiscE` Usage:** This variable also stores the current movement direction, likely for animation or other directional logic. +* **`SprTimerC` and `SprTimerD` Usage:** These timers are used to control the duration of movement states and the frequency of boomerang attacks. diff --git a/Docs/Sprites/HelmetChuchu.md b/Docs/Sprites/HelmetChuchu.md new file mode 100644 index 0000000..652051e --- /dev/null +++ b/Docs/Sprites/HelmetChuchu.md @@ -0,0 +1,433 @@ +# Helmet Chuchu Sprite Analysis + +This document provides a detailed analysis of the `helmet_chuchu.asm` sprite, outlining its properties, core routines, and behavioral patterns. + +## 1. Sprite Properties + +The following `!SPRID` constants define Helmet Chuchu's fundamental characteristics: + +```asm +!SPRID = Sprite_HelmetChuchu +!NbrTiles = 03 ; Number of tiles used in a frame +!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless +!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is +!Health = $10 ; Number of Health the sprite have +!Damage = 04 ; (08 is a whole heart), 04 is half heart +!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation +!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it +!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow +!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow +!Palette = 00 ; Unused in this template (can be 0 to 7) +!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool +!Persist = 00 ; 01 = your sprite continue to live offscreen +!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room) +!CollisionLayer = 00 ; 01 = will check both layer for collision +!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall +!DeflectArrow = 00 ; 01 = deflect arrows +!WaterSprite = 00 ; 01 = can only walk shallow water +!Blockable = 00 ; 01 = can be blocked by link's shield? +!Prize = 00 ; 00-15 = the prize pack the sprite will drop from +!Sound = 00 ; 01 = Play different sound when taking damage +!Interaction = 00 ; ?? No documentation +!Statue = 00 ; 01 = Sprite is statue +!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles +!ImperviousArrow = 00 ; 01 = Impervious to arrows +!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks +!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss +``` +**Note:** `!Health` is initially set to `$10` but is dynamically determined during initialization based on Link's sword level. + +## 2. Core Routines + +### 2.1. `Sprite_HelmetChuchu_Long` (Main Loop) + +This is the primary entry point for Helmet Chuchu's per-frame execution. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active. + +```asm +Sprite_HelmetChuchu_Long: +{ + PHB : PHK : PLB + JSR Sprite_HelmetChuchu_Draw + JSL Sprite_DrawShadow + JSL Sprite_CheckActive : BCC .SpriteIsNotActive + JSR Sprite_HelmetChuchu_Main + .SpriteIsNotActive + PLB + RTL +} +``` + +### 2.2. `Sprite_HelmetChuchu_Prep` (Initialization) + +This routine is executed once when Helmet Chuchu is first spawned. It sets its health based on Link's sword level, randomly assigns an initial `SprAction` (determining its type and initial frame), and initializes `SprMiscB` and `SprMiscD` to zero. + +```asm +Sprite_HelmetChuchu_Prep: +{ + PHB : PHK : PLB + LDA.l Sword : DEC A : TAY + LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level + JSL GetRandomInt : AND.b #$02 : STA.w SprAction, X ; Randomly set initial action (0, 1, or 2) + STZ.w SprMiscB, X + STZ.w SprMiscD, X + LDA.w SprAction, X : BNE + + LDA.b #$04 : STA.w SprFrame, X ; If action 0, set frame to 4 (Helmet Green) + + + CMP.b #$02 : BNE + + LDA.b #$02 : STA.w SprFrame, X ; If action 2, set frame to 2 (Mask Red) + + + PLB + RTL + + .health + db $08, $0C, $0F, $10 ; Health values for each sword level +} +``` + +### 2.3. `Sprite_HelmetChuchu_Main` (Behavioral State Machine) + +This routine manages Helmet Chuchu's AI through a state machine, using `SprAction, X` to determine its current behavior. It includes states for different Chuchu types (Green/Red, Helmet/No Helmet, Mask/No Mask) and separate states for the detached helmet and mask. + +```asm +Sprite_HelmetChuchu_Main: +{ + JSL Sprite_DamageFlash_Long + %SpriteJumpTable(GreenChuchu_Helmet, + GreenChuchu_NoHelmet, + RedChuchu_Masked, + RedChuchu_NoMask, + HelmetSubtype, + MaskSubtype) + + GreenChuchu_Helmet: + { + %StartOnFrame(4) + %PlayAnimation(4, 5, 16) + JSR Sprite_CheckForHookshot : BCC + + LDA.w SprFlash, X : BEQ + + %GotoAction(1) ; Transition to GreenChuchu_NoHelmet if hookshot hit and not flashing + + + JSL Sprite_CheckDamageFromPlayer + JSR Sprite_Chuchu_Move + RTS + } + + GreenChuchu_NoHelmet: + { + %StartOnFrame(0) + %PlayAnimation(0, 1, 16) + LDA.w SprMiscD, X : BNE + + JSR HelmetChuchu_SpawnHookshotDrag ; Spawn detached helmet + LDA.b #$01 : STA.w SprMiscD, X ; Set flag to prevent re-spawning + + + JSL Sprite_CheckDamageFromPlayer + JSR Sprite_Chuchu_Move + RTS + } + + RedChuchu_Masked: + { + %StartOnFrame(2) + %PlayAnimation(2, 3, 16) + JSR Sprite_CheckForHookshot : BCC + + LDA.w SprFlash, X : BEQ + + %GotoAction(3) ; Transition to RedChuchu_NoMask if hookshot hit and not flashing + + + JSL Sprite_CheckDamageFromPlayer + JSR Sprite_Chuchu_Move + RTS + } + + RedChuchu_NoMask: + { + %StartOnFrame(6) + %PlayAnimation(6, 7, 16) + LDA.w SprMiscD, X : BNE + + JSR HelmetChuchu_SpawnHookshotDrag ; Spawn detached mask + LDA.b #$01 : STA.w SprMiscD, X ; Set flag to prevent re-spawning + + + JSL Sprite_CheckDamageFromPlayer + JSR Sprite_Chuchu_Move + RTS + } + + HelmetSubtype: + { + %StartOnFrame(8) + %PlayAnimation(8, 8, 16) + JSL Sprite_Move + JSL Sprite_CheckIfLifted + JSL Sprite_CheckIfRecoiling + JSL ThrownSprite_TileAndSpriteInteraction_long + RTS + } + + MaskSubtype: + { + %StartOnFrame(8) + %PlayAnimation(9, 9, 16) + JSL Sprite_Move + JSL Sprite_CheckIfLifted + JSL Sprite_CheckIfRecoiling + JSL ThrownSprite_TileAndSpriteInteraction_long + RTS + } +} +``` + +### 2.4. `Sprite_Chuchu_Move` (Movement and Interaction Logic) + +This routine handles Helmet Chuchu's movement, which involves bouncing towards or recoiling from the player. It uses `SprMiscB, X` to switch between these two behaviors. + +```asm +Sprite_Chuchu_Move: +{ + JSL Sprite_Move + JSL Sprite_BounceFromTileCollision + JSL Sprite_PlayerCantPassThrough + JSL Sprite_CheckIfRecoiling + + LDA.w SprMiscB, X + JSL JumpTableLocal + + dw BounceTowardPlayer + dw RecoilFromPlayer + + BounceTowardPlayer: + { + JSL GetRandomInt : AND.b #$02 : STA $09 ; Speed + JSL GetRandomInt : AND.b #$07 : STA $08 ; Height + + JSL Sprite_MoveAltitude + DEC.w $0F80,X : DEC.w $0F80,X + LDA.w SprHeight, X : BPL .aloft + STZ.w SprHeight, X + LDA.b $08 : STA.w $0F80, X ; set height from 08 + LDA.b $09 + JSL Sprite_ApplySpeedTowardsPlayer + .aloft + LDA.w SprHeight, X : BEQ .dontmove + JSL Sprite_Move + .dontmove + + JSL Sprite_CheckDamageFromPlayer : BCC .no_damage + INC.w SprMiscB, X ; Switch to RecoilFromPlayer + LDA.b #$20 : STA.w SprTimerB, X + .no_damage + + JSL Sprite_CheckDamageToPlayer : BCC .no_attack + INC.w SprMiscB, X ; Switch to RecoilFromPlayer + LDA.b #$20 : STA.w SprTimerB, X + .no_attack + + RTS + } + + RecoilFromPlayer: + { + JSL GetRandomInt : AND.b #$02 : STA $09 ; Speed + LDA.w SprX, X : CLC : ADC $09 : STA $04 + LDA.w SprY, X : SEC : SBC $09 : STA $06 + LDA.w SprXH, X : ADC #$00 : STA $05 + LDA.w SprYH, X : ADC #$00 : STA $07 + LDA $09 : STA $00 : STA $01 + JSL Sprite_ProjectSpeedTowardsEntityLong + + LDA.w SprTimerB, X : BNE .not_done + JSR HelmetChuchu_SpawnHookshotDrag ; Spawn detached helmet/mask + STZ.w SprMiscB, X ; Switch back to BounceTowardPlayer + .not_done + + RTS + } +} +``` + +### 2.5. `HelmetChuchu_SpawnHookshotDrag` + +This routine is responsible for spawning the detached helmet or mask as a separate sprite when the Chuchu is hit by a hookshot. It determines whether to spawn a helmet or a mask based on the Chuchu's current `SprAction`. + +```asm +HelmetChuchu_SpawnHookshotDrag: +{ + ; Based on the subtype either spawn the helmet or the mask + PHX + LDA.w SprAction, X : CMP.b #$01 : BEQ .spawn_helmet + CMP.b #$03 : BEQ .spawn_mask + + .spawn_helmet + LDA.b #$05 ; Sprite ID for helmet/mask (assuming $05 is the ID) + JSL Sprite_SpawnDynamically : BMI .no_space + LDA.b #$05 : STA.w SprAction, Y ; Set action for detached helmet + JMP .prepare_mask + .no_space + JMP .no_space2 + + .spawn_mask + LDA.b #$05 ; Sprite ID for helmet/mask + JSL Sprite_SpawnDynamically : BMI .no_space2 + LDA.b #$04 : STA.w SprAction, Y ; Set action for detached mask + .prepare_mask + JSL Sprite_SetSpawnedCoordinates + LDA.b #$10 : STA.w SprHealth, Y + LDA.b #$00 : STA.w SprMiscB, Y + LDA.b #$80 : STA.w SprTimerA, Y + LDA.b #$01 : STA.w SprNbrOAM, Y + LDA.w .speed_x, X : STA.w SprXSpeed, Y + LDA.w .speed_y, X : STA.w SprYSpeed, Y + .no_space2 + PLX + RTS + + .speed_x + db 16, -11, -16, 11 + + .speed_y + db 0, 11, 0, -11 +} +``` + +### 2.6. `Sprite_CheckForHookshot` + +This routine checks if a hookshot is currently active and interacting with the Chuchu. It iterates through ancilla slots to find a hookshot (`$1F`) and returns with the carry flag set if found. + +```asm +Sprite_CheckForHookshot: +{ + PHX + LDX.b #$0A + .next_ancilla + LDA.w $0C4A, X : CMP.b #$1F : BNE .not_hooker ; Check ancilla type (assuming $1F is hookshot) + PLX + SEC ; Carry set if hookshot found + RTS + .not_hooker + DEX + BPL .next_ancilla + PLX + CLC ; Carry clear if no hookshot found + RTS +} +``` + +### 2.7. `Sprite_HelmetChuchu_Draw` (Drawing Routine) + +This routine is responsible for rendering Helmet Chuchu's graphics. It uses a custom OAM allocation and manipulation logic to handle its multi-tile appearance and animation, dynamically adjusting based on its current state (helmet/mask, color, animation frame). + +```asm +Sprite_HelmetChuchu_Draw: +{ + JSL Sprite_PrepOamCoord + JSL Sprite_OAM_AllocateDeferToPlayer + + LDA.w SprGfx, X : CLC : ADC.w SprFrame, 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 : 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.b #$02 : ORA $0F : STA ($92), Y ; store size in oam buffer + PLY : INY + PLX : DEX : BPL .nextTile + + PLX + + RTS + + + ; ======================================================= + ; chr prop + ; Mask $04 $37 + ; Helmet $08 $3B + + .start_index + db $00, $02, $03, $06, $08, $0A, $0C, $0E, $0F, $10 + .nbr_of_tiles + db 1, 0, 2, 1, 1, 1, 1, 0, 0, 0 + .y_offsets + dw 0, -8 + dw 0 + dw 0, -8, -8 + dw 0, -4 + dw 0, -8 + dw 0, -4 + dw 0, -8 + dw 0 + dw 0 + dw 0 + .chr + ; No Helmet Green + db $26, $16 + db $24 + ; Mask Red + db $26, $16, $04 + db $24, $04 + ; Helmet Green + db $26, $08 + db $24, $08 + ; No Helmet Red + db $26, $16 + db $24 + ; Mask + db $04 + ; Helmet + db $08 + .properties + db $2B, $2B + db $2B + db $25, $25, $27 + db $25, $27 + db $2B, $29 + db $2B, $29 + db $25, $25 + db $25 + ; mask + db $27 + ; helmet + db $29 +} +``` + +## 3. Key Behaviors and Implementation Details + +* **Dynamic Appearance and State:** Helmet Chuchu is a highly dynamic sprite that changes its appearance and behavior based on whether it has a helmet/mask and its color (green/red). This is managed through its `SprAction` and `SprFrame` values. +* **Conditional Damage Handling:** The Chuchu's vulnerability to damage is tied to the presence of its helmet or mask. When hit by a hookshot, the helmet/mask detaches, making the Chuchu vulnerable. +* **Hookshot Interaction:** Special logic (`Sprite_CheckForHookshot`) is implemented to detect interaction with Link's hookshot, which triggers the detachment of the helmet/mask. +* **Detached Helmet/Mask as Separate Sprites:** When the helmet or mask is detached, it is spawned as an independent sprite (`HelmetSubtype` or `MaskSubtype`) with its own movement (`Sprite_Move`), collision (`ThrownSprite_TileAndSpriteInteraction_long`), and interaction logic. This demonstrates a sophisticated use of child sprites. +* **Movement Patterns:** The Chuchu moves by bouncing towards (`BounceTowardPlayer`) and recoiling from (`RecoilFromPlayer`) the player, with randomness introduced in speed and height. This creates a distinct and challenging movement pattern. +* **Custom OAM Drawing:** The `Sprite_HelmetChuchu_Draw` routine is a complex example of custom OAM manipulation. It dynamically selects tiles and properties based on the Chuchu's current state, allowing for seamless transitions between helmeted, masked, and vulnerable forms. +* **`SprMiscB` Usage:** This variable controls the Chuchu's movement sub-states (`BounceTowardPlayer` and `RecoilFromPlayer`). It also plays a role in the detached helmet/mask sprites. +* **`SprMiscD` Usage:** This variable acts as a flag to ensure that the `HelmetChuchu_SpawnHookshotDrag` routine is called only once when the helmet/mask is detached. +* **`SprTimerB` Usage:** Used to control the duration of the recoil state. diff --git a/Docs/Sprites/PolsVoice.md b/Docs/Sprites/PolsVoice.md new file mode 100644 index 0000000..9c5f7cd --- /dev/null +++ b/Docs/Sprites/PolsVoice.md @@ -0,0 +1,221 @@ +# Pols Voice Sprite Analysis + +This document provides a detailed analysis of the `pols_voice.asm` sprite, outlining its properties, core routines, and behavioral patterns. + +## 1. Sprite Properties + +The following `!SPRID` constants define Pols Voice's fundamental characteristics: + +```asm +!SPRID = Sprite_PolsVoice +!NbrTiles = 02 ; Number of tiles used in a frame +!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless +!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is +!Health = 10 ; Number of Health the sprite have +!Damage = 00 ; (08 is a whole heart), 04 is half heart +!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation +!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it +!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow +!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow +!Palette = 00 ; Unused in this template (can be 0 to 7) +!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool +!Persist = 00 ; 01 = your sprite continue to live offscreen +!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room) +!CollisionLayer = 00 ; 01 = will check both layer for collision +!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall +!DeflectArrow = 00 ; 01 = deflect arrows +!WaterSprite = 00 ; 01 = can only walk shallow water +!Blockable = 00 ; 01 = can be blocked by link's shield? +!Prize = 00 ; 00-15 = the prize pack the sprite will drop from +!Sound = 00 ; 01 = Play different sound when taking damage +!Interaction = 00 ; ?? No documentation +!Statue = 00 ; 01 = Sprite is statue +!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles +!ImperviousArrow = 00 ; 01 = Impervious to arrows +!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks +!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss +``` +**Note:** `!Health` is set to `10` and is not dynamically determined by Link's sword level. + +## 2. Core Routines + +### 2.1. `Sprite_PolsVoice_Long` (Main Loop) + +This is the primary entry point for Pols Voice's per-frame execution. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active. + +```asm +Sprite_PolsVoice_Long: +{ + PHB : PHK : PLB + JSR Sprite_PolsVoice_Draw + JSL Sprite_DrawShadow + JSL Sprite_CheckActive : BCC .SpriteIsNotActive + JSR Sprite_PolsVoice_Main + .SpriteIsNotActive + PLB + RTL +} +``` + +### 2.2. `Sprite_PolsVoice_Prep` (Initialization) + +This routine is executed once when Pols Voice is first spawned. It initializes `SprTimerA` to `$80` and clears `SprDefl` and `SprTileDie`. + +```asm +Sprite_PolsVoice_Prep: +{ + PHB : PHK : PLB + LDA.b #$80 : STA.w SprTimerA, X + STZ.w SprDefl, X + STZ.w SprTileDie, X + PLB + RTL +} +``` + +### 2.3. `Sprite_PolsVoice_Main` (Behavioral State Machine) + +This routine manages Pols Voice's AI through a state machine, using `SprAction, X` to determine its current behavior. It includes states for moving around and hopping around, with a unique interaction based on the flute song. + +```asm +Sprite_PolsVoice_Main: +{ + JSR PolsVoice_CheckForFluteSong ; Check for flute song interaction + + %SpriteJumpTable(PolsVoice_MoveAround, + PolsVoice_HopAround) + + PolsVoice_MoveAround: + { + %StartOnFrame(0) + %PlayAnimation(0,3,10) + + ;$09 = speed, $08 = max height + LDA #$05 : STA $09 + LDA #$02 : STA $08 + JSL Sprite_BounceTowardPlayer + JSL Sprite_BounceFromTileCollision + JSL Sprite_DamageFlash_Long + + %DoDamageToPlayerSameLayerOnContact() + + JSL GetRandomInt : AND #$3F : BNE .not_done ; Random chance to change state + LDA #$04 : STA.w SprTimerA, X + %GotoAction(1) ; Transition to PolsVoice_HopAround + .not_done + + JSL Sprite_CheckDamageFromPlayer : BCC .no_damage ; Check if Link damages Pols Voice + JSL Sprite_DirectionToFacePlayer + + ; Apply the speed positive or negative speed + LDA $0E : BPL .not_up + LDA #$20 : STA.w SprYSpeed, X + BRA .not_down + .not_up + LDA #$E0 : STA.w SprYSpeed, X + .not_down + LDA $0F : BPL .not_right + LDA #$20 : STA.w SprXSpeed, X + BRA .not_left + .not_right + LDA #$E0 : STA.w SprXSpeed, X + .not_left + LDA #$04 : STA.w SprTimerA, X + %GotoAction(1) ; Transition to PolsVoice_HopAround + .no_damage + RTS + } + + PolsVoice_HopAround: + { + %StartOnFrame(4) + %PlayAnimation(4,4,10) + + JSL Sprite_MoveXyz + JSL Sprite_BounceFromTileCollision + JSL Sprite_DamageFlash_Long + + %DoDamageToPlayerSameLayerOnContact() + + LDA.w SprTimerA, X : BNE .not_done ; If timer A is not 0 + %GotoAction(0) ; Transition back to PolsVoice_MoveAround + .not_done + JSL Sprite_CheckDamageFromPlayer : BCC .no_damage ; Check if Link damages Pols Voice + JSL Sprite_InvertSpeed_XY ; Invert speed + .no_damage + RTS + } +} +``` + +### 2.4. `PolsVoice_CheckForFluteSong` + +This routine checks if the player is currently playing the flute (`SongFlag`). If the flute is being played, Pols Voice despawns (`STZ.w SprState, X`) and forces a prize drop. + +```asm +PolsVoice_CheckForFluteSong: +{ + ; If the player plays the flute + LDA.b SongFlag : BEQ + ; Check SongFlag + LDA.b #$03 : STA.w SprState, X ; Set sprite state to despawn + JSL ForcePrizeDrop_long ; Force prize drop + + + RTS +} +``` + +### 2.5. `Sprite_PolsVoice_Draw` (Drawing Routine) + +This routine is responsible for rendering Pols Voice's graphics. It uses the `%DrawSprite()` macro, which reads from a set of data tables to handle its appearance and animation. + +```asm +Sprite_PolsVoice_Draw: +{ + %DrawSprite() + + .start_index + db $00, $01, $02, $03, $04 + .nbr_of_tiles + db 0, 0, 0, 0, 1 + .x_offsets + dw 0 + dw 0 + dw 0 + dw 0 + dw 0, 0 + .y_offsets + dw 0 + dw 0 + dw 0 + dw 0 + dw -4, -20 + .chr + db $6C + db $6A + db $6C + db $6A + db $6E, $4E + .properties + db $3B + db $3B + db $3B + db $7B + db $3B, $3B + .sizes + db $02 + db $02 + db $02 + db $02 + db $02, $02 +} +``` + +## 3. Key Behaviors and Implementation Details + +* **Fixed Health:** Unlike many other sprites, Pols Voice has a fixed health of `10` and its health is not dynamically scaled based on Link's sword level. +* **State Management:** Pols Voice uses `SprAction, X` and `%SpriteJumpTable` to manage its `PolsVoice_MoveAround` and `PolsVoice_HopAround` states. Transitions between these states are triggered by timers or random chance. +* **Movement Patterns:** Pols Voice moves by bouncing towards the player (`Sprite_BounceTowardPlayer`) and also has a hopping movement (`PolsVoice_HopAround`). It reacts to tile collisions by bouncing (`Sprite_BounceFromTileCollision`). +* **Flute Song Interaction:** A unique and defining characteristic of Pols Voice is its vulnerability to the flute song. When Link plays the flute (`SongFlag` is set), Pols Voice immediately despawns and drops a prize (`ForcePrizeDrop_long`). This is a classic Zelda enemy mechanic. +* **Damage Reaction:** When damaged by Link, Pols Voice inverts its speed (`Sprite_InvertSpeed_XY`) and transitions to the `PolsVoice_HopAround` state, providing a temporary reprieve or change in behavior. +* **Custom OAM Drawing:** Pols Voice uses the `%DrawSprite()` macro with OAM data tables to render its appearance and animations. +* **`SprTimerA` Usage:** This timer controls the duration of the `PolsVoice_HopAround` state before transitioning back to `PolsVoice_MoveAround`. diff --git a/Docs/Sprites/Poltergeist.md b/Docs/Sprites/Poltergeist.md new file mode 100644 index 0000000..eea5ddf --- /dev/null +++ b/Docs/Sprites/Poltergeist.md @@ -0,0 +1,233 @@ +# Poltergeist Sprite Analysis + +This document provides a detailed analysis of the `poltergeist.asm` sprite, outlining its properties, core routines, and behavioral patterns. + +## 1. Sprite Properties + +The following `!SPRID` constants define Poltergeist's fundamental characteristics: + +```asm +!SPRID = Sprite_PolsVoice +!NbrTiles = 02 ; Number of tiles used in a frame +!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless +!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is +!Health = 10 ; Number of Health the sprite have +!Damage = 00 ; (08 is a whole heart), 04 is half heart +!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation +!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it +!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow +!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow +!Palette = 00 ; Unused in this template (can be 0 to 7) +!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool +!Persist = 00 ; 01 = your sprite continue to live offscreen +!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room) +!CollisionLayer = 00 ; 01 = will check both layer for collision +!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall +!DeflectArrow = 00 ; 01 = deflect arrows +!WaterSprite = 00 ; 01 = can only walk shallow water +!Blockable = 00 ; 01 = can be blocked by link's shield? +!Prize = 00 ; 00-15 = the prize pack the sprite will drop from +!Sound = 00 ; 01 = Play different sound when taking damage +!Interaction = 00 ; ?? No documentation +!Statue = 00 ; 01 = Sprite is statue +!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles +!ImperviousArrow = 00 ; 01 = Impervious to arrows +!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks +!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss +``` +**Note:** `!Health` is set to `10` and is dynamically determined during initialization based on Link's sword level. + +## 2. Core Routines + +### 2.1. `Sprite_Poltergeist_Long` (Main Loop) + +This is the primary entry point for Poltergeist's per-frame execution. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active. + +```asm +Sprite_Poltergeist_Long: +{ + PHB : PHK : PLB + JSR Sprite_Poltergeist_Draw + JSL Sprite_DrawShadow + JSL Sprite_CheckActive : BCC .SpriteIsNotActive + JSR Sprite_Poltergeist_Main + .SpriteIsNotActive + PLB + RTL +} +``` + +### 2.2. `Sprite_Poltergeist_Prep` (Initialization) + +This routine is executed once when Poltergeist is first spawned. It sets its health based on Link's sword level and initializes `SprTimerA` and `SprTimerB`. + +```asm +Sprite_Poltergeist_Prep: +{ + PHB : PHK : PLB + LDA.l Sword : DEC A : TAY + LDA.w .health, Y : STA.w SprHealth, X + LDA.b #$80 : STA.w SprTimerA, X + LDA.b #$80 : STA.w SprTimerB, X + PLB + RTL + + .health + db $06, $0A, $0C, $10 +} +``` + +### 2.3. `Sprite_Poltergeist_Main` (Behavioral State Machine) + +This routine manages Poltergeist's AI through a state machine, using `SprAction, X` to determine its current behavior. It includes states for moving, attacking, and being stunned. + +```asm +Sprite_Poltergeist_Main: +{ + JSL Sprite_DamageFlash_Long + + %SpriteJumpTable(Poltergeist_Move, + Poltergeist_Attack, + Poltergeist_Stunned) + + Poltergeist_Move: + { + %PlayAnimation(0, 1, 16) + JSR Sprite_Poltergeist_Move + RTS + } + + Poltergeist_Attack: + { + %PlayAnimation(2, 3, 16) + JSL Sprite_Move + JSL Sprite_CheckDamageToPlayer + LDA.w SprTimerA, X : BNE + ; If timer A is not 0 + %GotoAction(0) ; Transition back to Poltergeist_Move + + + RTS + } + + Poltergeist_Stunned: + { + %PlayAnimation(4, 5, 16) + JSL Sprite_Move + JSL Sprite_CheckDamageToPlayer + LDA.w SprTimerA, X : BNE + ; If timer A is not 0 + %GotoAction(0) ; Transition back to Poltergeist_Move + + + RTS + } +} +``` + +### 2.4. `Sprite_Poltergeist_Move` (Movement Logic) + +This routine handles Poltergeist's movement patterns, including moving towards Link, bouncing from tile collisions, and changing direction randomly. + +```asm +Sprite_Poltergeist_Move: +{ + JSL Sprite_Move + JSL Sprite_BounceFromTileCollision + JSL Sprite_PlayerCantPassThrough + JSL Sprite_CheckIfRecoiling + + LDA.w SprTimerC, X : BNE ++ ; Check timer C + JSL GetRandomInt : AND #$3F : BNE ++ ; Random chance to change direction + LDA.b #$40 : STA.w SprTimerC, X + JSL Sprite_SelectNewDirection + ++ + + LDA.w SprTimerA, X : BNE + ; Check timer A + JSL Sprite_IsToRightOfPlayer : CPY.b #$01 : BNE .ToRight + %GotoAction(1) ; Transition to Poltergeist_Attack + JMP .Continue + .ToRight + %GotoAction(1) ; Transition to Poltergeist_Attack + LDA.b #$20 : STA.w SprTimerA, X + JMP .Continue + + + %GotoAction(0) ; Transition to Poltergeist_Move + .Continue + + LDA.w SprMiscB, X + JSL JumpTableLocal + + dw PoltergeistMove + + PoltergeistMove: + { + JSL GetRandomInt : AND.b #$03 + JSL Sprite_ApplySpeedTowardsPlayer + JSL Sprite_CheckTileCollision + + JSL Sprite_CheckDamageFromPlayer + JSL Sprite_CheckDamageToPlayer + + RTS + } +} +``` + +### 2.5. `Sprite_Poltergeist_Draw` (Drawing Routine) + +This routine is responsible for rendering Poltergeist's graphics. It uses the `%DrawSprite()` macro, which reads from a set of data tables to handle its multi-tile appearance and animation. + +```asm +Sprite_Poltergeist_Draw: +{ + %DrawSprite() + + .start_index + db $00, $03, $06, $09, $0C, $0F + .nbr_of_tiles + db 2, 2, 2, 2, 2, 2 + .x_offsets + dw 0, 0, 8 + dw 8, 0, 0 + dw 0, 0, 8 + dw 0, 0, 8 + dw 0, 8, 0 + dw 0, 8, 0 + .y_offsets + dw -8, 0, -8 + dw -8, 0, -8 + dw 0, -8, -8 + dw 0, -8, -8 + dw 0, -8, -8 + dw 0, -8, -8 + .chr + db $3A, $02, $3B + db $3A, $02, $3B + db $20, $00, $01 + db $22, $10, $11 + db $20, $00, $01 + db $22, $10, $11 + .properties + db $2B, $2B, $2B + db $6B, $6B, $6B + db $2B, $2B, $2B + db $2B, $2B, $2B + db $6B, $6B, $6B + db $6B, $6B, $6B + .sizes + db $00, $02, $00 + db $00, $02, $00 + db $02, $00, $00 + db $02, $00, $00 + db $02, $00, $00 + db $02, $00, $00 +} +``` + +## 3. Key Behaviors and Implementation Details + +* **Dynamic Health:** Poltergeist's health is determined at spawn time based on Link's current sword level, allowing for dynamic difficulty scaling. +* **State Management:** Poltergeist uses `SprAction, X` and `%SpriteJumpTable` to manage its `Poltergeist_Move`, `Poltergeist_Attack`, and `Poltergeist_Stunned` states. Transitions between these states are triggered by timers and player proximity. +* **Movement Patterns:** Poltergeist moves towards Link (`Sprite_ApplySpeedTowardsPlayer`) with random direction changes (`Sprite_SelectNewDirection`). It also handles bouncing from tile collisions and cannot be passed through by Link. +* **Attack Behavior:** Poltergeist transitions to an `Poltergeist_Attack` state, which likely involves a direct contact attack or a projectile, and then returns to its movement state after a timer. +* **Stunned State:** When damaged, Poltergeist enters a `Poltergeist_Stunned` state, during which it is temporarily incapacitated. It recovers from this state after a timer. +* **Conditional Invulnerability:** The sprite properties indicate `!ImpervSwordHammer = 00`, but the code does not explicitly set it to `01` when stunned. This might be an oversight or handled by a global routine. However, the presence of `SprDefl` in `_Prep` suggests some form of deflection is intended. +* **Custom OAM Drawing:** Poltergeist uses the `%DrawSprite()` macro with detailed OAM data tables to render its multi-tile appearance and animations across its different states. +* **`SprTimerA`, `SprTimerB`, `SprTimerC` Usage:** These timers control the duration of attack and stunned states, and the frequency of direction changes. diff --git a/Docs/Sprites/Puffstool.md b/Docs/Sprites/Puffstool.md new file mode 100644 index 0000000..fd71c43 --- /dev/null +++ b/Docs/Sprites/Puffstool.md @@ -0,0 +1,287 @@ +# Puffstool Sprite Analysis + +This document provides a detailed analysis of the `puffstool.asm` sprite, outlining its properties, core routines, and behavioral patterns. + +## 1. Sprite Properties + +The following `!SPRID` constants define Puffstool's fundamental characteristics: + +```asm +!SPRID = Sprite_Puffstool +!NbrTiles = 02 ; Number of tiles used in a frame +!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless +!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is +!Health = 0 ; Number of Health the sprite have (dynamically set in _Prep) +!Damage = 0 ; (08 is a whole heart), 04 is half heart +!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation +!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it +!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow +!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow +!Palette = 0 ; Unused in this template (can be 0 to 7) +!Hitbox = 0 ; 00 to 31, can be viewed in sprite draw tool +!Persist = 00 ; 01 = your sprite continue to live offscreen +!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room) +!CollisionLayer = 00 ; 01 = will check both layer for collision +!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall +!DeflectArrow = 00 ; 01 = deflect arrows +!WaterSprite = 00 ; 01 = can only walk shallow water +!Blockable = 00 ; 01 = can be blocked by link's shield? +!Prize = 0 ; 00-15 = the prize pack the sprite will drop from +!Sound = 00 ; 01 = Play different sound when taking damage +!Interaction = 00 ; ?? No documentation +!Statue = 00 ; 01 = Sprite is statue +!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles +!ImperviousArrow = 00 ; 01 = Impervious to arrows +!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks +!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss +``` +**Note:** `!Health`, `!Damage`, `!Hitbox`, and `!Prize` are initially set to `0` but are dynamically determined during initialization. + +## 2. Core Routines + +### 2.1. `Sprite_Puffstool_Long` (Main Loop) + +This is the primary entry point for Puffstool's per-frame execution. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active. + +```asm +Sprite_Puffstool_Long: +{ + PHB : PHK : PLB + JSR Sprite_Puffstool_Draw + JSL Sprite_DrawShadow + JSL Sprite_CheckActive : BCC .SpriteIsNotActive + JSR Sprite_Puffstool_Main + .SpriteIsNotActive + PLB + RTL +} +``` + +### 2.2. `Sprite_Puffstool_Prep` (Initialization) + +This routine is executed once when Puffstool is first spawned. It sets its health based on Link's sword level and initializes `SprDefl`. + +```asm +Sprite_Puffstool_Prep: +{ + PHB : PHK : PLB + LDA.l $7EF359 : TAY + LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level + LDA.b #$80 : STA.w SprDefl, X + PLB + RTL + + .health + db $04, $08, $0A, $10 ; Health values for each sword level +} +``` + +### 2.3. `Sprite_Puffstool_Main` (Behavioral State Machine) + +This routine manages Puffstool's AI through a state machine, using `SprAction, X` to determine its current behavior. It includes states for walking, being stunned, and spawning spores. + +```asm +Sprite_Puffstool_Main: +{ + %SpriteJumpTable(Puffstool_Walking, + Puffstool_Stunned, + Puffstool_Spores) + + Puffstool_Walking: + { + %PlayAnimation(0,6,10) + + JSL Sprite_PlayerCantPassThrough + + LDA.w SprTimerA, X : BNE + ; If timer A is not 0 + JSL Sprite_SelectNewDirection ; Select a new direction + + + JSL Sprite_MoveXyz + JSL Sprite_BounceFromTileCollision + JSL Sprite_DamageFlash_Long + JSL ThrownSprite_TileAndSpriteInteraction_long ; Interact with thrown objects + JSL Sprite_CheckIfRecoiling + JSL Sprite_CheckDamageFromPlayer : BCC .no_dano ; Check if Link damages Puffstool + %GotoAction(1) ; Transition to Puffstool_Stunned + %SetTimerA($60) + %SetTimerF($20) + .no_dano + + RTS + } + + Puffstool_Stunned: + { + %PlayAnimation(7,7,10) + + JSL Sprite_CheckIfLifted + JSL Sprite_DamageFlash_Long + JSL ThrownSprite_TileAndSpriteInteraction_long + + LDA.w SprTimerA, X : BNE + ; If timer A is not 0 + %GotoAction(0) ; Transition back to Puffstool_Walking + + JSL GetRandomInt : AND.b #$1F : BEQ .bomb ; Random chance to spawn bomb + JSR Puffstool_SpawnSpores ; Spawn spores + RTS + .bomb + LDA.b #$4A ; SPRITE 4A (Bomb sprite ID) + LDY.b #$0B + JSL Sprite_SpawnDynamically : BMI .no_space + JSL Sprite_SetSpawnedCoordinates + JSL Sprite_TransmuteToBomb ; Transform into a bomb + .no_space + + + RTS + } + + Puffstool_Spores: + { + %StartOnFrame(8) + %PlayAnimation(8,11,10) + + JSL Sprite_MoveXyz + JSL Sprite_CheckDamageToPlayerSameLayer + + LDA.w SprTimerC, X : BNE + ; If timer C is not 0 + JSL ForcePrizeDrop_long ; Force prize drop + STZ.w SprState, X ; Clear sprite state (despawn?) + + + RTS + } +} +``` + +### 2.4. `Puffstool_SpawnSpores` + +This routine is responsible for spawning spore projectiles. It plays a sound effect and then spawns multiple spore sprites, setting their initial properties like speed, altitude, and timers. + +```asm +Puffstool_SpawnSpores: +{ + LDA.b #$0C ; SFX2.0C + JSL $0DBB7C ; SpriteSFX_QueueSFX2WithPan + + LDA.b #$03 : STA.b $0D ; Number of spores to spawn + + .nth_child + LDA.b #$B1 ; Spore sprite ID (assuming $B1 is the spore sprite ID) + JSL Sprite_SpawnDynamically : BMI .no_space + JSL Sprite_SetSpawnedCoordinates + PHX + + LDX.b $0D + LDA.w .speed_x, X : STA.w SprXSpeed, Y + LDA.w .speed_y, X : STA.w SprYSpeed, Y + LDA.b #$20 : STA.w $0F80, Y ; Altitude + LDA.b #$FF : STA.w $0E80, Y ; Gravity + LDA.b #$40 : STA.w SprTimerC, Y + LDA.b #$01 : STA.w SprSubtype, Y + LDA.b #$02 : STA.w SprAction, Y + + PLX + .no_space + DEC.b $0D + BPL .nth_child + RTS + + .speed_x + db 11, -11, -11, 11 + + .speed_y + db 0, 11, 0, -11 +} +``` + +### 2.5. `Sprite_Puffstool_Draw` (Drawing Routine) + +This routine is responsible for rendering Puffstool's graphics. It uses the `%DrawSprite()` macro, which reads from a set of data tables to handle its multi-tile appearance and animation. + +```asm +Sprite_Puffstool_Draw: +{ + %DrawSprite() + + .start_index + db $00, $02, $04, $06, $08, $0A, $0C, $0E, $0F, $10, $11, $12 + .nbr_of_tiles + db 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 + .x_offsets + dw 0, 0 + dw 0, 0 + dw 0, 0 + dw 0, 0 + dw 0, 0 + dw 0, 0 + dw 0, 0 + dw 0 + dw 0 + dw 0 + dw 0 + dw 4 + .y_offsets + dw -8, 0 + dw 0, -8 + dw 0, -8 + dw 0, -8 + dw 0, -8 + dw 0, -8 + dw 0, -8 + dw 0 + dw 0 + dw 0 + dw 0 + dw 4 + .chr + db $C0, $D0 + db $D2, $C2 + db $D4, $C4 + db $D2, $C2 + db $D0, $C0 + db $D2, $C2 + db $D4, $C4 + db $D6 + db $EA + db $C8 + db $E8 + db $F7 + .properties + db $33, $33 + db $33, $33 + db $33, $33 + db $33, $33 + db $33, $33 + db $73, $73 + db $73, $73 + db $3D + db $33 + db $33 + db $33 + db $33 + .sizes + db $02, $02 + db $02, $02 + db $02, $02 + db $02, $02 + db $02, $02 + db $02, $02 + db $02, $02 + db $02 + db $02 + db $02 + db $02 + db $00 +} +``` + +## 3. Key Behaviors and Implementation Details + +* **Dynamic Health:** Puffstool's health is determined at spawn time based on Link's current sword level, allowing for dynamic difficulty scaling. +* **State Management:** Puffstool uses `SprAction, X` and `%SpriteJumpTable` to manage its `Puffstool_Walking`, `Puffstool_Stunned`, and `Puffstool_Spores` states. +* **Movement Patterns:** In its walking state, Puffstool moves with random direction changes (`Sprite_SelectNewDirection`) and interacts with the environment (`Sprite_MoveXyz`, `Sprite_BounceFromTileCollision`). +* **Stunned State and Counter-Attack:** When damaged, Puffstool enters a `Puffstool_Stunned` state. After a timer, it either spawns multiple spores (`Puffstool_SpawnSpores`) or, with a random chance, transforms into a bomb (`Sprite_TransmuteToBomb`). This provides a unique counter-attack mechanism. +* **Spore Attack:** Puffstool can spawn multiple spore projectiles (`Puffstool_SpawnSpores`) that have their own movement and interaction logic. These spores are spawned with initial speed, altitude, and gravity. +* **Bomb Spawning/Transformation:** A unique behavior where Puffstool can transform into a bomb (`Sprite_TransmuteToBomb`) when stunned, adding an element of surprise and danger. +* **Interaction with Thrown Objects:** The use of `ThrownSprite_TileAndSpriteInteraction_long` suggests Puffstool can be lifted and thrown by Link, or interacts with other thrown objects. +* **Custom OAM Drawing:** Puffstool uses the `%DrawSprite()` macro with detailed OAM data tables to render its multi-tile appearance and animations across its different states. +* **`SprTimerA`, `SprTimerF`, `SprTimerC` Usage:** These timers control the duration of the stunned state, the delay before spawning spores/bombs, and the lifespan of the spawned spores. diff --git a/Docs/Sprites/SeaUrchin.md b/Docs/Sprites/SeaUrchin.md new file mode 100644 index 0000000..57a5efc --- /dev/null +++ b/Docs/Sprites/SeaUrchin.md @@ -0,0 +1,221 @@ +# Sea Urchin Sprite Analysis + +This document provides a detailed analysis of the `sea_urchin.asm` sprite, outlining its properties, core routines, and behavioral patterns. + +## 1. Sprite Properties + +The following `!SPRID` constants define Sea Urchin's fundamental characteristics: + +```asm +!SPRID = Sprite_SeaUrchin +!NbrTiles = 04 ; Number of tiles used in a frame +!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless +!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is +!Health = 06 ; Number of Health the sprite have +!Damage = 04 ; (08 is a whole heart), 04 is half heart +!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation +!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it +!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow +!Shadow = 01 ; 00 = don't draw shadow, 01 = draw a shadow +!Palette = 00 ; Unused in this template (can be 0 to 7) +!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool +!Persist = 00 ; 01 = your sprite continue to live offscreen +!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room) +!CollisionLayer = 00 ; 01 = will check both layer for collision +!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall +!DeflectArrow = 00 ; 01 = deflect arrows +!WaterSprite = 00 ; 01 = can only walk shallow water +!Blockable = 01 ; 01 = can be blocked by link's shield? +!Prize = 03 ; 00-15 = the prize pack the sprite will drop from +!Sound = 00 ; 01 = Play different sound when taking damage +!Interaction = 00 ; ?? No documentation +!Statue = 00 ; 01 = Sprite is statue +!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles +!ImperviousArrow = 00 ; 01 = Impervious to arrows +!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks +!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss +``` +**Note:** `!Health` is fixed at `06` and `!Damage` is `04` (half a heart). + +## 2. Core Routines + +### 2.1. `Sprite_SeaUrchin_Long` (Main Loop) + +This is the primary entry point for Sea Urchin's per-frame execution. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active. + +```asm +Sprite_SeaUrchin_Long: +{ + PHB : PHK : PLB + JSR Sprite_SeaUrchin_Draw + JSL Sprite_DrawShadow + JSL Sprite_CheckActive : BCC .SpriteIsNotActive + JSR Sprite_SeaUrchin_Main + .SpriteIsNotActive + PLB + RTL +} +``` + +### 2.2. `Sprite_SeaUrchin_Prep` (Initialization) + +This routine is executed once when Sea Urchin is first spawned. It sets the initial prize and conditionally modifies its imperviousness and prize based on the `WORLDFLAG` (likely for different game states or areas, such as the Eon Sea). + +```asm +Sprite_SeaUrchin_Prep: +{ + PHB : PHK : PLB + LDA #$01 : STA.w SprPrize, X ; Default prize + LDA.w WORLDFLAG : BEQ + ; Check WORLDFLAG + ; Eon Sea Urchin impervious to sword + LDA.b #%10000100 : STA.w SprDefl, X ; Set imperviousness flags + LDA.b #$07 : STA.w SprPrize, X ; Change prize for Eon Sea + + + PLB + RTL +} +``` + +### 2.3. `Sprite_SeaUrchin_Main` (Behavioral State Machine) + +This routine manages Sea Urchin's AI through a simple state machine, using `SprAction, X` to determine its current behavior. It includes `Idle` and `Death` states. + +```asm +Sprite_SeaUrchin_Main: +{ + LDA.w SprAction, X + JSL JumpTableLocal + + dw Idle + dw Death + + Idle: + { + %PlayAnimation(0,3,8) + %PlayerCantPassThrough() + %DoDamageToPlayerSameLayerOnContact() + JSL Sprite_CheckDamageFromPlayer : BCC .NoDamage + %GotoAction(1) ; Transition to Death state if damaged + .NoDamage + RTS + } + + Death: + { + LDA.b #$06 : STA.w SprState, X ; Set sprite state to despawn + LDA.b #$0A : STA.w SprTimerA, X + STZ.w SprPrize,X + JSL ForcePrizeDrop_long ; Force prize drop + LDA.b #$09 : JSL SpriteSFX_QueueSFX3WithPan ; Play sound effect + RTS + } +} +``` + +### 2.4. `Sprite_SeaUrchin_Draw` (Drawing Routine) + +This routine is responsible for rendering Sea Urchin's graphics. It uses a custom OAM allocation and manipulation logic to handle its multi-tile appearance and animation. + +```asm +Sprite_SeaUrchin_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.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 + .nbr_of_tiles + db 0, 0, 0, 0, 0, 0, 0, 0 + .x_offsets + dw 0 + dw 0 + dw 0 + dw 0 + dw 0 + dw 0 + dw 0 + dw 0 + .y_offsets + dw 0 + dw -1 + dw 0 + dw -1 + dw 0 + dw -1 + dw 0 + dw -1 + .chr + db $EA + db $EC + db $EA + db $EC + db $EA + db $EC + db $EA + db $EC + .properties + db $29 + db $29 + db $69 + db $69 + db $29 + db $29 + db $69 + db $69 +} +``` + +## 3. Key Behaviors and Implementation Details + +* **Fixed Health:** Sea Urchin has a fixed health of `06` and its health is not dynamically scaled based on Link's sword level. +* **Dynamic Prize Drop and Imperviousness:** A notable feature is its conditional behavior based on the `WORLDFLAG`. If this flag is set (e.g., indicating a specific game area like the Eon Sea), the Sea Urchin becomes impervious to sword attacks (`SprDefl`) and drops a different prize. This demonstrates how global game state variables can influence individual sprite properties. +* **State Management:** Sea Urchin uses a simple state machine with `Idle` and `Death` states, managed by `SprAction, X` and `JumpTableLocal`. +* **Movement Patterns:** The Sea Urchin has a simple idle animation (`%PlayAnimation(0,3,8)`) and does not exhibit complex movement behaviors. It remains stationary but can be pushed by Link (`%PlayerCantPassThrough()`). +* **Damage Handling:** Upon taking damage from Link (`Sprite_CheckDamageFromPlayer`), the Sea Urchin transitions to its `Death` state. In this state, it despawns (`STZ.w SprState, X`), forces a prize drop (`ForcePrizeDrop_long`), and plays a sound effect (`SpriteSFX_QueueSFX3WithPan`). +* **Custom OAM Drawing:** Sea Urchin utilizes a custom OAM drawing routine to render its multi-tile appearance and animation. The drawing logic includes coordinate calculations with `REP`/`SEP` for 16-bit operations. diff --git a/Docs/Sprites/ThunderGhost.md b/Docs/Sprites/ThunderGhost.md new file mode 100644 index 0000000..2fbf893 --- /dev/null +++ b/Docs/Sprites/ThunderGhost.md @@ -0,0 +1,313 @@ +# Thunder Ghost Sprite Analysis + +This document provides a detailed analysis of the `thunder_ghost.asm` sprite, outlining its properties, core routines, and behavioral patterns. + +## 1. Sprite Properties + +The following `!SPRID` constants define Thunder Ghost's fundamental characteristics: + +```asm +!SPRID = Sprite_ThunderGhost +!NbrTiles = 03 ; Number of tiles used in a frame +!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless +!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is +!Health = 10 ; Number of Health the sprite have (dynamically set in _Prep) +!Damage = 00 ; (08 is a whole heart), 04 is half heart +!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation +!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it +!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow +!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow +!Palette = 00 ; Unused in this template (can be 0 to 7) +!Hitbox = 09 ; 00 to 31, can be viewed in sprite draw tool +!Persist = 00 ; 01 = your sprite continue to live offscreen +!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room) +!CollisionLayer = 00 ; 01 = will check both layer for collision +!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall +!DeflectArrow = 00 ; 01 = deflect arrows +!WaterSprite = 00 ; 01 = can only walk shallow water +!Blockable = 00 ; 01 = can be blocked by link's shield? +!Prize = 00 ; 00-15 = the prize pack the sprite will drop from +!Sound = 00 ; 01 = Play different sound when taking damage +!Interaction = 00 ; ?? No documentation +!Statue = 00 ; 01 = Sprite is statue +!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles +!ImperviousArrow = 00 ; 01 = Impervious to arrows +!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks +!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss +``` +**Note:** `!Health` is initially set to `10` but is dynamically determined during initialization based on Link's sword level. + +## 2. Core Routines + +### 2.1. `Sprite_ThunderGhost_Long` (Main Loop) + +This is the primary entry point for Thunder Ghost's per-frame execution. It handles drawing, shadow rendering (conditionally), and then dispatches to the main logic routine if the sprite is active. + +```asm +Sprite_ThunderGhost_Long: +{ + PHB : PHK : PLB + JSR Sprite_ThunderGhost_Draw + LDA.w SprAction, X : CMP.b #$03 : BCS + ; Don't draw shadow if casting thunder + JSL Sprite_DrawShadow + + + JSL Sprite_CheckActive : BCC .SpriteIsNotActive + JSR Sprite_ThunderGhost_Main + .SpriteIsNotActive + PLB + RTL +} +``` + +### 2.2. `Sprite_ThunderGhost_Prep` (Initialization) + +This routine is executed once when Thunder Ghost is first spawned. It sets its health based on Link's sword level and initializes `SprTimerA` and `SprTimerB`. + +```asm +Sprite_ThunderGhost_Prep: +{ + PHB : PHK : PLB + LDA.l Sword : DEC A : TAY + LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level + LDA.b #$08 : STA.w SprTimerB, X + LDA.b #$08 : STA.w SprTimerA, X + PLB + RTL + + .health + db $06, $0A, $0C, $10 ; Health values for each sword level +} +``` + +### 2.3. `Sprite_ThunderGhost_Main` (Behavioral State Machine) + +This routine manages Thunder Ghost's AI through a state machine, using `SprAction, X` to determine its current behavior. It includes states for facing forward, left, and right, as well as states for casting thunder in different directions. + +```asm +Sprite_ThunderGhost_Main: +{ + %SpriteJumpTable(ThunderGhostFaceForward, + ThunderGhostLeft, + ThunderGhostRight, + CastThunderLeft, + CastThunderRight) + + ThunderGhostFaceForward: + { + %PlayAnimation(0, 1, 16) + JSR Sprite_ThunderGhost_Move + RTS + } + + ThunderGhostLeft: + { + %PlayAnimation(2, 3, 16) + JSR Sprite_ThunderGhost_Move + RTS + } + + ThunderGhostRight: + { + %PlayAnimation(4, 5, 16) + JSR Sprite_ThunderGhost_Move + RTS + } + + CastThunderLeft: + { + %StartOnFrame(6) + %PlayAnimation(6, 6, 16) + JSL Sprite_CheckDamageToPlayer + JSL Sprite_MoveLong + + LDA.w SprTimerA, X : BNE + ; If timer A is not 0 + STZ.w SprState, X ; Clear sprite state (despawn?) + + + RTS + } + + CastThunderRight: + { + %StartOnFrame(6) + %PlayAnimation(7, 7, 16) + + JSL Sprite_CheckDamageToPlayer + JSL Sprite_MoveLong + + LDA.w SprTimerA, X : BNE + ; If timer A is not 0 + STZ.w SprState, X ; Clear sprite state (despawn?) + + + RTS + } +} +``` + +### 2.4. `Sprite_ThunderGhost_Move` (Movement and Interaction Logic) + +This routine handles Thunder Ghost's movement, collision, and interaction with the player. It also includes logic for randomly triggering lightning attacks and changing its facing direction. + +```asm +Sprite_ThunderGhost_Move: +{ + JSL Sprite_Move + JSL Sprite_BounceFromTileCollision + JSL Sprite_PlayerCantPassThrough + JSL Sprite_DamageFlash_Long + JSL Sprite_CheckIfRecoiling + LDA.w SprTimerC, X : BNE ++ ; Check timer C + JSL GetRandomInt : AND #$3F : BNE ++ ; Random chance to spawn lightning + LDA.b #$40 : STA.w SprTimerC, X ; Set timer C + JSR SpawnLightningAttack ; Spawn lightning attack + ++ + + LDA.w SprTimerA, X : BNE + ; Check timer A + JSL Sprite_IsToRightOfPlayer : CPY.b #$01 : BNE .ToRight ; Determine if to the right of Link + %GotoAction(1) ; Transition to ThunderGhostLeft + JMP .Continue + .ToRight + %GotoAction(2) ; Transition to ThunderGhostRight + LDA.b #$20 : STA.w SprTimerA, X ; Set timer A + JMP .Continue + + + %GotoAction(0) ; Transition to ThunderGhostFaceForward + .Continue + + LDA.w SprMiscB, X + JSL JumpTableLocal + + dw ThunderGhostMove + + ThunderGhostMove: + { + JSL GetRandomInt : AND.b #$03 + JSL Sprite_ApplySpeedTowardsPlayer + JSL Sprite_CheckTileCollision + + JSL Sprite_CheckDamageFromPlayer + JSL Sprite_CheckDamageToPlayer + + RTS + } +} +``` + +### 2.5. `SpawnLightningAttack` + +This routine is responsible for spawning the lightning attack sprite. It sets up the lightning's initial properties, including its `SprSubtype`, action, position, and speed, based on Thunder Ghost's position relative to Link. + +```asm +SpawnLightningAttack: +{ + PHX + LDA.b #$CD ; Sprite ID for lightning (assuming $CD is the lightning sprite ID) + JSL Sprite_SpawnDynamically : BMI .no_space + ; Use SprXSpeed, SprYSpeed, SprXRound, SprYRound + ; SprX, SprY, SprXH, SprY, to cast the lightning spell + ; and make it move off to the bottom left or bottom right + + ; Y is the ID of the new attack sprite + ; X is the ID of the current source sprite + + ; Left 0 or Right 1 + PHY + JSL Sprite_IsToRightOfPlayer : TAY : CMP.b #$01 : BEQ + ; Determine if to the right of Link + LDA.b #$00 + JMP .Continue + + + LDA.b #$01 + .Continue + CLC : ADC.b #$03 + PLY + STA.w SprSubtype, Y ; Set SprSubtype for lightning + STA.w SprAction, Y ; Set action for lightning + + LDA.w SprX, X : STA.w SprX, Y + LDA.w SprY, X : STA.w SprY, Y + LDA.w SprXH, X : STA.w SprXH, Y + LDA.w SprYH, X : STA.w SprYH, Y + + LDA.w SprXSpeed, X : STA.w SprXSpeed, Y + LDA.w SprYSpeed, X : STA.w SprYSpeed, Y + LDA.b #$02 : STA.w SprXRound, Y + LDA.b #$02 : STA.w SprYRound, Y + LDA.b #$30 : STA.w SprTimerA, Y + LDA.b #$30 : STA.w SprTimerB, Y + .no_space + + PLX + + RTS +} +``` + +### 2.6. `Sprite_ThunderGhost_Draw` (Drawing Routine) + +This routine is responsible for rendering Thunder Ghost's graphics. It uses the `%DrawSprite()` macro, which reads from a set of data tables to handle its multi-tile appearance and animation. + +```asm +Sprite_ThunderGhost_Draw: +{ + %DrawSprite() + + .start_index + db $00, $03, $06, $09, $0C, $0F, $12, $15 + .nbr_of_tiles + db 2, 2, 2, 2, 2, 2, 2, 2 + .x_offsets + dw 0, 0, 8 + dw 8, 0, 0 + dw 0, 0, 8 + dw 0, 0, 8 + dw 0, 8, 0 + dw 0, 8, 0 + dw -12, -8, -16 + dw 12, 16, 20 + .y_offsets + dw -8, 0, -8 + dw -8, 0, -8 + dw 0, -8, -8 + dw 0, -8, -8 + dw 0, -8, -8 + dw 0, -8, -8 + dw 12, 24, 20 + dw 12, 24, 12 + .chr + db $3A, $02, $3B + db $3A, $02, $3B + db $20, $00, $01 + db $22, $10, $11 + db $20, $00, $01 + db $22, $10, $11 + db $28, $2A, $2B + db $28, $2A, $2B + .properties + db $2B, $2B, $2B + db $6B, $6B, $6B + db $2B, $2B, $2B + db $2B, $2B, $2B + db $6B, $6B, $6B + db $6B, $6B, $6B + db $2B, $2B, $2B + db $6B, $2B, $2B + .sizes + db $00, $02, $00 + db $00, $02, $00 + db $02, $00, $00 + db $02, $00, $00 + db $02, $00, $00 + db $02, $00, $00 + db $02, $00, $00 + db $02, $00, $00 +} +``` + +## 3. Key Behaviors and Implementation Details + +* **Dynamic Health:** Thunder Ghost's health is determined at spawn time based on Link's current sword level, allowing for dynamic difficulty scaling. +* **Conditional Shadow Drawing:** The shadow is not drawn when Thunder Ghost is in its `CastThunderLeft` or `CastThunderRight` states, suggesting a visual distinction during its attack. +* **Lightning Attack:** Thunder Ghost has a random chance to spawn a lightning attack (`SpawnLightningAttack`) at regular intervals, which then becomes an independent sprite with its own movement and interaction logic. +* **State Management:** Thunder Ghost uses `SprAction, X` and `%SpriteJumpTable` to manage its facing direction (forward, left, right) and its thunder-casting states. +* **Movement Patterns:** Thunder Ghost moves randomly and applies speed towards the player, while also bouncing from tile collisions and being unable to be passed through by Link. +* **Projectile Spawning with Directional Logic:** The `SpawnLightningAttack` routine demonstrates how to spawn a projectile (`$CD`) and initialize its properties, including its `SprSubtype` and `SprAction`, based on Thunder Ghost's position relative to Link. +* **`SprTimerA`, `SprTimerB`, `SprTimerC` Usage:** These timers are used to control the frequency of lightning attacks and the duration of facing/movement states. +* **`Sprite_MoveLong`:** Used in the `CastThunderLeft` and `CastThunderRight` states, suggesting a specific movement behavior during the attack animation.