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.
This commit is contained in:
scawful
2025-10-02 23:55:31 -04:00
parent 6780dd0d45
commit 1cc7d84782
11 changed files with 3083 additions and 11 deletions

View File

@@ -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.
* **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.

254
Docs/Sprites/AntiKirby.md Normal file
View File

@@ -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.

290
Docs/Sprites/Booki.md Normal file
View File

@@ -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.

328
Docs/Sprites/Darknut.md Normal file
View File

@@ -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.

469
Docs/Sprites/Goriya.md Normal file
View File

@@ -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.

View File

@@ -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.

221
Docs/Sprites/PolsVoice.md Normal file
View File

@@ -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`.

233
Docs/Sprites/Poltergeist.md Normal file
View File

@@ -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.

287
Docs/Sprites/Puffstool.md Normal file
View File

@@ -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.

221
Docs/Sprites/SeaUrchin.md Normal file
View File

@@ -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.

View File

@@ -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.