Add new sprite documentation for Minecart, Pedestal, Portal, and Switch Track
- Created detailed documentation for the Minecart sprite, outlining its properties, constants, collision setup, main logic, and design patterns. - Added documentation for the Pedestal sprite, including its vanilla overrides, custom logic for item interaction, and event triggering based on area context. - Introduced documentation for the Portal sprite, detailing its two-way warping system, initialization, main logic, and helper routines for seamless transitions. - Documented the Switch Track sprite, explaining its interactive behavior, state-based animation, and integration with external switches for dynamic track manipulation.
This commit is contained in:
254
Docs/Sprites/Enemies/AntiKirby.md
Normal file
254
Docs/Sprites/Enemies/AntiKirby.md
Normal 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/Enemies/Booki.md
Normal file
290
Docs/Sprites/Enemies/Booki.md
Normal 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.
|
||||
122
Docs/Sprites/Enemies/BusinessScrub.md
Normal file
122
Docs/Sprites/Enemies/BusinessScrub.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Business Scrub
|
||||
|
||||
## Overview
|
||||
The Business Scrub is a custom enemy sprite that likely overrides a vanilla sprite ID. It is characterized by its low health and harmless contact damage, suggesting its primary threat comes from projectiles or other interactions defined within its main logic.
|
||||
|
||||
## Sprite Properties
|
||||
* **`!SPRID`**: `$00` (Vanilla sprite ID, likely overridden)
|
||||
* **`!NbrTiles`**: `$02`
|
||||
* **`!Health`**: `$01`
|
||||
* **`!Damage`**: `$00` (Harmless contact)
|
||||
* **`!Harmless`**: `$00`
|
||||
* **`!Hitbox`**: `$08`
|
||||
* **`!ImperviousAll`**: `$00`
|
||||
* **`!Statue`**: `$00`
|
||||
* **`!Prize`**: `$00`
|
||||
* **`!Boss`**: `$00`
|
||||
* **Collision Properties**: All collision-related properties (`!Defl`, `!SprColl`, etc.) are set to `$00`, indicating that direct contact damage and knockback are not handled by these properties. Interaction and damage are likely managed within the sprite's main logic.
|
||||
|
||||
## Main Structure (`Sprite_BusinessScrub_Long`)
|
||||
This routine is the main entry point for the Business Scrub, executed every frame. It sets up bank registers, calls the drawing routine, and then executes the main logic if the sprite is active.
|
||||
|
||||
```asm
|
||||
Sprite_BusinessScrub_Long:
|
||||
{
|
||||
PHB : PHK : PLB
|
||||
JSR Sprite_BusinessScrub_Draw
|
||||
JSL Sprite_DrawShadow
|
||||
|
||||
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
|
||||
JSR Sprite_BusinessScrub_Main
|
||||
.SpriteIsNotActive
|
||||
|
||||
PLB
|
||||
RTL
|
||||
}
|
||||
```
|
||||
|
||||
## Initialization (`Sprite_BusinessScrub_Prep`)
|
||||
This routine runs once when the Business Scrub is spawned. It initializes the sprite's action state to `0` and sets a general-purpose timer (`SprTimerA`) to `120` frames (2 seconds).
|
||||
|
||||
```asm
|
||||
Sprite_BusinessScrub_Prep:
|
||||
{
|
||||
PHB : PHK : PLB
|
||||
%GotoAction(0)
|
||||
%SetTimerA(120)
|
||||
PLB
|
||||
RTL
|
||||
}
|
||||
```
|
||||
|
||||
## Main Logic & State Machine (`Sprite_BusinessScrub_Main`)
|
||||
The core behavior of the Business Scrub is managed by a state machine using `%SpriteJumpTable`. The current states are:
|
||||
|
||||
* **`State_Idle`**: The initial state where the scrub is idle. It plays an animation and checks for player proximity. If Link is within 80 pixels, it transitions to `State_Attacking`.
|
||||
* **`State_Attacking`**: In this state, the scrub plays an attack animation, moves towards the player, and deals damage on contact. It also checks if it has been hit by the player and transitions to `State_Hurt` if so.
|
||||
* **`State_Hurt`**: This state handles the sprite being hit, causing it to flash and be knocked back.
|
||||
|
||||
```asm
|
||||
Sprite_BusinessScrub_Main:
|
||||
{
|
||||
%SpriteJumpTable(State_Idle, State_Attacking, State_Hurt)
|
||||
|
||||
State_Idle:
|
||||
{
|
||||
%PlayAnimation(0, 1, 15)
|
||||
|
||||
JSL GetDistance8bit_Long : CMP.b #$50 : BCS .player_is_far
|
||||
%GotoAction(1)
|
||||
.player_is_far
|
||||
RTS
|
||||
}
|
||||
|
||||
State_Attacking:
|
||||
{
|
||||
%PlayAnimation(2, 3, 8)
|
||||
%MoveTowardPlayer(12)
|
||||
%DoDamageToPlayerSameLayerOnContact()
|
||||
|
||||
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
|
||||
%GotoAction(2)
|
||||
.no_damage
|
||||
RTS
|
||||
}
|
||||
|
||||
State_Hurt:
|
||||
{
|
||||
JSL Sprite_DamageFlash_Long
|
||||
RTS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Drawing (`Sprite_BusinessScrub_Draw`)
|
||||
The drawing routine uses the `%DrawSprite()` macro to render the sprite's graphics based on defined OAM data tables.
|
||||
|
||||
```asm
|
||||
Sprite_BusinessScrub_Draw:
|
||||
{
|
||||
%DrawSprite()
|
||||
|
||||
.start_index
|
||||
db $00, $02, $04, $06
|
||||
.nbr_of_tiles
|
||||
db 1, 1, 1, 1
|
||||
|
||||
.x_offsets
|
||||
dw -8, 8, -8, 8, -8, 8, -8, 8
|
||||
.y_offsets
|
||||
dw -8, -8, -8, -8, -8, -8, -8, -8
|
||||
.chr
|
||||
db $C0, $C2, $C4, $C6, $C8, $CA, $CC, $CE
|
||||
.properties
|
||||
db $3B, $7B, $3B, $7B, $3B, $7B, $3B, $7B
|
||||
}
|
||||
```
|
||||
|
||||
## Design Patterns
|
||||
* **State Machine**: Utilizes a clear state machine (`%SpriteJumpTable`) for managing different behaviors (Idle, Attacking, Hurt).
|
||||
* **Player Interaction**: Incorporates distance checks (`GetDistance8bit_Long`) to trigger state changes and direct damage on contact (`DoDamageToPlayerSameLayerOnContact`).
|
||||
* **Damage Handling**: Includes a basic damage reaction (`Sprite_CheckDamageFromPlayer`, `Sprite_DamageFlash_Long`).
|
||||
* **Vanilla ID Override**: The use of `!SPRID = $00` suggests this sprite is intended to replace or modify the behavior of a vanilla sprite with ID $00.
|
||||
328
Docs/Sprites/Enemies/Darknut.md
Normal file
328
Docs/Sprites/Enemies/Darknut.md
Normal 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.
|
||||
122
Docs/Sprites/Enemies/EonScrub.md
Normal file
122
Docs/Sprites/Enemies/EonScrub.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Eon Scrub
|
||||
|
||||
## Overview
|
||||
The Eon Scrub is a custom enemy sprite, likely a variation of the Business Scrub, that overrides a vanilla sprite ID. It shares similar characteristics with the Business Scrub, including low health and harmless contact damage, implying its primary threat comes from projectiles or other interactions defined within its main logic.
|
||||
|
||||
## Sprite Properties
|
||||
* **`!SPRID`**: `$00` (Vanilla sprite ID, likely overridden)
|
||||
* **`!NbrTiles`**: `$02`
|
||||
* **`!Health`**: `$01`
|
||||
* **`!Damage`**: `$00` (Harmless contact)
|
||||
* **`!Harmless`**: `$00`
|
||||
* **`!Hitbox`**: `$08`
|
||||
* **`!ImperviousAll`**: `$00`
|
||||
* **`!Statue`**: `$00`
|
||||
* **`!Prize`**: `$00`
|
||||
* **`!Boss`**: `$00`
|
||||
* **Collision Properties**: All collision-related properties (`!Defl`, `!SprColl`, etc.) are set to `$00`, indicating that direct contact damage and knockback are not handled by these properties. Interaction and damage are likely managed within the sprite's main logic.
|
||||
|
||||
## Main Structure (`Sprite_EonScrub_Long`)
|
||||
This routine is the main entry point for the Eon Scrub, executed every frame. It sets up bank registers, calls the drawing routine, and then executes the main logic if the sprite is active.
|
||||
|
||||
```asm
|
||||
Sprite_EonScrub_Long:
|
||||
{
|
||||
PHB : PHK : PLB
|
||||
JSR Sprite_EonScrub_Draw
|
||||
JSL Sprite_DrawShadow
|
||||
|
||||
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
|
||||
JSR Sprite_EonScrub_Main
|
||||
.SpriteIsNotActive
|
||||
|
||||
PLB
|
||||
RTL
|
||||
}
|
||||
```
|
||||
|
||||
## Initialization (`Sprite_EonScrub_Prep`)
|
||||
This routine runs once when the Eon Scrub is spawned. It initializes the sprite's action state to `0` and sets a general-purpose timer (`SprTimerA`) to `120` frames (2 seconds).
|
||||
|
||||
```asm
|
||||
Sprite_EonScrub_Prep:
|
||||
{
|
||||
PHB : PHK : PLB
|
||||
%GotoAction(0)
|
||||
%SetTimerA(120)
|
||||
PLB
|
||||
RTL
|
||||
}
|
||||
```
|
||||
|
||||
## Main Logic & State Machine (`Sprite_EonScrub_Main`)
|
||||
The core behavior of the Eon Scrub is managed by a state machine using `%SpriteJumpTable`. The current states are:
|
||||
|
||||
* **`State_Idle`**: The initial state where the scrub is idle. It plays an animation and checks for player proximity. If Link is within 80 pixels, it transitions to `State_Attacking`.
|
||||
* **`State_Attacking`**: In this state, the scrub plays an attack animation, moves towards the player, and deals damage on contact. It also checks if it has been hit by the player and transitions to `State_Hurt` if so.
|
||||
* **`State_Hurt`**: This state handles the sprite being hit, causing it to flash and be knocked back.
|
||||
|
||||
```asm
|
||||
Sprite_EonScrub_Main:
|
||||
{
|
||||
%SpriteJumpTable(State_Idle, State_Attacking, State_Hurt)
|
||||
|
||||
State_Idle:
|
||||
{
|
||||
%PlayAnimation(0, 1, 15)
|
||||
|
||||
JSL GetDistance8bit_Long : CMP.b #$50 : BCS .player_is_far
|
||||
%GotoAction(1)
|
||||
.player_is_far
|
||||
RTS
|
||||
}
|
||||
|
||||
State_Attacking:
|
||||
{
|
||||
%PlayAnimation(2, 3, 8)
|
||||
%MoveTowardPlayer(12)
|
||||
%DoDamageToPlayerSameLayerOnContact()
|
||||
|
||||
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
|
||||
%GotoAction(2)
|
||||
.no_damage
|
||||
RTS
|
||||
}
|
||||
|
||||
State_Hurt:
|
||||
{
|
||||
JSL Sprite_DamageFlash_Long
|
||||
RTS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Drawing (`Sprite_EonScrub_Draw`)
|
||||
The drawing routine uses the `%DrawSprite()` macro to render the sprite's graphics based on defined OAM data tables.
|
||||
|
||||
```asm
|
||||
Sprite_EonScrub_Draw:
|
||||
{
|
||||
%DrawSprite()
|
||||
|
||||
.start_index
|
||||
db $00, $02, $04, $06
|
||||
.nbr_of_tiles
|
||||
db 1, 1, 1, 1
|
||||
|
||||
.x_offsets
|
||||
dw -8, 8, -8, 8, -8, 8, -8, 8
|
||||
.y_offsets
|
||||
dw -8, -8, -8, -8, -8, -8, -8, -8
|
||||
.chr
|
||||
db $C0, $C2, $C4, $C6, $C8, $CA, $CC, $CE
|
||||
.properties
|
||||
db $3B, $7B, $3B, $7B, $3B, $7B, $3B, $7B
|
||||
}
|
||||
```
|
||||
|
||||
## Design Patterns
|
||||
* **State Machine**: Utilizes a clear state machine (`%SpriteJumpTable`) for managing different behaviors (Idle, Attacking, Hurt).
|
||||
* **Player Interaction**: Incorporates distance checks (`GetDistance8bit_Long`) to trigger state changes and direct damage on contact (`DoDamageToPlayerSameLayerOnContact`).
|
||||
* **Damage Handling**: Includes a basic damage reaction (`Sprite_CheckDamageFromPlayer`, `Sprite_DamageFlash_Long`).
|
||||
* **Vanilla ID Override**: The use of `!SPRID = $00` suggests this sprite is intended to replace or modify the behavior of a vanilla sprite with ID $00.
|
||||
469
Docs/Sprites/Enemies/Goriya.md
Normal file
469
Docs/Sprites/Enemies/Goriya.md
Normal 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.
|
||||
433
Docs/Sprites/Enemies/HelmetChuchu.md
Normal file
433
Docs/Sprites/Enemies/HelmetChuchu.md
Normal 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.
|
||||
293
Docs/Sprites/Enemies/Keese.md
Normal file
293
Docs/Sprites/Enemies/Keese.md
Normal file
@@ -0,0 +1,293 @@
|
||||
# Keese
|
||||
|
||||
## Overview
|
||||
The Keese sprite (`!SPRID = $11`) is a versatile enemy that encompasses multiple variations: Ice Keese, Fire Keese, and Vampire Bat. Its behavior is dynamically determined by its `SprSubtype`.
|
||||
|
||||
## Subtypes
|
||||
* `00` - Ice Keese
|
||||
* `01` - Fire Keese
|
||||
* `02` - Vampire Bat
|
||||
|
||||
## Sprite Properties
|
||||
* **`!SPRID`**: `$11` (Vanilla sprite ID for Keese/Vampire Bat)
|
||||
* **`!NbrTiles`**: `08`
|
||||
* **`!Harmless`**: `00`
|
||||
* **`!HVelocity`**: `00`
|
||||
* **`!Health`**: `00` (Dynamically set in `_Prep` based on subtype)
|
||||
* **`!Damage`**: `00` (Damage is handled by projectiles or specific attack logic)
|
||||
* **`!DeathAnimation`**: `00`
|
||||
* **`!ImperviousAll`**: `00`
|
||||
* **`!SmallShadow`**: `00`
|
||||
* **`!Shadow`**: `01`
|
||||
* **`!Palette`**: `00`
|
||||
* **`!Hitbox`**: `00`
|
||||
* **`!Persist`**: `00`
|
||||
* **`!Statis`**: `00`
|
||||
* **`!CollisionLayer`**: `00`
|
||||
* **`!CanFall`**: `00`
|
||||
* **`!DeflectArrow`**: `00`
|
||||
* **`!WaterSprite`**: `00`
|
||||
* **`!Blockable`**: `00`
|
||||
* **`!Prize`**: `00` (Dynamically set in `_Prep` based on subtype)
|
||||
* **`!Sound`**: `00`
|
||||
* **`!Interaction`**: `00`
|
||||
* **`!Statue`**: `00`
|
||||
* **`!DeflectProjectiles`**: `00`
|
||||
* **`!ImperviousArrow`**: `00`
|
||||
* **`!ImpervSwordHammer`**: `00`
|
||||
* **`!Boss`**: `00`
|
||||
|
||||
## Main Structure (`Sprite_Keese_Long`)
|
||||
This routine dispatches to different drawing and main logic routines based on the sprite's `SprSubtype`.
|
||||
|
||||
```asm
|
||||
Sprite_Keese_Long:
|
||||
{
|
||||
PHB : PHK : PLB
|
||||
LDA.w SprSubtype, X : CMP.b #$02 : BEQ +
|
||||
JSR Sprite_Keese_Draw
|
||||
JSL Sprite_DrawShadow
|
||||
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
|
||||
JSR Sprite_Keese_Main
|
||||
.SpriteIsNotActive
|
||||
JMP ++
|
||||
+
|
||||
JSR Sprite_VampireBat_Draw
|
||||
JSL Sprite_DrawShadow
|
||||
JSL Sprite_CheckActive : BCC ++
|
||||
JSR Sprite_VampireBat_Main
|
||||
++
|
||||
PLB
|
||||
RTL
|
||||
}
|
||||
```
|
||||
|
||||
## Initialization (`Sprite_Keese_Prep`)
|
||||
This routine initializes sprite properties upon spawning, including health and prize, based on its subtype.
|
||||
|
||||
```asm
|
||||
Sprite_Keese_Prep:
|
||||
{
|
||||
PHB : PHK : PLB
|
||||
LDA.b #$80 : STA.w SprDefl, X
|
||||
LDA.b #$30 : STA.w SprTimerC, X
|
||||
LDA.w SprSubtype, X : CMP.b #$02 : BNE +
|
||||
LDA.b #$20 : STA.w SprHealth, X
|
||||
BRA ++
|
||||
+
|
||||
LDA.b #$03 : STA.w SprNbrOAM, X
|
||||
LDA.b #$03 : STA.w SprPrize, X
|
||||
++
|
||||
PLB
|
||||
RTL
|
||||
}
|
||||
```
|
||||
|
||||
## Main Logic & State Machine (`Sprite_Keese_Main`)
|
||||
The Keese's behavior is managed by a state machine with `Keese_Idle` and `Keese_FlyAround` states.
|
||||
|
||||
* **`Keese_Idle`**: The sprite remains stationary until Link is within a certain distance, then transitions to `Keese_FlyAround`.
|
||||
* **`Keese_FlyAround`**: The Keese flies around, plays an animation, checks for collisions with Link and tiles, and can initiate an attack. It uses `GetRandomInt` for varied movement and `Sprite_ProjectSpeedTowardsPlayer` to move towards Link.
|
||||
|
||||
```asm
|
||||
Sprite_Keese_Main:
|
||||
{
|
||||
LDA.w SprAction, X
|
||||
JSL JumpTableLocal
|
||||
|
||||
dw Keese_Idle
|
||||
dw Keese_FlyAround
|
||||
|
||||
Keese_Idle:
|
||||
{
|
||||
STZ.w SprFrame, X
|
||||
; Wait til the player is nearby then fly around
|
||||
LDA.w SprTimerC, X : BEQ .move
|
||||
JSL GetDistance8bit_Long : CMP.b #$20 : BCS +
|
||||
.move
|
||||
INC.w SprAction, X
|
||||
JSL GetRandomInt
|
||||
STA.w SprTimerA, X
|
||||
+
|
||||
RTS
|
||||
}
|
||||
|
||||
Keese_FlyAround:
|
||||
{
|
||||
%PlayAnimation(0,5,8)
|
||||
JSL Sprite_CheckDamageToPlayer
|
||||
JSL Sprite_CheckDamageFromPlayer : BCC +
|
||||
JSL ForcePrizeDrop_long
|
||||
+
|
||||
JSL Sprite_DamageFlash_Long
|
||||
JSL Sprite_BounceFromTileCollision
|
||||
|
||||
JSL GetRandomInt : AND.b #$3F : BNE +
|
||||
LDA.b #$10 : STA.w SprTimerC, X
|
||||
+
|
||||
JSR Sprite_Keese_Attack
|
||||
|
||||
LDA.w SprTimerA, X : AND.b #$10 : BNE +
|
||||
LDA.b #$40
|
||||
JSL Sprite_ProjectSpeedTowardsPlayer
|
||||
+
|
||||
|
||||
JSL Sprite_SelectNewDirection
|
||||
JSL Sprite_Move
|
||||
|
||||
LDA.w SprTimerA, X : BNE +
|
||||
STZ.w SprAction, X
|
||||
+
|
||||
RTS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Attack Logic (`Sprite_Keese_Attack`)
|
||||
This routine handles the Keese's attack, which varies by subtype:
|
||||
|
||||
* **Ice Keese (`SprSubtype = 0`)**: Spawns sparkle garnish and a blind laser trail.
|
||||
* **Fire Keese (`SprSubtype = 1`)**: Utilizes `Sprite_Twinrova_FireAttack`.
|
||||
|
||||
```asm
|
||||
Sprite_Keese_Attack:
|
||||
{
|
||||
LDA.w SprTimerC, X : BEQ +
|
||||
LDA.w SprSubtype, X : BEQ ++
|
||||
JSL Sprite_Twinrova_FireAttack
|
||||
JMP +
|
||||
++
|
||||
JSL Sprite_SpawnSparkleGarnish
|
||||
JSL BlindLaser_SpawnTrailGarnish
|
||||
+
|
||||
RTS
|
||||
}
|
||||
```
|
||||
|
||||
## Drawing (`Sprite_Keese_Draw`)
|
||||
The drawing routine handles OAM allocation, animation, and palette adjustments based on the sprite's subtype. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
|
||||
|
||||
```asm
|
||||
Sprite_Keese_Draw:
|
||||
{
|
||||
JSL Sprite_PrepOamCoord
|
||||
JSL Sprite_OAM_AllocateDeferToPlayer
|
||||
|
||||
LDA.w SprFrame, X : TAY ;Animation Frame
|
||||
LDA .start_index, Y : STA $06
|
||||
LDA.w SprFlash, X : STA $08
|
||||
LDA.w SprMiscB, X : STA $09
|
||||
LDA.w SprSubtype, X : CMP.b #$01 : BNE +
|
||||
LDA.b #$0A : EOR $08 : STA $08
|
||||
+
|
||||
|
||||
PHX
|
||||
LDX .nbr_of_tiles, Y ;amount of tiles -1
|
||||
LDY.b #$00
|
||||
.nextTile
|
||||
|
||||
PHX ; Save current Tile Index?
|
||||
|
||||
TXA : CLC : ADC $06 ; Add Animation Index Offset
|
||||
|
||||
PHA ; Keep the value with animation index offset?
|
||||
|
||||
ASL A : TAX
|
||||
|
||||
REP #$20
|
||||
|
||||
LDA $00 : CLC : ADC .x_offsets, X : STA ($90), Y
|
||||
AND.w #$0100 : STA $0E
|
||||
INY
|
||||
LDA $02 : CLC : ADC .y_offsets, X : STA ($90), Y
|
||||
CLC : ADC #$0010 : CMP.w #$0100
|
||||
SEP #$20
|
||||
BCC .on_screen_y
|
||||
|
||||
LDA.b #$F0 : STA ($90), Y ;Put the sprite out of the way
|
||||
STA $0E
|
||||
.on_screen_y
|
||||
|
||||
PLX ; Pullback Animation Index Offset (without the *2 not 16bit anymore)
|
||||
INY
|
||||
|
||||
; If SprMiscA != 0, then use 4th sheet
|
||||
LDA.b $09 : BEQ +
|
||||
LDA .chr_2, X : STA ($90), Y
|
||||
JMP ++
|
||||
+
|
||||
LDA .chr, X : STA ($90), Y
|
||||
++
|
||||
INY
|
||||
LDA .properties, X : ORA $08 : STA ($90), Y
|
||||
|
||||
PHY
|
||||
|
||||
TYA : LSR #2 : TAY
|
||||
|
||||
LDA .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer
|
||||
|
||||
PLY : INY
|
||||
|
||||
PLX : DEX : BPL .nextTile
|
||||
|
||||
PLX
|
||||
|
||||
RTS
|
||||
|
||||
.start_index
|
||||
db $00, $01, $03, $04, $06, $08
|
||||
.nbr_of_tiles
|
||||
db 0, 1, 0, 1, 1, 0
|
||||
.x_offsets
|
||||
dw 0
|
||||
dw -4, 4
|
||||
dw 0
|
||||
dw -4, 4
|
||||
dw -4, 4
|
||||
dw 0
|
||||
.y_offsets
|
||||
dw 0
|
||||
dw 0, 0
|
||||
dw 0
|
||||
dw 0, 0
|
||||
dw 0, 0
|
||||
dw 0
|
||||
.chr
|
||||
db $80
|
||||
db $A2, $A2
|
||||
db $82
|
||||
db $84, $84
|
||||
db $A4, $A4
|
||||
db $A0
|
||||
.chr_2
|
||||
db $C0
|
||||
db $E2, $E2
|
||||
db $C2
|
||||
db $C4, $C4
|
||||
db $E4, $E4
|
||||
db $E0
|
||||
.properties
|
||||
db $35
|
||||
db $35, $75
|
||||
db $35
|
||||
db $35, $75
|
||||
db $35, $75
|
||||
db $35
|
||||
.sizes
|
||||
db $02
|
||||
db $02, $02
|
||||
db $02
|
||||
db $02, $02
|
||||
db $02, $02
|
||||
db $02
|
||||
}
|
||||
```
|
||||
|
||||
## Design Patterns
|
||||
* **Subtype-based Behavior**: The sprite uses `SprSubtype` to implement distinct behaviors and appearances for Ice Keese, Fire Keese, and Vampire Bat, all under a single `!SPRID`.
|
||||
* **Dynamic Property Initialization**: Health and prize values are set dynamically during the `_Prep` routine based on the sprite's subtype.
|
||||
* **Conditional Drawing and Palette**: The drawing routine adjusts the sprite's palette and potentially its graphics based on its subtype, allowing for visual differentiation.
|
||||
* **Randomized Movement**: Utilizes `GetRandomInt` to introduce variability in movement patterns, making the enemy less predictable.
|
||||
* **Projectile Attacks**: Implements different projectile attacks based on subtype, showcasing varied offensive capabilities.
|
||||
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, which is crucial for accurate sprite rendering.
|
||||
242
Docs/Sprites/Enemies/Leever.md
Normal file
242
Docs/Sprites/Enemies/Leever.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# Leever
|
||||
|
||||
## Overview
|
||||
The Leever sprite is a custom implementation that overrides the vanilla Leever behavior (`Sprite_71_Leever`). It features distinct states for being underground, emerging, attacking, and digging back down, with randomized timers controlling its transitions.
|
||||
|
||||
## Vanilla Override
|
||||
This custom Leever implementation hooks into the vanilla sprite ID $71. It uses a custom flag at `$0FFF` to determine whether to execute its custom logic (`Sprite_Leever_Long`) or fall back to the original vanilla Leever behavior (`Sprite_71_Leever`).
|
||||
|
||||
```asm
|
||||
pushpc
|
||||
|
||||
Sprite_71_Leever = $06CBA2
|
||||
|
||||
org $069365 : dw Sprite_71_Leever_Alt
|
||||
|
||||
Sprite_71_Leever_Alt:
|
||||
{
|
||||
LDA.w $0FFF : BEQ +
|
||||
JSL Sprite_Leever_Long
|
||||
JMP ++
|
||||
+
|
||||
JSR Sprite_71_Leever
|
||||
++
|
||||
RTS
|
||||
}
|
||||
assert pc() <= $06A5C0
|
||||
|
||||
pullpc
|
||||
```
|
||||
|
||||
## Sprite Properties
|
||||
Explicit sprite properties (`!SPRID`, `!Health`, etc.) are not defined within this file, suggesting it either inherits vanilla properties for sprite ID $71 or these are defined in a separate configuration file.
|
||||
|
||||
## Main Structure (`Sprite_Leever_Long`)
|
||||
This routine is the main entry point for the custom Leever logic, executed every frame. It handles bank setup, conditional drawing (skipping drawing when underground), and dispatches to the main logic if the sprite is active.
|
||||
|
||||
```asm
|
||||
Sprite_Leever_Long:
|
||||
{
|
||||
PHB : PHK : PLB
|
||||
LDA.w SprAction, X : BEQ +
|
||||
JSR Sprite_Leever_Draw
|
||||
+
|
||||
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
|
||||
JSR Sprite_Leever_Main
|
||||
.SpriteIsNotActive
|
||||
PLB
|
||||
RTL
|
||||
}
|
||||
```
|
||||
|
||||
## Movement Routine (`Sprite_Leever_Move`)
|
||||
A shared routine for handling the Leever's movement, including applying speed towards the player, moving the sprite, and bouncing off tiles.
|
||||
|
||||
```asm
|
||||
Sprite_Leever_Move:
|
||||
{
|
||||
JSL Sprite_ApplySpeedTowardsPlayer
|
||||
JSL Sprite_Move
|
||||
JSL Sprite_BounceFromTileCollision
|
||||
RTS
|
||||
}
|
||||
```
|
||||
|
||||
## Main Logic & State Machine (`Sprite_Leever_Main`)
|
||||
The Leever's core behavior is managed by a state machine with four distinct states:
|
||||
|
||||
* **`Leever_Underground`**: The Leever moves underground. After a timer (`SprTimerA`) expires, it transitions to `Leever_Emerge`.
|
||||
* **`Leever_Emerge`**: The Leever plays a backwards animation as it emerges. After a randomized timer, it transitions to `Leever_Attack`.
|
||||
* **`Leever_Attack`**: The Leever plays an attack animation, checks for damage to/from Link, and moves. After a timer, it transitions to `Leever_Dig`.
|
||||
* **`Leever_Dig`**: The Leever plays an animation as it digs back into the ground. After a randomized timer, it transitions back to `Leever_Underground`.
|
||||
|
||||
```asm
|
||||
Sprite_Leever_Main:
|
||||
{
|
||||
JSL Sprite_DamageFlash_Long
|
||||
LDA.w SprAction, X
|
||||
JSL JumpTableLocal
|
||||
|
||||
dw Leever_Underground
|
||||
dw Leever_Emerge
|
||||
dw Leever_Attack
|
||||
dw Leever_Dig
|
||||
|
||||
Leever_Underground:
|
||||
{
|
||||
LDA.w SprTimerA, X : BNE +
|
||||
LDA.b #$40 : STA.w SprTimerA, X
|
||||
INC.w SprAction, X
|
||||
+
|
||||
LDA.b #$10
|
||||
JSR Sprite_Leever_Move
|
||||
RTS
|
||||
}
|
||||
|
||||
Leever_Emerge:
|
||||
{
|
||||
%PlayAnimBackwards(3, 2, 10)
|
||||
LDA.w SprTimerA, X : BNE +
|
||||
JSL GetRandomInt
|
||||
AND.b #$3F
|
||||
ADC.b #$A0
|
||||
STA.w $0DF0,X
|
||||
INC.w SprAction, X
|
||||
STZ.w SprXSpeed, X : STZ.w SprYSpeed, X
|
||||
+
|
||||
RTS
|
||||
}
|
||||
|
||||
Leever_Attack:
|
||||
{
|
||||
%PlayAnimation(0, 1, 10)
|
||||
LDA.w SprTimerA, X : BNE +
|
||||
LDA.b #$7F : STA.w SprTimerA, X
|
||||
INC.w SprAction, X
|
||||
+
|
||||
PHX
|
||||
JSL Sprite_CheckIfRecoiling
|
||||
JSL Sprite_CheckDamageToPlayerSameLayer
|
||||
JSL Sprite_CheckDamageFromPlayer
|
||||
PLX
|
||||
LDA.b #$0C
|
||||
JSR Sprite_Leever_Move
|
||||
RTS
|
||||
}
|
||||
|
||||
Leever_Dig:
|
||||
{
|
||||
%PlayAnimation(2, 3, 10)
|
||||
LDA.w SprTimerA, X : BNE +
|
||||
JSL GetRandomInt
|
||||
AND.b #$1F
|
||||
ADC.b #$40
|
||||
STA.w $0DF0,X
|
||||
STZ.w SprAction, X
|
||||
+
|
||||
LDA.b #$08
|
||||
JSR Sprite_Leever_Move
|
||||
RTS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Drawing (`Sprite_Leever_Draw`)
|
||||
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
|
||||
|
||||
```asm
|
||||
Sprite_Leever_Draw:
|
||||
{
|
||||
JSL Sprite_PrepOamCoord
|
||||
JSL Sprite_OAM_AllocateDeferToPlayer
|
||||
|
||||
LDA $0DC0, X : CLC : ADC $0D90, X : TAY;Animation Frame
|
||||
LDA .start_index, Y : STA $06
|
||||
LDA.w SprFlash, X : STA $08
|
||||
|
||||
|
||||
PHX
|
||||
LDX .nbr_of_tiles, Y ;amount of tiles -1
|
||||
LDY.b #$00
|
||||
.nextTile
|
||||
|
||||
PHX ; Save current Tile Index?
|
||||
|
||||
TXA : CLC : ADC $06 ; Add Animation Index Offset
|
||||
|
||||
PHA ; Keep the value with animation index offset?
|
||||
|
||||
ASL A : TAX
|
||||
|
||||
REP #$20
|
||||
|
||||
LDA $00 : CLC : ADC .x_offsets, X : STA ($90), Y
|
||||
AND.w #$0100 : STA $0E
|
||||
INY
|
||||
LDA $02 : CLC : ADC .y_offsets, X : STA ($90), Y
|
||||
CLC : ADC #$0010 : CMP.w #$0100
|
||||
SEP #$20
|
||||
BCC .on_screen_y
|
||||
|
||||
LDA.b #$F0 : STA ($90), Y ;Put the sprite out of the way
|
||||
STA $0E
|
||||
.on_screen_y
|
||||
|
||||
PLX ; Pullback Animation Index Offset (without the *2 not 16bit anymore)
|
||||
INY
|
||||
LDA .chr, X : STA ($90), Y
|
||||
INY
|
||||
LDA .properties, X : ORA $08 : STA ($90), Y
|
||||
|
||||
PHY
|
||||
|
||||
TYA : LSR #2 : TAY
|
||||
|
||||
LDA .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer
|
||||
|
||||
PLY : INY
|
||||
|
||||
PLX : DEX : BPL .nextTile
|
||||
|
||||
PLX
|
||||
|
||||
RTS
|
||||
|
||||
.start_index
|
||||
db $00, $01, $02, $03
|
||||
.nbr_of_tiles
|
||||
db 0, 0, 0, 0
|
||||
.x_offsets
|
||||
dw 0
|
||||
dw 0
|
||||
dw 0
|
||||
dw 0
|
||||
.y_offsets
|
||||
dw 0
|
||||
dw 0
|
||||
dw 0
|
||||
dw 0
|
||||
.chr
|
||||
db $C4
|
||||
db $C6
|
||||
db $C2
|
||||
db $C0
|
||||
.properties
|
||||
db $33
|
||||
db $33
|
||||
db $33
|
||||
db $33
|
||||
.sizes
|
||||
db $02
|
||||
db $02
|
||||
db $02
|
||||
db $02
|
||||
}
|
||||
```
|
||||
|
||||
## Design Patterns
|
||||
* **Vanilla Override**: Explicitly overrides a vanilla sprite's behavior, demonstrating how to replace existing game logic with custom implementations.
|
||||
* **Conditional Logic**: Uses a custom flag (`$0FFF`) to dynamically switch between vanilla and custom behaviors, offering flexibility in game design.
|
||||
* **Emerging/Digging State Machine**: Implements a robust state machine to manage the Leever's characteristic emerging from and digging back into the ground, with randomized timers for unpredictable transitions.
|
||||
* **Animation Control**: Utilizes `%PlayAnimBackwards` for specific animation effects, such as the Leever emerging from the ground.
|
||||
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, essential for accurate sprite rendering.
|
||||
79
Docs/Sprites/Enemies/Octoboss.md
Normal file
79
Docs/Sprites/Enemies/Octoboss.md
Normal file
@@ -0,0 +1,79 @@
|
||||
# Octoboss Sprite Analysis
|
||||
|
||||
## Overview
|
||||
The `octoboss` sprite (ID: `Sprite_Octoboss`, which is `$3C`) is a multi-phase boss, likely an octopus-like creature. It features a unique mechanic involving a "brother" Octoboss, and can summon stalfos offspring. The fight progresses through distinct phases including emergence, movement, taunting, ascending, submerging, and a surrender sequence.
|
||||
|
||||
## Key Properties:
|
||||
* **Sprite ID:** `Sprite_Octoboss` (`$3C`)
|
||||
* **Description:** A multi-phase boss with a "brother" Octoboss, capable of summoning stalfos and engaging in various movement and attack patterns.
|
||||
* **Number of Tiles:** 11
|
||||
* **Health:** `00` (Health is managed by `ReturnTotalHealth` and `CheckForNextPhase`, combining the health of both Octobosses.)
|
||||
* **Damage:** `00` (Damage dealt to Link is likely handled by its attacks or spawned offspring.)
|
||||
* **Special Properties:**
|
||||
* `!Boss = $01` (Correctly identified as a boss.)
|
||||
* `!Shadow = 01` (Draws a shadow.)
|
||||
* `!Hitbox = 03`
|
||||
|
||||
## Custom Variables:
|
||||
* `!ConsecutiveHits = $AC`: Tracks consecutive hits on the boss.
|
||||
* `!KydrogPhase = $7A`: Tracks the current phase of the boss fight. (Note: This variable name is `KydrogPhase`, suggesting shared logic or a copy-paste from the Kydrog boss.)
|
||||
* `!WalkSpeed = 10`: Defines the boss's walking speed.
|
||||
* `BrotherSpr = $0EB0`: Stores the sprite index of the "brother" Octoboss.
|
||||
|
||||
## Main Logic Flow (`Sprite_Octoboss_Main` and `Sprite_Octoboss_Secondary`):
|
||||
The Octoboss has two primary main routines, `Sprite_Octoboss_Main` and `Sprite_Octoboss_Secondary`, which are called based on `SprMiscF, X`. This suggests different forms or behaviors for the two Octobosses.
|
||||
|
||||
**`Sprite_Octoboss_Main` Jump Table (for the primary Octoboss):**
|
||||
* **`WaitForPlayerToApproach` (0x00):** Waits for Link to reach a specific Y-coordinate (`$08C8`) to trigger activation.
|
||||
* **`Emerge` (0x01):** Emerges from the water, preventing Link's movement.
|
||||
* **`EmergedShowMessage` (0x02):** Displays an introductory message.
|
||||
* **`SpawnAndAwakeHisBrother` (0x03):** Spawns a "brother" Octoboss (`Sprite_Octoboss` with `SprMiscF = $01`) and stores its ID in `BrotherSpr`.
|
||||
* **`WaitForBrotherEmerge` (0x04):** Waits for the brother to emerge and displays a message.
|
||||
* **`SpawnPirateHats` (0x05):** Spawns "boss poof" effects for both Octobosses, changes their frames, and spawns walls using `Overworld_DrawMap16_Persist` macros.
|
||||
* **`IdlePhase` (0x06):** Idles, can spawn fireballs, and checks total health (`ReturnTotalHealth`) to potentially trigger surrender.
|
||||
* **`PickDirection` (0x07):** Picks a random direction and speed for movement.
|
||||
* **`Moving` (0x08):** Moves around, handles boundary checks, spawns splash effects, and checks total health to potentially trigger surrender.
|
||||
* **`WaitMessageBeforeSurrender` (0x09):** Displays a surrender message (`$004A`), sets the brother's action, and transitions to `RemoveHat`.
|
||||
* **`RemoveHat` (0x0A):** Removes hats from both Octobosses with "boss poof" effects, displays a message (`$004B`), and transitions to `Submerge`.
|
||||
* **`Submerge` (0x0B):** Submerges, playing an animation and spawning a splash effect.
|
||||
* **`SubmergeWaitWall` (0x0C):** Submerges further, drawing walls using `Overworld_DrawMap16_Persist` macros.
|
||||
* **`EmergeWaitGiveItem` (0x0D):** Emerges, spawns a medallion (`SpawnMedallion`), and sets an SRAM flag.
|
||||
* **`SubmergeForeverKill` (0x0E):** Submerges completely, despawns, and allows Link to move again.
|
||||
|
||||
**`Sprite_Octoboss_Secondary` Jump Table (for the secondary/brother Octoboss, when `SprMiscF, X` is set):**
|
||||
This routine largely mirrors `Sprite_Octoboss_Main` but includes specific states like `WaitDialog` and `Moving2`, suggesting slight behavioral differences.
|
||||
|
||||
## Initialization (`Sprite_Octoboss_Long` and `Sprite_Octoboss_Prep`):
|
||||
* **`Sprite_Octoboss_Long`:** Main entry point. Handles sprite initialization, including setting OAM properties, bulletproofing, frame, and palette values. It also checks for boss defeat and medallion spawning.
|
||||
* **`Sprite_Octoboss_Prep`:** Initializes `!KydrogPhase` to `00`, sets initial health (`$A0`), configures deflection, hitbox, bump damage, and calls `JSR KydrogBoss_Set_Damage` to set up the damage table. Sets initial sprite speeds and an intro timer.
|
||||
|
||||
## Health Management (`Sprite_Octoboss_CheckIfDead`, `ReturnTotalHealth`, `CheckForNextPhase`):
|
||||
* **`Sprite_Octoboss_CheckIfDead`:** Monitors `SprHealth, X`. If health is zero or negative, it triggers the boss's death sequence.
|
||||
* **`ReturnTotalHealth`:** Calculates the combined health of both Octobosses (`SprHealth, X` and `SprHealth, Y` of `BrotherSpr`).
|
||||
* **`CheckForNextPhase`:** Manages the boss's phases based on health thresholds, similar to the Kydrog boss.
|
||||
|
||||
## Offspring Spawning (`RandomStalfosOffspring`, `Sprite_Offspring_Spawn`, `Sprite_Offspring_SpawnHead`):
|
||||
* These routines are identical to those found in `kydrog_boss.asm`, spawning stalfos offspring.
|
||||
|
||||
## Attacks (`Chuchu_SpawnBlast`, `Mothula_SpawnBeams`, `Kydrog_ThrowBoneAtPlayer`):
|
||||
* **`Chuchu_SpawnBlast`:** Spawns a Chuchu blast projectile.
|
||||
* **`Mothula_SpawnBeams`:** Spawns beam projectiles.
|
||||
* **`Kydrog_ThrowBoneAtPlayer`:** Spawns a bone projectile (reused from Kydrog).
|
||||
|
||||
## Drawing (`Sprite_Octoboss_Draw`, `Sprite_Octoboss_Draw2`):
|
||||
* **`Sprite_Octoboss_Draw`:** Draws the primary Octoboss.
|
||||
* **`Sprite_Octoboss_Draw2`:** Draws the secondary/brother Octoboss.
|
||||
* Both use standard OAM allocation routines and handle complex animation frames, offsets, character data, properties, and sizes.
|
||||
|
||||
## Other Routines:
|
||||
* **`SpawnSplash`:** Spawns a splash effect.
|
||||
* **`SpawnBossPoof`:** Spawns a boss poof effect.
|
||||
* **`HandleMovingSplash`:** Handles splash effects during movement.
|
||||
* **`SpawnMedallion` / `SpawnMedallionAlt`:** Spawns a medallion.
|
||||
|
||||
## Discrepancies/Notes:
|
||||
* **Shared Variables/Code with Kydrog:** The extensive use of `!KydrogPhase`, `KydrogBoss_Set_Damage`, and `Kydrog_ThrowBoneAtPlayer` suggests significant code reuse or copy-pasting from the Kydrog boss. This could lead to unexpected interactions or make maintenance challenging if changes are made to one boss but not the other. Refactoring shared logic into common functions or macros would be beneficial.
|
||||
* **`Sprite_Octoboss_Secondary`:** The existence of a separate main routine for the "brother" Octoboss indicates that the two Octobosses might have slightly different behaviors or phases.
|
||||
|
||||
## Hardcoded Activation Trigger:
|
||||
* As noted by the user, the activation trigger for Octoboss is hardcoded. In the `WaitForPlayerToApproach` routine, the boss checks `LDA.b $20 : CMP #$08C8`. `$20` represents Link's Y position, and `$08C8` is a hardcoded Y-coordinate. This means the boss will only activate when Link reaches this specific Y-coordinate, making it difficult to relocate the boss to other overworld maps without modifying this value. This hardcoded dependency needs to be addressed for improved reusability.
|
||||
547
Docs/Sprites/Enemies/Octorok.md
Normal file
547
Docs/Sprites/Enemies/Octorok.md
Normal file
@@ -0,0 +1,547 @@
|
||||
# Octorok
|
||||
|
||||
## Overview
|
||||
The Octorok sprite (`!SPRID = $08`) is a complex enemy implementation that supports both land-based and water-based variations. It features dynamic behavior, including transformation between forms, distinct movement patterns, and projectile attacks.
|
||||
|
||||
## Sprite Properties
|
||||
* **`!SPRID`**: `$08` (Vanilla sprite ID for Octorok)
|
||||
* **`!NbrTiles`**: `05`
|
||||
* **`!Harmless`**: `00`
|
||||
* **`!HVelocity`**: `00`
|
||||
* **`!Health`**: `00` (Health is likely set dynamically or is vanilla)
|
||||
* **`!Damage`**: `00` (Damage is likely from projectiles)
|
||||
* **`!DeathAnimation`**: `00`
|
||||
* **`!ImperviousAll`**: `00`
|
||||
* **`!SmallShadow`**: `00`
|
||||
* **`!Shadow`**: `00` (Shadow is drawn conditionally in `_Long`)
|
||||
* **`!Palette`**: `00`
|
||||
* **`!Hitbox`**: `00`
|
||||
* **`!Persist`**: `00`
|
||||
* **`!Statis`**: `00`
|
||||
* **`!CollisionLayer`**: `00`
|
||||
* **`!CanFall`**: `00`
|
||||
* **`!DeflectArrow`**: `00`
|
||||
* **`!WaterSprite`**: `00`
|
||||
* **`!Blockable`**: `00`
|
||||
* **`!Prize`**: `00`
|
||||
* **`!Sound`**: `00`
|
||||
* **`!Interaction`**: `00`
|
||||
* **`!Statue`**: `00`
|
||||
* **`!DeflectProjectiles`**: `00`
|
||||
* **`!ImperviousArrow`**: `00`
|
||||
* **`!ImpervSwordHammer`**: `00`
|
||||
* **`!Boss`**: `00`
|
||||
|
||||
## Main Structure (`Sprite_Octorok_Long`)
|
||||
This routine acts as a dispatcher, determining whether to execute Land Octorok or Water Octorok logic based on `SprSubtype`.
|
||||
|
||||
```asm
|
||||
Sprite_Octorok_Long:
|
||||
{
|
||||
PHB : PHK : PLB
|
||||
JSR Sprite_Octorok_Draw
|
||||
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
|
||||
LDA.w SprSubtype, X : BEQ +
|
||||
JSL Sprite_DrawWaterRipple
|
||||
JSR Sprite_WaterOctorok_Main
|
||||
JMP ++
|
||||
+
|
||||
JSL Sprite_DrawShadow
|
||||
JSR Sprite_Octorok_Main
|
||||
++
|
||||
.SpriteIsNotActive
|
||||
PLB
|
||||
RTL
|
||||
}
|
||||
```
|
||||
|
||||
## Initialization (`Sprite_Octorok_Prep`)
|
||||
This routine is currently empty, indicating that initial setup is minimal or handled by vanilla routines.
|
||||
|
||||
## Land Octorok Main Logic (`Sprite_Octorok_Main`)
|
||||
This routine handles the behavior of a land-based Octorok, including movement and potential transformation into a Water Octorok.
|
||||
|
||||
* **Movement**: Calls `Sprite_Octorok_Move` for general movement.
|
||||
* **Transformation**: Checks the tile type the Octorok is on. If it's a water tile, the Octorok transforms into a Water Octorok by setting `SprSubtype, X` to `01`.
|
||||
* **Directional States**: Uses a jump table to manage animations for moving in different directions (Down, Up, Left, Right).
|
||||
|
||||
```asm
|
||||
Sprite_Octorok_Main:
|
||||
{
|
||||
JSR Sprite_Octorok_Move
|
||||
|
||||
; TILETYPE 08
|
||||
LDA.l $7FF9C2,X : CMP.b #$08 : BEQ .water_tile
|
||||
; TILETYPE 09
|
||||
CMP.b #$09 : BNE .not_water_tile
|
||||
.water_tile
|
||||
LDA.b #$01 : STA.w SprSubtype, X
|
||||
STZ.w SprAction, X
|
||||
STZ.w SprMiscG, X
|
||||
RTS
|
||||
.not_water_tile
|
||||
|
||||
LDA.w SprAction, X
|
||||
JSL JumpTableLocal
|
||||
|
||||
dw Octorok_MoveDown
|
||||
dw Octorok_MoveUp
|
||||
dw Octorok_MoveLeft
|
||||
dw Octorok_MoveRight
|
||||
|
||||
Octorok_MoveDown:
|
||||
{
|
||||
%PlayAnimation(0,1,10)
|
||||
RTS
|
||||
}
|
||||
|
||||
Octorok_MoveUp:
|
||||
{
|
||||
%StartOnFrame(2)
|
||||
%PlayAnimation(2,3,10)
|
||||
RTS
|
||||
}
|
||||
|
||||
Octorok_MoveLeft:
|
||||
{
|
||||
%StartOnFrame(4)
|
||||
%PlayAnimation(4,5,10)
|
||||
RTS
|
||||
}
|
||||
|
||||
Octorok_MoveRight:
|
||||
{
|
||||
%StartOnFrame(6)
|
||||
%PlayAnimation(6,7,10)
|
||||
RTS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Octorok Movement (`Sprite_Octorok_Move`)
|
||||
This shared routine handles the Octorok's general movement, damage reactions, and tile collision.
|
||||
|
||||
* **Damage & Collision**: Handles damage flash (`Sprite_DamageFlash_Long`), movement (`Sprite_Move`), and checks for damage to/from Link.
|
||||
* **Directional Logic**: Sets the sprite's action based on its direction (`SprMiscC, X`).
|
||||
* **Tile Collision**: Detects tile collisions and changes the Octorok's direction accordingly.
|
||||
* **Barrage Logic**: Contains logic related to a potential projectile barrage (`octorok_used_barrage`).
|
||||
|
||||
```asm
|
||||
Sprite_Octorok_Move:
|
||||
{
|
||||
JSL Sprite_DamageFlash_Long
|
||||
JSL Sprite_Move
|
||||
JSL Sprite_CheckDamageFromPlayer
|
||||
JSL Sprite_CheckDamageToPlayer
|
||||
|
||||
; Set the SprAction based on the direction
|
||||
LDA.w SprMiscC, X : AND.b #$03 : TAY
|
||||
LDA.w .direction, Y : STA.w SprAction, X
|
||||
|
||||
LDA.w SprMiscF, X : AND.b #$01 : BNE .octorok_used_barrage
|
||||
LDA.w SprMiscC, X : AND.b #$02 : ASL A : STA.b $00
|
||||
INC.w SprDelay, X
|
||||
LDA.w SprDelay, X
|
||||
LSR A
|
||||
LSR A
|
||||
LSR A
|
||||
AND.b #$03
|
||||
ORA.b $00
|
||||
STA.w SprGfx, X
|
||||
|
||||
LDA.w SprTimerA, X : BNE .wait
|
||||
INC.w SprMiscF,X
|
||||
|
||||
LDY.w SprType,X
|
||||
LDA.w .timer-8,Y : STA.w SprTimerA,X
|
||||
|
||||
RTS
|
||||
|
||||
.wait
|
||||
LDY.w SprMiscC, X
|
||||
|
||||
LDA.w .speed_x, Y : STA.w SprXSpeed, X
|
||||
LDA.w .speed_y, Y : STA.w SprYSpeed, X
|
||||
|
||||
JSL Sprite_CheckTileCollision
|
||||
LDA.w $0E70, X : BEQ .no_collision
|
||||
LDA.w SprMiscC,X : EOR.b #$01 : STA.w SprMiscC,X
|
||||
BRA .exit
|
||||
.no_collision
|
||||
RTS
|
||||
|
||||
.octorok_used_barrage
|
||||
STZ.w SprXSpeed, X : STZ.w SprYSpeed,X
|
||||
LDA.w SprTimerA, X : BNE Octorock_ShootEmUp
|
||||
INC.w SprMiscF, X
|
||||
LDA.w SprMiscC, X
|
||||
PHA
|
||||
JSL GetRandomInt : AND.b #$3F : ADC.b #$30 : STA.w SprTimerA, X
|
||||
AND.b #$03 : STA.w SprMiscC, X
|
||||
PLA
|
||||
CMP.w SprMiscC, X : BEQ .exit
|
||||
EOR.w SprMiscC, X : BNE .exit
|
||||
LDA.b #$08 : STA.w SprTimerB,X
|
||||
.exit
|
||||
RTS
|
||||
|
||||
.direction
|
||||
db 3, 2, 0, 1
|
||||
|
||||
.speed_x
|
||||
db 24, -24, 0, 0
|
||||
|
||||
.speed_y
|
||||
db 0, 0, 24, -24
|
||||
|
||||
.timer
|
||||
db 60, 128, 160, 128
|
||||
}
|
||||
```
|
||||
|
||||
## Octorok Projectile Logic (`Octorock_ShootEmUp`)
|
||||
This routine determines the Octorok's shooting behavior, allowing for both single-shot and four-way attacks.
|
||||
|
||||
```asm
|
||||
Octorock_ShootEmUp:
|
||||
{
|
||||
; Use SprMiscD as a flag to shoot 4 ways for awhile before going back to single shot
|
||||
|
||||
LDA.w SprMiscD, X : BEQ .continue
|
||||
LDA.w SprTimerD, X : BNE .four_ways
|
||||
LDA.b #$01 : STA.w SprMiscD, X
|
||||
.continue
|
||||
JSL GetRandomInt : AND.b #$1F : BNE .single_shot
|
||||
.four_ways
|
||||
LDA.b #$01 : STA.w SprMiscD, X
|
||||
LDA.b #$20 : STA.w SprTimerD, X
|
||||
JSR Octorok_Shoot4Ways
|
||||
RTS
|
||||
.single_shot
|
||||
JSR Octorok_ShootSingle
|
||||
RTS
|
||||
}
|
||||
```
|
||||
|
||||
## Water Octorok Main Logic (`Sprite_WaterOctorok_Main`)
|
||||
This routine governs the behavior of a water-based Octorok, including its attack patterns and states.
|
||||
|
||||
* **Attack**: Calls `Sprite_WaterOctorok_Attack`.
|
||||
* **Facing Directions**: Uses a jump table to manage animations for facing different directions (Down, Up, Left, Right) and a hidden state.
|
||||
|
||||
```asm
|
||||
Sprite_WaterOctorok_Main:
|
||||
{
|
||||
JSR Sprite_WaterOctorok_Attack
|
||||
|
||||
LDA.w SprAction, X
|
||||
JSL JumpTableLocal
|
||||
|
||||
dw WaterOctorok_FaceDown
|
||||
dw WaterOctorok_FaceUp
|
||||
dw WaterOctorok_FaceLeft
|
||||
dw WaterOctorok_FaceRight
|
||||
dw WaterOctorok_FaceHidden
|
||||
|
||||
WaterOctorok_FaceDown:
|
||||
{
|
||||
%PlayAnimation(0,1,10)
|
||||
RTS
|
||||
}
|
||||
|
||||
WaterOctorok_FaceUp:
|
||||
{
|
||||
%StartOnFrame(2)
|
||||
%PlayAnimation(2,3,10)
|
||||
RTS
|
||||
}
|
||||
|
||||
WaterOctorok_FaceLeft:
|
||||
{
|
||||
%StartOnFrame(4)
|
||||
%PlayAnimation(4,5,10)
|
||||
RTS
|
||||
}
|
||||
|
||||
WaterOctorok_FaceRight:
|
||||
{
|
||||
%StartOnFrame(6)
|
||||
%PlayAnimation(6,7,10)
|
||||
RTS
|
||||
}
|
||||
|
||||
WaterOctorok_FaceHidden:
|
||||
{
|
||||
%StartOnFrame(8)
|
||||
%PlayAnimation(8,8,10)
|
||||
RTS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Water Octorok Attack Logic (`Sprite_WaterOctorok_Attack`)
|
||||
This routine manages the Water Octorok's attack states, including hiding, emerging, attacking, and re-hiding.
|
||||
|
||||
* **States**: Uses `SprMiscG, X` as a state machine for `WaterOctorok_Hidden`, `WaterOctorok_PoppingUp`, `WaterOctorok_Attacking`, and `WaterOctorok_Hiding`.
|
||||
* **`WaterOctorok_Hidden`**: Remains hidden until Link is within a certain distance, then transitions to `WaterOctorok_PoppingUp`.
|
||||
* **`WaterOctorok_PoppingUp`**: Emerges from the water, faces Link, and then transitions to `WaterOctorok_Attacking`.
|
||||
* **`WaterOctorok_Attacking`**: Shoots a single projectile (`Octorok_ShootSingle`) after a timer, then transitions to `WaterOctorok_Hiding`.
|
||||
* **`WaterOctorok_Hiding`**: Hides back in the water and transitions to `WaterOctorok_Hidden`.
|
||||
|
||||
```asm
|
||||
Sprite_WaterOctorok_Attack:
|
||||
{
|
||||
JSL Sprite_DamageFlash_Long
|
||||
JSL Sprite_CheckDamageToPlayer
|
||||
|
||||
LDA.w SprMiscG, X
|
||||
JSL JumpTableLocal
|
||||
|
||||
dw WaterOctorok_Hidden
|
||||
dw WaterOctorok_PoppingUp
|
||||
dw WaterOctorok_Attacking
|
||||
dw WaterOctorok_Hiding
|
||||
|
||||
WaterOctorok_Hidden:
|
||||
{
|
||||
LDA.w SprTimerA, X : BEQ +
|
||||
RTS
|
||||
+
|
||||
|
||||
JSL GetDistance8bit_Long
|
||||
CMP.b #$40 : BCC .not_close_enough ; LD < 64
|
||||
INC.w SprMiscG, X
|
||||
%SetTimerA($10)
|
||||
.not_close_enough
|
||||
RTS
|
||||
}
|
||||
|
||||
WaterOctorok_PoppingUp:
|
||||
{
|
||||
JSL Sprite_CheckDamageFromPlayer
|
||||
LDA.w SprTimerA, X : BNE +
|
||||
INC.w SprMiscG, X
|
||||
%SetTimerA($20)
|
||||
JSL Sprite_DirectionToFacePlayer
|
||||
; LDA.w SprMiscC, X : AND.b #$03 : TAY
|
||||
; LDA.w Sprite_Octorok_Move_direction, Y : STA.w SprAction, X
|
||||
+
|
||||
RTS
|
||||
}
|
||||
|
||||
WaterOctorok_Attacking:
|
||||
{
|
||||
JSL Sprite_CheckDamageFromPlayer
|
||||
LDA.w SprTimerA, X : BNE +
|
||||
INC.w SprMiscG, X
|
||||
%SetTimerA($10)
|
||||
RTS
|
||||
+
|
||||
JSR Octorok_ShootSingle
|
||||
RTS
|
||||
}
|
||||
|
||||
WaterOctorok_Hiding:
|
||||
{
|
||||
LDA.w SprTimerA, X : BNE +
|
||||
LDA.b #$04 : STA.w SprAction, X
|
||||
STZ.w SprMiscG, X
|
||||
%SetTimerA($40)
|
||||
+
|
||||
RTS
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Projectile Spawning (`Octorok_ShootSingle`, `Octorok_Shoot4Ways`, `Octorok_SpawnRock`)
|
||||
These routines handle the spawning and animation of Octorok projectiles.
|
||||
|
||||
* **`Octorok_ShootSingle`**: Manages the animation and timing for shooting a single rock projectile.
|
||||
* **`Octorok_Shoot4Ways`**: Manages the animation, timing, and direction changes for shooting rock projectiles in four cardinal directions.
|
||||
* **`Octorok_SpawnRock`**: Spawns a rock projectile (sprite ID `$0C`) with specific initial offsets and speeds based on the Octorok's current direction.
|
||||
|
||||
```asm
|
||||
Octorok_ShootSingle:
|
||||
{
|
||||
LDA.w SprTimerA, X : CMP.b #$1C : BNE .bide_time
|
||||
PHA
|
||||
JSR Octorok_SpawnRock
|
||||
PLA
|
||||
.bide_time
|
||||
LSR #3
|
||||
TAY
|
||||
LDA.w .mouth_anim_step, Y : STA.w SprMiscB, X
|
||||
RTS
|
||||
|
||||
.mouth_anim_step
|
||||
db $00, $02, $02, $02
|
||||
db $01, $01, $01, $00
|
||||
db $00, $00, $00, $00
|
||||
db $02, $02, $02, $02
|
||||
db $02, $01, $01, $00
|
||||
}
|
||||
|
||||
Octorok_Shoot4Ways:
|
||||
{
|
||||
LDA.w SprTimerA, X
|
||||
PHA
|
||||
CMP.b #$80 : BCS .animate
|
||||
AND.b #$0F : BNE .delay_turn
|
||||
PHA
|
||||
LDY.w SprMiscC, X
|
||||
LDA.w .next_direction, Y : STA.w SprMiscC, X
|
||||
PLA
|
||||
.delay_turn
|
||||
CMP.b #$08 : BNE .animate
|
||||
JSR Octorok_SpawnRock
|
||||
.animate
|
||||
PLA
|
||||
LSR #4
|
||||
TAY
|
||||
LDA.w .mouth_anim_step, Y : STA.w SprMiscB, X
|
||||
RTS
|
||||
|
||||
.next_direction
|
||||
db $02, $03, $01, $00
|
||||
|
||||
.mouth_anim_step
|
||||
db $02, $02, $02, $02
|
||||
db $02, $02, $02, $02
|
||||
db $01, $00
|
||||
}
|
||||
|
||||
Octorok_SpawnRock:
|
||||
{
|
||||
LDA.b #$07 : JSL SpriteSFX_QueueSFX2WithPan
|
||||
LDA.b #$0C : JSL Sprite_SpawnDynamically : BMI .fired_a_blank
|
||||
PHX
|
||||
|
||||
LDA.w SprMiscC,X
|
||||
TAX
|
||||
|
||||
LDA.b $00 : CLC : ADC.w .offset_x_low,X : STA.w SprX,Y
|
||||
LDA.b $01 : ADC.w .offset_x_high,X : STA.w SprXH,Y
|
||||
LDA.b $02 : CLC : ADC.w .offset_y_low,X : STA.w SprY,Y
|
||||
LDA.b $03 : ADC.w .offset_y_high,X : STA.w SprYH,Y
|
||||
|
||||
LDA.w SprMiscC,Y
|
||||
TAX
|
||||
|
||||
LDA.w .rock_speed_x,X : STA.w SprXSpeed,Y
|
||||
LDA.w .rock_speed_y,X : STA.w SprYSpeed,Y
|
||||
|
||||
PLX
|
||||
.fired_a_blank
|
||||
RTS
|
||||
|
||||
.offset_x_low
|
||||
db 12, -12, 0, 0
|
||||
|
||||
.offset_x_high
|
||||
db 0, -1, 0, 0
|
||||
|
||||
.offset_y_low
|
||||
db 4, 4, 12, -12
|
||||
|
||||
.offset_y_high
|
||||
db 0, 0, 0, -1
|
||||
|
||||
.rock_speed_x
|
||||
db 44, -44, 0, 0
|
||||
|
||||
.rock_speed_y
|
||||
db 0, 0, 44, -44
|
||||
}
|
||||
```
|
||||
|
||||
## Drawing (`Sprite_Octorok_Draw`)
|
||||
The drawing routine handles OAM allocation, animation, and palette adjustments. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
|
||||
|
||||
```asm
|
||||
Sprite_Octorok_Draw:
|
||||
{
|
||||
JSL Sprite_PrepOamCoord
|
||||
JSL Sprite_OAM_AllocateDeferToPlayer
|
||||
|
||||
LDA.w SprFrame, X : TAY ;Animation Frame
|
||||
LDA .start_index, Y : STA $06
|
||||
LDA.w SprFlash : STA $08
|
||||
|
||||
PHX
|
||||
LDX .nbr_of_tiles, Y ;amount of tiles -1
|
||||
LDY.b #$00
|
||||
.nextTile
|
||||
|
||||
PHX ; Save current Tile Index?
|
||||
TXA : CLC : ADC $06 ; Add Animation Index Offset
|
||||
|
||||
PHA ; Keep the value with animation index offset?
|
||||
|
||||
ASL A : TAX
|
||||
|
||||
REP #$20
|
||||
|
||||
LDA $00 : STA ($90), Y
|
||||
AND.w #$0100 : STA $0E
|
||||
INY
|
||||
LDA $02 : STA ($90), Y
|
||||
CLC : ADC #$0010 : CMP.w #$0100
|
||||
SEP #$20
|
||||
BCC .on_screen_y
|
||||
|
||||
LDA.b #$F0 : STA ($90), Y ;Put the sprite out of the way
|
||||
STA $0E
|
||||
.on_screen_y
|
||||
|
||||
PLX ; Pullback Animation Index Offset
|
||||
INY
|
||||
LDA .chr, X : STA ($90), Y
|
||||
INY
|
||||
LDA .properties, X : ORA $08 : STA ($90), Y
|
||||
|
||||
PHY
|
||||
TYA : LSR #2 : TAY
|
||||
LDA.b #$02 : ORA $0F : STA ($92), Y ; store size in oam buffer
|
||||
PLY : INY
|
||||
PLX : DEX : BPL .nextTile
|
||||
|
||||
PLX
|
||||
|
||||
RTS
|
||||
|
||||
|
||||
; =========================================================
|
||||
|
||||
.start_index
|
||||
db $00, $01, $02, $03, $04, $05, $06, $07, $08
|
||||
.nbr_of_tiles
|
||||
db 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||
.chr
|
||||
db $80
|
||||
db $80
|
||||
db $82
|
||||
db $82
|
||||
db $A0
|
||||
db $A2
|
||||
db $A0
|
||||
db $A2
|
||||
db $AA ; Water Octorok
|
||||
.properties
|
||||
db $0D
|
||||
db $4D
|
||||
db $0D
|
||||
db $4D
|
||||
db $0D
|
||||
db $0D
|
||||
db $4D
|
||||
db $4D
|
||||
db $3D ; Water Octorok
|
||||
}
|
||||
```
|
||||
|
||||
## Design Patterns
|
||||
* **Subtype-based Behavior**: The Octorok utilizes `SprSubtype` to implement distinct behaviors for Land and Water Octoroks, including different main logic routines and conditional drawing (shadow vs. water ripple).
|
||||
* **Dynamic Transformation**: A Land Octorok can dynamically transform into a Water Octorok if it moves onto a water tile, showcasing a unique environmental interaction.
|
||||
* **Complex State Machines**: Both Land and Water Octoroks employ intricate state machines to manage their movement, attack patterns, and emerging/hiding behaviors, making them engaging enemies.
|
||||
* **Projectile Attacks**: The Octorok can perform both single-shot and four-way projectile attacks, adding variety to its offensive capabilities.
|
||||
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering and positioning.
|
||||
221
Docs/Sprites/Enemies/PolsVoice.md
Normal file
221
Docs/Sprites/Enemies/PolsVoice.md
Normal 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/Enemies/Poltergeist.md
Normal file
233
Docs/Sprites/Enemies/Poltergeist.md
Normal 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/Enemies/Puffstool.md
Normal file
287
Docs/Sprites/Enemies/Puffstool.md
Normal 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/Enemies/SeaUrchin.md
Normal file
221
Docs/Sprites/Enemies/SeaUrchin.md
Normal 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.
|
||||
313
Docs/Sprites/Enemies/ThunderGhost.md
Normal file
313
Docs/Sprites/Enemies/ThunderGhost.md
Normal 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.
|
||||
80
Docs/Sprites/Enemies/Twinrova.md
Normal file
80
Docs/Sprites/Enemies/Twinrova.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Twinrova Boss Sprite Analysis
|
||||
|
||||
## Overview
|
||||
The `twinrova` sprite (ID: `Sprite_Twinrova`, which is `$CE`) is a complex, multi-phase boss designed to override the vanilla Blind and Blind Maiden sprites. It features a dramatic transformation from Blind Maiden into Twinrova, followed by alternating phases where Twinrova switches between Koume (fire) and Kotake (ice) forms, each possessing distinct attacks and environmental interactions.
|
||||
|
||||
## Key Properties:
|
||||
* **Sprite ID:** `Sprite_Twinrova` (`$CE`)
|
||||
* **Description:** A multi-phase boss that transforms from Blind Maiden, then alternates between fire (Koume) and ice (Kotake) forms.
|
||||
* **Number of Tiles:** 6
|
||||
* **Health:** `00` (Health is managed by `Sprite_Twinrova_CheckIfDead` and phase transitions.)
|
||||
* **Damage:** `00` (Damage dealt to Link is likely handled by its attacks.)
|
||||
* **Special Properties:**
|
||||
* `!Boss = 01` (Correctly identified as a boss.)
|
||||
* `!Shadow = 01` (Draws a shadow.)
|
||||
* `!Hitbox = 03`
|
||||
* `!CollisionLayer = 01` (Checks both layers for collision.)
|
||||
|
||||
## Custom Variables/Macros:
|
||||
* `!AnimSpeed = 8`: Defines the animation speed for various states.
|
||||
* `Twinrova_Front()`, `Twinrova_Back()`, `Twinrova_Ready()`, `Twinrova_Attack()`, `Show_Koume()`, `Show_Kotake()`, `Twinrova_Hurt()`: Macros for playing specific animations, enhancing code readability.
|
||||
* `$AC`: A RAM address used to store the current attack type (Fire or Ice).
|
||||
|
||||
## Main Logic Flow (`Sprite_Twinrova_Main`):
|
||||
The boss's behavior is governed by a detailed state machine:
|
||||
* **`Twinrova_Init` (0x00):** Initial state. Displays an introductory message and transitions to `Twinrova_MoveState`.
|
||||
* **`Twinrova_MoveState` (0x01):** The core movement and phase management state. It checks `SprHealth, X` to determine if Twinrova is in Phase 1 (single entity) or Phase 2 (alternating forms).
|
||||
* **Phase 1:** Twinrova moves around, randomly spawning Fire/Ice Keese, or preparing Fire/Ice attacks.
|
||||
* **Phase 2:** Twinrova alternates between `Twinrova_KoumeMode` (fire) and `Twinrova_KotakeMode` (ice) forms.
|
||||
* **`Twinrova_MoveForwards` (0x02), `Twinrova_MoveBackwards` (0x03):** Handles movement using `Sprite_FloatTowardPlayer` and `Sprite_CheckTileCollision`.
|
||||
* **`Twinrova_PrepareAttack` (0x04):** Prepares either a Fire or Ice attack based on the value in `$AC`.
|
||||
* **`Twinrova_FireAttack` (0x05):** Executes a Fire attack. Restores floor tiles, uses `JSL Sprite_Twinrova_FireAttack` (a shared function for the actual attack), and randomly releases fireballs (`ReleaseFireballs`).
|
||||
* **`Twinrova_IceAttack` (0x06):** Executes an Ice attack using `JSL Sprite_Twinrova_IceAttack` (a shared function).
|
||||
* **`Twinrova_Hurt` (0x07):** Manages Twinrova taking damage. Plays a hurt animation and, after a timer, determines whether to dodge or retaliate with a fire or ice attack.
|
||||
* **`Twinrova_KoumeMode` (0x08):** Koume (fire) form. Spawns pit hazards (`AddPitHazard`), falling tiles (`Ganon_SpawnFallingTilesOverlord`), and fireballs (`Sprite_SpawnFireball`). Uses `RageModeMove` for dynamic movement.
|
||||
* **`Twinrova_KotakeMode` (0x09):** Kotake (ice) form. Can spawn lightning (`JSL $1DE612`) and uses `RageModeMove` for dynamic movement.
|
||||
* **`Twinrova_Dead` (0x0A):** Handles Twinrova's death sequence, killing all spawned friends and playing a hurt animation.
|
||||
|
||||
## Initialization (`Sprite_Twinrova_Prep`):
|
||||
* Checks for the presence of the Blind Maiden (`$7EF3CC = $06`). If the Maiden is present, Twinrova is killed, indicating that Twinrova spawns *from* the Blind Maiden.
|
||||
* Sets initial health to `$5A` (90 decimal).
|
||||
* Configures deflection (`SprDefl = $80`), bump damage (`SprBump = $04`), and ensures Twinrova is not invincible.
|
||||
* Configures Blind Boss startup parameters and initializes various timers and `SprMisc` variables.
|
||||
|
||||
## Death Check (`Sprite_Twinrova_CheckIfDead`):
|
||||
* Monitors `SprHealth, X`. If health is zero or negative, it triggers the boss's death sequence, setting `SprState = $04` (kill sprite boss style) and `SprAction = $0A` (Twinrova_Dead stage).
|
||||
|
||||
## Movement (`RageModeMove`, `DoRandomStrafe`, `VelocityOffsets`):
|
||||
* **`RageModeMove`:** A sophisticated routine for dynamic, floaty movement. It randomly determines a movement mode (predictive movement towards player, random strafe, random dodge, stay in place) based on timers and probabilities. It also handles evasive actions.
|
||||
* **`DoRandomStrafe`:** Generates random strafing movement.
|
||||
* **`VelocityOffsets`:** A table defining X and Y speed offsets for movement.
|
||||
|
||||
## Environmental Interactions (`Twinrova_RestoreFloorTile`, `RestoreFloorTile`, `AddPitHazard`, `Ganon_SpawnFallingTilesOverlord`):
|
||||
* **`Twinrova_RestoreFloorTile` / `RestoreFloorTile`:** Restores floor tiles, likely after they have been modified by an attack.
|
||||
* **`AddPitHazard`:** Adds a pit hazard to the floor.
|
||||
* **`Ganon_SpawnFallingTilesOverlord`:** Spawns falling tiles (reused from Ganon's mechanics).
|
||||
|
||||
## Drawing (`Sprite_Twinrova_Draw`):
|
||||
* Uses standard OAM allocation routines.
|
||||
* Handles complex animation frames, x/y offsets, character data, properties, and sizes for drawing Twinrova.
|
||||
* Utilizes 16-bit operations for precise drawing calculations.
|
||||
|
||||
## Graphics Transfer (`ApplyTwinrovaGraphics`):
|
||||
* Handles DMA transfer of graphics data (`twinrova.bin`) to VRAM.
|
||||
|
||||
## Attack Spawning (`Fireball_Configure`, `ReleaseFireballs`, `Sprite_SpawnFireKeese`, `Sprite_SpawnIceKeese`, `JSL Sprite_SpawnFireball`, `JSL $1DE612` (Sprite_SpawnLightning)):
|
||||
* Twinrova can spawn various projectiles and enemies, including fireballs, Fire Keese, Ice Keese, and lightning.
|
||||
|
||||
## Blind Maiden Integration:
|
||||
Twinrova's fight is deeply integrated with the Blind Maiden mechanics:
|
||||
* **`Follower_BasicMover`:** This routine is hooked to check if the follower is the Blind Maiden, triggering the transformation to Twinrova.
|
||||
* **`Follower_CheckBlindTrigger`:** Checks if the Blind Maiden follower is within a specific trigger area.
|
||||
* **`Blind_SpawnFromMaiden`:** This is the core routine for the transformation. It applies Twinrova graphics, sets Twinrova's initial state and position based on the Maiden's, and sets various timers and properties.
|
||||
* **`SpritePrep_Blind_PrepareBattle`:** This routine is overridden to handle Twinrova's prep or to despawn if a room flag is set.
|
||||
|
||||
## Discrepancies/Notes:
|
||||
* **Health Management:** The `!Health` property is `00`. The boss's health is managed by `Sprite_Twinrova_CheckIfDead` and phase transitions.
|
||||
* **Code Reuse:** There is extensive code reuse from other sprites/bosses (e.g., `Sprite_Twinrova_FireAttack` is also used by KydreeokHead, `Ganon_SpawnFallingTilesOverlord` in Koume mode, `Sprite_SpawnFireKeese`/`Sprite_SpawnIceKeese` in MoveState). This is an efficient practice but requires careful management to ensure thematic consistency and avoid unintended side effects.
|
||||
* **Hardcoded Addresses:** Several `JSL` calls are to hardcoded addresses (e.g., `JSL $1DE612` for lightning). These should ideally be replaced with named labels for better maintainability.
|
||||
* **Blind Maiden Overrides:** The boss heavily relies on overriding vanilla Blind Maiden behavior, which is a common ROM hacking technique but requires careful understanding of the original game's code.
|
||||
* **`TargetPositions`:** This table is defined but appears unused in the provided code.
|
||||
46
Docs/Sprites/Enemies/VampireBat.md
Normal file
46
Docs/Sprites/Enemies/VampireBat.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Vampire Bat Mini-Boss Sprite Analysis
|
||||
|
||||
## Overview
|
||||
The `vampire_bat` sprite is a mini-boss, a specialized enemy that utilizes the generic Keese sprite ID (`$11`) but differentiates its behavior through `SprSubtype = 02`. It features more complex movement patterns and attacks compared to a standard Keese, including ascending, flying around, descending, and spawning other Keese.
|
||||
|
||||
## Key Properties:
|
||||
* **Sprite ID:** `0x11` (Custom Keese Subtype 02)
|
||||
* **Description:** A mini-boss variant of the Keese, with enhanced movement and attack capabilities.
|
||||
* **Number of Tiles:** 8 (Inherited from the base Keese sprite.)
|
||||
* **Health:** `32` (decimal, set in `Sprite_Keese_Prep` based on subtype.)
|
||||
* **Damage:** `00` (Damage dealt to Link is likely handled by its attacks.)
|
||||
* **Special Properties:**
|
||||
* `!Boss = 00` (Not marked as a boss, but functions as a mini-boss/special enemy.)
|
||||
* `!Shadow = 01` (Draws a shadow.)
|
||||
|
||||
## Main Logic Flow (`Sprite_VampireBat_Main`):
|
||||
The Vampire Bat's behavior is governed by a state machine:
|
||||
* **`VampireBat_Idle` (0x00):** Waits for Link to approach within a specified distance (`$24`). Transitions to `VampireBat_Ascend`.
|
||||
* **`VampireBat_Ascend` (0x01):** Plays an ascending animation, increases its `SprHeight` to `$50`, and randomly spawns a Fire Keese (`Sprite_SpawnFireKeese`). Transitions to `VampireBat_FlyAround`.
|
||||
* **`VampireBat_FlyAround` (0x02):** Plays a flying animation, moves towards Link (`Sprite_ProjectSpeedTowardsPlayer`), and randomly selects new directions (`Sprite_SelectNewDirection`). Transitions to `VampireBat_Descend` after a timer.
|
||||
* **`VampireBat_Descend` (0x03):** Plays a descending animation, decreases its `SprHeight` until it's on the ground, and randomly uses `Sprite_Twinrova_FireAttack`. Transitions back to `VampireBat_Idle` after a timer.
|
||||
|
||||
## Initialization (from `Sprite_Keese_Prep` in `keese.asm`):
|
||||
The Vampire Bat does not have its own `_Prep` routine and relies on the generic `Sprite_Keese_Prep` routine in `keese.asm`. When `SprSubtype = 02`:
|
||||
* `SprHealth` is set to `$20` (32 decimal).
|
||||
* `SprDefl` is set to `$80`.
|
||||
* `SprTimerC` is set to `$30`.
|
||||
|
||||
## Drawing (`Sprite_VampireBat_Draw`):
|
||||
* This routine is called from `Sprite_Keese_Long` in `keese.asm` when `SprSubtype = 02`.
|
||||
* Uses standard OAM allocation routines.
|
||||
* Handles animation frames, x/y offsets, character data, properties, and sizes specific to the Vampire Bat's appearance.
|
||||
|
||||
## Attack Spawning (`Sprite_SpawnFireKeese`, `Sprite_SpawnIceKeese`):
|
||||
* **`Sprite_SpawnFireKeese`:** Spawns a Keese sprite (`$11`) with `SprSubtype = $01` (Fire Keese).
|
||||
* **`Sprite_SpawnIceKeese`:** Spawns a Keese sprite (`$11`) with `SprSubtype = $00` (Ice Keese).
|
||||
|
||||
## Interactions:
|
||||
* **Damage:** Responds to damage from Link, including flashing and bouncing from tile collisions.
|
||||
* **Attacks:** Can spawn Fire Keese and utilize `Sprite_Twinrova_FireAttack` (a shared attack function).
|
||||
|
||||
## Discrepancies/Notes:
|
||||
* **Shared Sprite ID:** The Vampire Bat efficiently reuses the generic Keese sprite ID (`$11`), with `SprSubtype = 02` serving as the primary differentiator for its unique behavior.
|
||||
* **Health Management:** Its health is configured within the generic `Sprite_Keese_Prep` routine based on its subtype.
|
||||
* **Code Reuse:** It reuses `Sprite_Twinrova_FireAttack`, demonstrating efficient code sharing across different boss/mini-boss sprites.
|
||||
* **Hardcoded Values:** Many numerical values for timers, speeds, and offsets are hardcoded. Replacing these with named constants would improve readability and maintainability.
|
||||
62
Docs/Sprites/Enemies/Wolfos.md
Normal file
62
Docs/Sprites/Enemies/Wolfos.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Wolfos Mini-Boss Sprite Analysis
|
||||
|
||||
## Overview
|
||||
The `wolfos` sprite (ID: `Sprite_Wolfos`, which is `$A9`) functions as a mini-boss or special enemy. It engages Link in combat with various movement and attack patterns. A key aspect of this sprite is its integration into a mask quest, where it can be subdued and, under specific conditions, grants Link the Wolf Mask.
|
||||
|
||||
## Key Properties:
|
||||
* **Sprite ID:** `Sprite_Wolfos` (`$A9`)
|
||||
* **Description:** A mini-boss/special enemy that fights Link and is part of a mask quest.
|
||||
* **Number of Tiles:** 4
|
||||
* **Health:** `30` (decimal)
|
||||
* **Damage:** `00` (Damage dealt to Link is likely handled by its attacks.)
|
||||
* **Special Properties:**
|
||||
* `!Boss = 00` (Not marked as a boss, but functions as a mini-boss/special enemy.)
|
||||
* `!ImperviousArrow = 01` (Impervious to arrows.)
|
||||
|
||||
## Custom Variables/Macros:
|
||||
* `WolfosDialogue = SprMiscD`: Stores a flag to control Wolfos dialogue.
|
||||
* `Wolfos_AnimateAction = SprMiscE`: Stores the current animation action.
|
||||
* `AttackForward()`, `AttackBack()`, `WalkRight()`, `WalkLeft()`, `AttackRight()`, `AttackLeft()`, `Subdued()`, `GrantMask()`, `Dismiss()`: Macros for setting `SprAction` and `Wolfos_AnimateAction`, improving code clarity.
|
||||
* `!NormalSpeed = $08`, `!AttackSpeed = $0F`: Constants for movement speeds.
|
||||
|
||||
## Main Logic Flow (`Sprite_Wolfos_Main`):
|
||||
The Wolfos's behavior is governed by a state machine:
|
||||
* **`Wolfos_AttackForward` (0x00), `Wolfos_AttackBack` (0x01), `Wolfos_WalkRight` (0x02), `Wolfos_WalkLeft` (0x03), `Wolfos_AttackRight` (0x04), `Wolfos_AttackLeft` (0x05):** These states manage the Wolfos's movement and attacks. They call `Wolfos_Move` and can randomly trigger attack actions with increased speed and temporary imperviousness.
|
||||
* **`Wolfos_Subdued` (0x06):** In this state, the Wolfos stops moving, displays dialogue (`$23`), and waits for Link to play the Song of Healing (`SongFlag = $01`). If the song is played, it transitions to `Wolfos_GrantMask`.
|
||||
* **`Wolfos_GrantMask` (0x07):** Displays the Wolfos mask graphic, shows a message (`$10F`), grants Link the `WolfMask` item, and transitions to `Wolfos_Dismiss`.
|
||||
* **`Wolfos_Dismiss` (0x08):** Stops moving, kills the sprite, and clears Link's `BRANDISH` flag.
|
||||
|
||||
## Initialization (`Sprite_Wolfos_Prep`):
|
||||
* Checks if Link is outdoors (`$1B`). If so, it further checks if the Wolfos has already been defeated (`$7EF303 = $01`). If defeated, the sprite is killed to prevent respawning.
|
||||
* Sets initial timers (`SprTimerA = $40`, `SprTimerC = $40`).
|
||||
* Configures deflection properties (`SprDefl = $82`, making it impervious to arrows).
|
||||
* Sets `SprNbrOAM = $08` and initializes `SprMiscG, X` and `SprMiscE, X` to `0`.
|
||||
|
||||
## Defeat Check (`Sprite_Wolfos_CheckIfDefeated`):
|
||||
* Checks if Link is outdoors. If `SprHealth, X` drops below `$04`, the Wolfos is considered "defeated."
|
||||
* Upon defeat, it sets `SprAction = $06` (Wolfos_Subdued), `SprState = $09` (normal state, avoiding a full death animation), refills its health to `$40`, and clears `WolfosDialogue`. This indicates pacification rather than outright killing.
|
||||
|
||||
## Movement (`Wolfos_Move`, `Wolfos_DecideAction`, `Wolfos_MoveAction_Basic`, `Wolfos_MoveAction_CirclePlayer`, `Wolfos_MoveAction_Dodge`):
|
||||
* **`Wolfos_Move`:** Handles damage flash, checks damage from player, prevents player from passing through, bounces from tile collision, checks for recoiling, moves the sprite, and calls `Wolfos_DecideAction`.
|
||||
* **`Wolfos_DecideAction`:** Determines the Wolfos's next movement action based on timers and random chance. It uses a jump table to select between `Wolfos_MoveAction_Basic`, `Wolfos_MoveAction_CirclePlayer`, and `Wolfos_MoveAction_Dodge`.
|
||||
* **`Wolfos_MoveAction_Basic`:** Basic movement towards or away from Link based on distance.
|
||||
* **`Wolfos_MoveAction_CirclePlayer`:** Attempts to circle the player.
|
||||
* **`Wolfos_MoveAction_Dodge`:** Dodges by applying speed towards the player.
|
||||
|
||||
## Animation (`Sprite_Wolfos_Animate`):
|
||||
* This routine is called from `Sprite_Wolfos_Main`.
|
||||
* It uses `Wolfos_AnimateAction` (stored in `SprMiscE, X`) to determine which animation to play.
|
||||
* It has separate animation routines for `AttackForward`, `AttackBack`, `WalkRight`, `WalkLeft`, `AttackRight`, `AttackLeft`, and `Subdued`.
|
||||
* It also spawns sparkle garnishes (`JSL Sprite_SpawnSparkleGarnish`).
|
||||
|
||||
## Drawing (`Sprite_Wolfos_Draw`):
|
||||
* Uses standard OAM allocation routines.
|
||||
* Handles animation frames, x/y offsets, character data, properties, and sizes for drawing the Wolfos.
|
||||
* Includes a special frame for the Wolf Mask (`$CC`) when granting the item.
|
||||
|
||||
## Discrepancies/Notes:
|
||||
* **Mask Quest Integration:** The Wolfos is directly integrated into a mask quest, where playing the Song of Healing subdues it and leads to receiving the Wolf Mask.
|
||||
* **Health Refill on Defeat:** When defeated, its health is refilled to `$40`, and its state is set to `Wolfos_Subdued`, indicating it's not truly killed but rather pacified.
|
||||
* **Hardcoded Values:** Many numerical values for timers, speeds, and offsets are hardcoded. Replacing these with named constants would improve readability and maintainability.
|
||||
* **`JSL Link_ReceiveItem`:** This is a standard function for giving items to Link.
|
||||
* **`JSL Sprite_SpawnSparkleGarnish`:** This is a generic garnish spawning function.
|
||||
Reference in New Issue
Block a user