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:
scawful
2025-10-03 01:52:48 -04:00
parent 8c3bf9d95b
commit aede7551a3
60 changed files with 6176 additions and 150 deletions

View File

@@ -1,78 +0,0 @@
# Ice Block System (`Sprites/Objects/ice_block.asm`)
## Overview
This file contains the logic for the pushable ice block sprite. It's a statue-like object that, when pushed by Link, slides in one direction until it collides with a wall, another object, or a switch.
## Key Functionality
- **Pushing Mechanics:** When Link makes contact, the block's `SprMiscA` register stores Link's facing direction. A timer (`SprTimerA`) is used to confirm the push, after which the block is set in motion.
- **Sliding:** Once pushed, the block moves at a constant velocity until a collision is detected.
- **Collision:** The block stops moving when it collides with a wall (detected via `SprCollision`) or another sprite (handled in `Statue_BlockSprites`). It also stops when it hits a specific switch tile.
- **State Management:** The sprite uses `SprMisc` registers to track its state, such as whether it's currently being pushed (`SprMiscC`) or is in motion (`SprMiscB`).
## Analysis & Areas for Improvement
The ice block is a classic Zelda puzzle element, but its current implementation suffers from over-sensitivity and unpredictable behavior, leading to frustration for beta testers. The goal is to achieve a "Pokémon-style" grid-based sliding puzzle feel, requiring intentional pushes and predictable movement.
### Current Problems:
- **Over-sensitivity:** The block initiates movement on simple hitbox overlap with Link, leading to accidental pushes.
- **Unpredictable Direction:** Link's slight movements or diagonal contact can result in the block sliding in unintended directions.
- **Non-grid-aligned movement:** The block does not snap to a grid, making its stopping positions feel imprecise.
### Proposed Improvements:
#### 1. Intent-Based Push Mechanics (Addressing Sensitivity & Direction)
The current `JSL Sprite_CheckDamageToPlayerSameLayer` is too broad. It will be replaced with a more robust system:
- **Directional Alignment Check:** A new subroutine (`IceBlock_CheckLinkPushAlignment`) will be implemented. This routine will verify that Link is:
- Directly adjacent to the ice block.
- Facing the ice block (e.g., if Link is facing right, the block must be to his right).
- Aligned within a small pixel tolerance (e.g., +/- 4 pixels) on the non-pushing axis (e.g., for a horizontal push, Link's Y-coordinate must be close to the block's Y-coordinate).
- **Push Confirmation Timer:** Instead of an immediate push, a short timer (e.g., 10-15 frames) will be introduced. Link must maintain the correct directional alignment and contact for this duration to confirm an intentional push. If contact or alignment is broken, the timer resets.
- **Locked Direction:** Once a push is confirmed, the block's movement direction will be locked to a single cardinal direction (horizontal or vertical) until it collides with an obstacle.
#### 2. Predictable Movement & Stopping (Grid Alignment)
- **Grid Snapping on Push:** When a push is confirmed, the ice block's coordinates (`SprX, SprY`) will be snapped to the nearest 8-pixel grid boundary before movement begins. This ensures that all slides start and end cleanly on the game's tile grid.
#### 3. Code Refactoring & Readability (General Improvements)
While implementing the above, the following existing suggestions from the previous analysis will also be applied:
- **Use `subroutine` for All Code Blocks:** Convert all major logical blocks within `Sprites/Objects/ice_block.asm` into proper `subroutine`s for better scope management and readability.
- **Replace Magic Numbers with Constants:** Define named constants for all hardcoded values (speeds, timers, tile IDs, alignment tolerances) to improve code clarity and maintainability.
- **Refactor `Sprite_ApplyPush`:** Convert the `Sprite_ApplyPush` routine to use a lookup table for setting `SprXSpeed` and `SprYSpeed` based on the determined push direction. This will make the code more compact and easier to modify.
- **Clarify `Statue_BlockSprites`:** Rename this routine to `IceBlock_HandleSpriteToSpriteCollision` and add detailed comments to explain its logic, especially concerning sprite-to-sprite collision and recoil calculations.
## Deeper Analysis: Sprite Solidity and Link's Collision
To understand why the ice block is not solid, we need to look at both the sprite's properties and Link's collision detection code.
### Sprite Property Analysis
Several flags in the sprite's RAM data structure control its physical properties. These are the most relevant for solidity:
* **`!Statue = 01` Property:** In the sprite's header, `!Statue = 01` is set. This is a high-level property that should be translated into one or more of the low-level RAM flags by the `%Set_Sprite_Properties` macro when the sprite is initialized.
* **`SprDefl` (`$0CAA`):** This is the "Sprite Deflection" register and appears to be critical.
* **Bit 2 (`$04`):** The `Core/symbols.asm` file labels this the "pushable interaction flag". Although the comment says it's "Never queried," this is highly suspect and is the most likely candidate for enabling pushable-statue physics.
* **Our Bug:** Our previous attempts to modify `Sprite_IceBlock_Prep` either cleared this register entirely (`STZ.w SprDefl, X`) or left it alone, both of which resulted in Link walking through the block. This indicates that this register's value is crucial and must be set correctly, likely by the engine's default property loading routines. Our code was interfering with this.
* **`SprHitbox` (`$0F60`):** This register contains the `I` (Ignore Collisions) bit.
* **Bit 7 (`$80`):** If this bit is set, the sprite will ignore all collisions with Link. We must ensure our code does not accidentally set this bit.
### Link's Collision Logic (Hypothesis)
The core of Link's interaction with the world is handled in `bank_07.asm`.
* **`Link_HandleCardinalCollision` (`JSR` at `#_0782C2`):** This is the key function that processes Link's movement against solid objects. A full analysis is pending a complete reading of `bank_07.asm`, but we can hypothesize its behavior.
* **Hypothesis:** This routine likely loops through all active sprites on screen. For each sprite, it checks a combination of flags (e.g., `SprDefl`, `SprHitbox`) to determine if the sprite is solid. If it is, it performs a bounding box check. If Link's next position would overlap with a solid sprite, his movement is halted. The fact that Link walks through the ice block proves that the block is not being flagged as solid correctly.
### Revised Troubleshooting Plan
Based on this deeper analysis, the plan is to work *with* the game engine's properties, not against them.
1. **Analyze `Link_HandleCardinalCollision`:** The top priority is to find and fully analyze this function in `bank_07.asm` to understand exactly which sprite flags it checks to identify a solid, pushable object.
2. **Analyze `SpritePrep_LoadProperties`:** Understand how the `%Set_Sprite_Properties` macro and the subsequent `JSL SpritePrep_LoadProperties` function translate the `!Statue = 01` property into RAM flags. This will reveal the correct default values for a statue.
3. **Correct `Sprite_IceBlock_Prep`:** With the knowledge from the steps above, write a definitive `Sprite_IceBlock_Prep` routine that correctly initializes all necessary flags (`SprDefl`, etc.) for a pushable statue, without overriding engine defaults.
4. **Verify Solidity:** Build and test to confirm Link collides with the block.
5. **Re-evaluate Push Logic:** Once the block is solid, re-evaluate the push initiation logic, which uses `JSL Sprite_CheckDamageToPlayerSameLayer`. If it still fails, we will have a solid object to debug against, which is a much better state.

View File

@@ -1,72 +0,0 @@
# Minecart System (`Sprites/Objects/minecart.asm`)
## Overview
The minecart is a highly complex and stateful sprite. It functions as a vehicle for the player, following predefined tracks, handling intersections, and persisting its location across rooms. This system is one of the most intricate custom sprites in the project.
## Key Functionality
- **State Machine:** The sprite operates on a state machine (`Minecart_WaitHoriz`, `Minecart_WaitVert`, `Minecart_Move...`) to handle waiting, player interaction, and movement.
- **Track System:** A custom system using a large block of SRAM (starting at `!MinecartTrackRoom = $0728`) allows up to 32 unique minecart "tracks" to exist in the world. The sprite saves its position and room index to this table, allowing it to reappear where the player left it.
- **Custom Collision:** The minecart does not use standard sprite collision. Instead, it reads the tile ID at its center to determine its behavior, following a complex set of rules for straight tracks, corners, intersections, and stops.
- **Player Interaction:** The player can start, stop, and change the direction of the cart at specific junctions.
## Analysis & Areas for Improvement
This is a very impressive piece of engineering for an SNES game. The code is dense and showcases advanced techniques. The main areas for improvement are in readability, data organization, and reducing code duplication.
### 1. Use a `struct` for the Track System
- **Observation:** The minecart tracking system is managed by a series of parallel arrays in SRAM (`!MinecartTrackRoom`, `!MinecartTrackX`, `!MinecartTrackY`). This is functional but can be confusing to read and maintain.
- **Suggestion:** This is a perfect use case for asar's `struct` directive. Define a structure for a single track and then create a table of those structures.
*Example:*
```asm
struct MinecartTrack
Room dw
XPos dw
YPos dw
endstruct
; In your RAM definitions
MinecartTracks table[32] of MinecartTrack
```
- **Benefit:** This makes the data structure explicit and far more readable. Accessing data becomes `MinecartTracks.Room[track_index]` instead of calculating offsets into a generic block of RAM.
### 2. Refactor Movement and Direction-Setting Code
- **Observation:**
- The routines `Minecart_MoveNorth`, `Minecart_MoveEast`, `Minecart_MoveSouth`, and `Minecart_MoveWest` contain very similar logic, differing only in the speed value and axis.
- The direction-setting routines (`Minecart_SetDirectionNorth`, etc.) also contain significant duplication.
- **Suggestion:**
- Create a single `Minecart_Move` subroutine that takes a direction as an argument. It could use a lookup table to fetch the correct speed and axis.
- Create a single `Minecart_SetDirection` subroutine that takes a direction argument and sets the `SprMiscB`, `!MinecartDirection`, and animation state from lookup tables.
- **Benefit:** Massively reduces code duplication, making the logic easier to debug and modify. A change to movement logic would only need to be made in one place.
### 3. Use `subroutine` for All Code Blocks
- **Observation:** The file uses a mix of labels and macros (`%GotoAction`) for its state machine. The main logic is a large jump table.
- **Suggestion:** Convert all logical blocks (`Minecart_WaitHoriz`, `HandleTileDirections`, `CheckForCornerTiles`, etc.) into proper `subroutine`s.
- **Benefit:** Enforces local scoping for labels, improves readability, and makes the code's structure much clearer.
### 4. Replace Magic Numbers with Constants
- **Observation:** The code is full of hardcoded values for directions, speeds, tile IDs, and sprite states.
- **Suggestion:** Define constants for all of these.
*Example:*
```asm
!CART_SPEED = 20
!TILE_TRACK_CORNER_TL = $B2
!DIR_NORTH = 0
!DIR_EAST = 1
; etc.
```
- **Benefit:** This is crucial for a system this complex. It makes the code self-documenting and dramatically reduces the chance of introducing bugs from typos in numerical values.
### 5. Add High-Level Comments
- **Observation:** The code has some comments, but they mostly describe *what* a single line is doing.
- **Suggestion:** Add block comments at the top of major subroutines (especially `Sprite_Minecart_Prep` and `HandleTileDirections`) explaining the overall *purpose* and *logic* of the code block. Explain the caching/tracking system in detail.
- **Benefit:** This is essential for future maintainability, especially for a system this intricate. It will make it possible for you or others to understand the code after being away from it for a while.

View File

@@ -0,0 +1,225 @@
# Bean Vendor
## Overview
The Bean Vendor is an NPC (Non-Player Character) sprite designed for player interaction, primarily through dialogue. It features a simple state machine to manage its idle and talking behaviors.
## Sprite Properties
* **`!SPRID`**: `$00` (Vanilla sprite ID, likely overridden)
* **`!NbrTiles`**: `08`
* **`!Harmless`**: `01` (Indicates the sprite is harmless to Link)
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Indicates the sprite is impervious to all attacks)
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `01`
* **`!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_BeanVendor_Long`)
This routine is the main entry point for the Bean Vendor, executed every frame. It handles drawing, shadow rendering, and dispatches to the main logic if the sprite is active.
```asm
Sprite_BeanVendor_Long:
{
PHB : PHK : PLB
JSR Sprite_BeanVendor_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_BeanVendor_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_BeanVendor_Prep`)
This routine runs once when the Bean Vendor is spawned. It initializes `SprDefl, X`, `SprTimerC, X`, `SprNbrOAM, X`, and `SprPrize, X`.
```asm
Sprite_BeanVendor_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.b #$30 : STA.w SprTimerC, X
LDA.b #$03 : STA.w SprNbrOAM, X
LDA.b #$03 : STA.w SprPrize, X
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_BeanVendor_Main`)
The Bean Vendor's core behavior is managed by a state machine with `BeanVendor_Idle` and `BeanVendor_Talk` states.
* **`BeanVendor_Idle`**: The vendor plays an idle animation. When Link is nearby (`GetDistance8bit_Long`), it transitions to the `BeanVendor_Talk` state.
* **`BeanVendor_Talk`**: The vendor plays a talking animation and displays a message using `JSL Interface_PrepAndDisplayMessage`. Once the message is dismissed, it transitions back to the `BeanVendor_Idle` state.
```asm
Sprite_BeanVendor_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw BeanVendor_Idle
dw BeanVendor_Talk
BeanVendor_Idle:
{
%PlayAnimation(0,1,15)
JSL GetDistance8bit_Long : CMP.b #$20 : BCS +
INC.w SprAction, X
+
RTS
}
BeanVendor_Talk:
{
%PlayAnimation(2,3,8)
JSL Interface_PrepAndDisplayMessage : BCC +
STZ.w SprAction, X
+
RTS
}
}
```
## Drawing (`Sprite_BeanVendor_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_BeanVendor_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
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
* **NPC Interaction**: The sprite is designed to engage with the player through dialogue, triggered by proximity.
* **State Machine**: Employs a simple state machine to manage its `Idle` and `Talk` behaviors, ensuring appropriate animations and actions based on player interaction.
* **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.

View File

@@ -0,0 +1,225 @@
# Bottle Vendor
## Overview
The Bottle Vendor is an NPC (Non-Player Character) sprite designed for player interaction, primarily through dialogue. It features a simple state machine to manage its idle and talking behaviors, very similar in structure to the Bean Vendor.
## Sprite Properties
* **`!SPRID`**: `$00` (Vanilla sprite ID, likely overridden)
* **`!NbrTiles`**: `08`
* **`!Harmless`**: `01` (Indicates the sprite is harmless to Link)
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Indicates the sprite is impervious to all attacks)
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `01`
* **`!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_BottleVendor_Long`)
This routine is the main entry point for the Bottle Vendor, executed every frame. It handles drawing, shadow rendering, and dispatches to the main logic if the sprite is active.
```asm
Sprite_BottleVendor_Long:
{
PHB : PHK : PLB
JSR Sprite_BottleVendor_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_BottleVendor_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_BottleVendor_Prep`)
This routine runs once when the Bottle Vendor is spawned. It initializes `SprDefl, X`, `SprTimerC, X`, `SprNbrOAM, X`, and `SprPrize, X`.
```asm
Sprite_BottleVendor_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.b #$30 : STA.w SprTimerC, X
LDA.b #$03 : STA.w SprNbrOAM, X
LDA.b #$03 : STA.w SprPrize, X
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_BottleVendor_Main`)
The Bottle Vendor's core behavior is managed by a state machine with `BottleVendor_Idle` and `BottleVendor_Talk` states.
* **`BottleVendor_Idle`**: The vendor plays an idle animation. When Link is nearby (`GetDistance8bit_Long`), it transitions to the `BottleVendor_Talk` state.
* **`BottleVendor_Talk`**: The vendor plays a talking animation and displays a message using `JSL Interface_PrepAndDisplayMessage`. Once the message is dismissed, it transitions back to the `BottleVendor_Idle` state.
```asm
Sprite_BottleVendor_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw BottleVendor_Idle
dw BottleVendor_Talk
BottleVendor_Idle:
{
%PlayAnimation(0,1,15)
JSL GetDistance8bit_Long : CMP.b #$20 : BCS +
INC.w SprAction, X
+
RTS
}
BottleVendor_Talk:
{
%PlayAnimation(2,3,8)
JSL Interface_PrepAndDisplayMessage : BCC +
STZ.w SprAction, X
+
RTS
}
}
```
## Drawing (`Sprite_BottleVendor_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_BottleVendor_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
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
* **NPC Interaction**: The sprite is designed to engage with the player through dialogue, triggered by proximity.
* **State Machine**: Employs a simple state machine to manage its `Idle` and `Talk` behaviors, ensuring appropriate animations and actions based on player interaction.
* **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.

View File

@@ -0,0 +1,86 @@
# Bug Net Kid (Sick Kid)
## Overview
The Bug Net Kid, also referred to as the Sick Kid, is an NPC sprite that plays a role in a quest involving the "Song of Healing" and the acquisition of the Boots (referred to as Bug Net in the comments). This sprite is implemented by overriding vanilla game code to introduce custom interactions and progression.
## Vanilla Overrides
This sprite extensively uses `pushpc`/`pullpc` blocks and `org` directives to inject custom logic into existing vanilla routines. This approach allows for modifying the behavior of a vanilla NPC without creating a new sprite ID.
* **`org $068D7F`**: Overrides the vanilla `SpritePrep_SickKid` routine.
* **`org $06B962`**: Overrides a routine related to the kid's resting state (`BugNetKid_Resting`).
* **`org $06B9C6`**: Overrides a routine responsible for granting the item (`BugNetKid_GrantBugNet`).
## `SickKid_CheckForSongOfHealing`
This routine is a core component of the Bug Net Kid's logic. It checks if the "Song of Healing" has been played by examining a `SongFlag` (likely a WRAM address like `$7E001F`). If the song has been played, it updates internal sprite state variables (`$0D80, X`, `$02E4`) and clears the `SongFlag`.
```asm
SickKid_CheckForSongOfHealing:
{
LDA.b SongFlag : CMP.b #$01 : BNE .no_song
INC $0D80, X
INC $02E4
STZ.b SongFlag
.no_song
RTL
}
```
## `SpritePrep_SickKid` (Initialization)
This routine is executed when the Sick Kid sprite is initialized. It checks an SRAM flag (`$7EF355`) to determine if Link has already obtained the Boots. If so, it sets `$0D80, X` to `$03`. It also increments `SprBulletproof, X`, making the kid invulnerable to attacks.
```asm
SpritePrep_SickKid:
{
LDA.l $7EF355 : BEQ .no_boots
LDA.b #$03 : STA $0D80, X
.no_boots
INC.w SprBulletproof, X
RTS
}
```
## `BugNetKid_Resting` (Main Logic)
This routine controls the kid's behavior when not actively granting an item. It checks for player preoccupation and damage, and crucially, calls `SickKid_CheckForSongOfHealing`. If Link has not yet received the Boots, it displays a solicited message to the player.
```asm
BugNetKid_Resting:
{
JSL Sprite_CheckIfPlayerPreoccupied : BCS .dont_awaken
JSR Sprite_CheckDamageToPlayer_same_layer : BCC .dont_awaken
JSL SickKid_CheckForSongOfHealing
LDA.l $7EF355
CMP.b #$01 : BCC .no_boots
.dont_awaken
RTS
.no_boots
LDA.b #$04
LDY.b #$01
JSL Sprite_ShowSolicitedMessageIfPlayerFacing
RTS
}
```
## `BugNetKid_GrantBugNet` (Item Granting)
This routine is responsible for giving Link the Boots. It sets the item ID (`LDY.b #$4B`), clears a flag (`$02E9`), calls `JSL Link_ReceiveItem` to add the item to Link's inventory, and updates internal sprite state variables (`$0D80, X`, `$02E4`).
```asm
BugNetKid_GrantBugNet:
{
; Give Link the Boots
LDY.b #$4B
STZ $02E9
PHX
JSL Link_ReceiveItem
PLX
INC $0D80, X
STZ $02E4
RTS
}
```
## Design Patterns
* **Vanilla Override**: This sprite is a prime example of overriding vanilla game code to introduce new NPC interactions and quest elements without creating entirely new sprite definitions.
* **Quest/Item Gating**: The sprite's behavior and the ability to receive the Boots are directly tied to specific game progression flags, such as the `SongFlag` and the SRAM flag for the Boots (`$7EF355`).
* **NPC Interaction**: The sprite interacts with the player by displaying messages and granting a key item, driving forward a specific questline.
* **Global Flags and SRAM Usage**: Utilizes global WRAM flags (`$02E4`, `SongFlag`) and SRAM (`$7EF355`) to maintain and track the state of the quest across game sessions.

View File

@@ -0,0 +1,307 @@
# Deku Scrub
## Overview
The Deku Scrub sprite is a highly versatile NPC implementation capable of representing multiple distinct characters, including a Withered Deku Scrub, Deku Butler, and Deku Princess. Its behavior is intricately tied to game progression, player actions, and specific in-game locations.
## Sprite Properties
* **`!SPRID`**: `Sprite_DekuScrubNPCs` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `06`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!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`
## Main Structure (`Sprite_DekuScrub_Long`)
This routine is the main entry point for the Deku Scrub, executed every frame. It handles drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_DekuScrub_Long:
{
PHB : PHK : PLB
JSR Sprite_DekuScrub_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_DekuScrub_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_DekuScrub_Prep`)
This routine runs once when the Deku Scrub is spawned. It sets `SprDefl, X` and then determines the initial `SprAction, X` based on the current `AreaIndex`, `SprSubtype, X`, and whether the Deku Mask has been obtained (`$7EF301`). It also checks if Tail Palace is cleared (`Crystals`) to potentially set `SprState, X` to `0`.
```asm
Sprite_DekuScrub_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
; Peacetime Deku Scrub NPCs
LDA.b AreaIndex : CMP.b #$2E : BNE .check_next
; Deku Butler
LDA.b #$07 : STA.w SprAction, X
JMP +
.check_next
CMP.b #$2F : BNE .continue
LDA.b #$08 : STA.w SprAction, X
JMP +
.continue
LDA.w SprSubtype, X : CMP.b #$01 : BEQ .DekuButler
CMP.b #$02 : BEQ .DekuPrincess
LDA.l $7EF301 : BEQ +
LDA.b #$04 : STA.w SprAction, X
JMP +
.DekuButler
LDA.b #$05 : STA.w SprAction, X
JMP ++
.DekuPrincess
LDA.b #$06 : STA.w SprAction, X
++
; Check if tail palace is cleared
LDA.l Crystals : AND #$10 : BEQ +
STZ.w SprState, X
+
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_DekuScrub_Main`)
The Deku Scrub's core behavior is managed by a complex state machine with several states, many of which are named in Spanish:
* **`EstadoInactivo` (Inactive State)**: The scrub plays an idle animation, prevents player passage, and transitions to `QuiereCuracion` upon player interaction.
* **`QuiereCuracion` (Wants Healing)**: Plays an animation and checks if the "Song of Healing" (`SongFlag`) has been played. If so, it clears the flag, sets a timer, and transitions to `DarMascara`.
* **`DarMascara` (Give Mask)**: Plays an animation, displays a message after a timer, and then transitions to `Regalo`.
* **`Regalo` (Gift)**: After a timer, grants Link the Deku Mask (`$11`) and updates the Deku Mask flag (`$7EF301`), then transitions to `Withered`.
* **`Withered`**: Plays a withered animation.
* **`DekuButler`**: Plays a specific animation, prevents player passage, and displays a message.
* **`DekuPrincess`**: Plays a specific animation, prevents player passage, and displays a message.
* **`DekuButler_Peacetime`**: Plays a specific animation, prevents player passage, and displays a message. If the message is dismissed, it sets `MapIcon` to `$02`.
* **`DekuPrinces_Peacetime`**: Plays a specific animation, prevents player passage, and displays a message. If the message is dismissed, it sets `MapIcon` to `$02`.
```asm
Sprite_DekuScrub_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw EstadoInactivo
dw QuiereCuracion
dw DarMascara
dw Regalo
dw Withered
dw DekuButler
dw DekuPrincess
dw DekuButler_Peacetime
dw DekuPrinces_Peacetime
EstadoInactivo:
{
%PlayAnimation(0, 1, 16)
JSL Sprite_PlayerCantPassThrough
%ShowSolicitedMessage($140) : BCC .no_hablaba
%GotoAction(1)
.no_hablaba
RTS
}
QuiereCuracion:
{
%PlayAnimation(0, 1, 16)
LDA.b SongFlag : CMP.b #$01 : BNE .ninguna_cancion
STZ.b SongFlag
LDA.b #$C0 : STA.w SprTimerD, X
%GotoAction(2)
.ninguna_cancion
RTS
}
DarMascara:
{
%PlayAnimation(0, 1, 16)
LDA.w SprTimerD, X : BNE +
%ShowUnconditionalMessage($141)
LDA.b #$C0 : STA.w SprTimerD, X
%GotoAction(3)
+
RTS
}
Regalo:
{
LDA.w SprTimerD, X : BNE +
LDY #$11 : STZ $02E9 ; Give the Deku Mask
JSL Link_ReceiveItem
LDA.b #$01 : STA.l $7EF301
%GotoAction(4)
+
RTS
}
Withered:
{
%PlayAnimation(2, 2, 10)
RTS
}
DekuButler:
{
%PlayAnimation(3, 3, 10)
JSL Sprite_PlayerCantPassThrough
%ShowSolicitedMessage($080)
RTS
}
DekuPrincess:
{
%PlayAnimation(4, 4, 10)
JSL Sprite_PlayerCantPassThrough
%ShowSolicitedMessage($0C3)
RTS
}
DekuButler_Peacetime:
{
%StartOnFrame(3)
%PlayAnimation(3, 3, 10)
JSL Sprite_PlayerCantPassThrough
%ShowSolicitedMessage($1B9) : BCC +
LDA.b #$02 : STA.l MapIcon
+
RTS
}
DekuPrinces_Peacetime:
{
%StartOnFrame(4)
%PlayAnimation(4, 4, 10)
JSL Sprite_PlayerCantPassThrough
%ShowSolicitedMessage($1BA) : BCC +
LDA.b #$02 : STA.l MapIcon
+
RTS
}
}
```
## Drawing (`Sprite_DekuScrub_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_DekuScrub_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 .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer
PLY : INY
PLX : DEX : BPL .nextTile
PLX
RTS
.start_index
db $00, $04, $08, $0C, $10
.nbr_of_tiles
db 3, 3, 3, 3, 3
.x_offsets
dw 4, 4, -4, -4
dw 4, -4, -4, 4
dw -8, -8, 8, 8
dw -4, 4, -4, 4
dw -4, -4, 4, 4
.y_offsets
dw 4, -4, -4, 4
dw 4, 4, -4, -4
dw 4, -12, -12, 4
dw -12, -12, 4, 4
dw 4, -12, 4, -12
.chr
db $2E, $0E, $0E, $2E
db $2C, $2C, $0C, $0C
db $20, $00, $02, $22
db $04, $05, $24, $25
db $27, $07, $27, $07
.properties
db $3B, $7B, $3B, $7B
db $3B, $7B, $3B, $7B
db $3B, $3B, $3B, $3B
db $3B, $3B, $3B, $3B
db $3B, $3B, $7B, $7B
.sizes
db $02, $02, $02, $02
db $02, $02, $02, $02
db $02, $02, $02, $02
db $02, $02, $02, $02
db $02, $02, $02, $02
}
```
## Design Patterns
* **Multi-Character NPC**: A single sprite definition is used to represent multiple distinct NPC characters (Withered Deku Scrub, Deku Butler, Deku Princess), with their specific roles determined by `SprSubtype` and `AreaIndex`.
* **Quest Progression Integration**: The sprite's behavior is deeply integrated with various quest elements, checking for specific items (Deku Mask), songs (Song of Healing), and cleared dungeons (Tail Palace) to determine its current state and interactions.
* **Conditional Behavior**: Extensive use of conditional logic based on `AreaIndex`, `SprSubtype`, and global game state flags allows for dynamic changes in the NPC's role, dialogue, and actions.
* **NPC Interaction**: Provides rich interaction with the player through dialogue (`%ShowSolicitedMessage`, `%ShowUnconditionalMessage`) and the granting of key items (`Link_ReceiveItem`).
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make the NPC a solid object that Link cannot walk through.
* **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.

204
Docs/Sprites/NPCs/EonOwl.md Normal file
View File

@@ -0,0 +1,204 @@
# Eon Owl / Kaepora Gaebora
## Overview
This sprite is a sophisticated NPC implementation that serves as both the "Eon Owl" and "Kaepora Gaebora" (a character from The Legend of Zelda: Ocarina of Time). Its appearance, behavior, and interactions are highly conditional, depending on the player's location and various game progression flags.
## Sprite Properties
* **`!SPRID`**: `Sprite_EonOwl` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `03`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!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`**: `01` (Deflects all projectiles)
* **`!ImperviousArrow`**: `01` (Impervious to arrows)
* **`!ImpervSwordHammer`**: `01` (Impervious to sword and hammer attacks)
* **`!Boss`**: `00`
## Main Structure (`Sprite_EonOwl_Long`)
This routine serves as a dispatcher for the Eon Owl and Kaepora Gaebora, and includes logic for conditional despawning based on game state.
* **Kaepora Gaebora Logic**: If the `AreaIndex` is `$0E` (Hall of Secrets map) and certain conditions regarding collected crystals (`$7EF37A`) and the player's possession of the "Song of Soaring" (`$7EF34C`) are met, the sprite is identified as Kaepora Gaebora (`SprSubtype, X` set to `01`) and `Sprite_KaeporaGaebora_Draw` is called.
* **Eon Owl Logic**: Otherwise, `Sprite_EonOwl_Draw` is called.
* **Despawning**: If conditions for either character are not met, the sprite despawns (`STZ.w SprState, X`).
```asm
Sprite_EonOwl_Long:
{
PHB : PHK : PLB
; If it is not the Hall of Secrets map
LDA.b $8A : CMP.b #$0E : BNE .NotGaebora
; If the map doesn't have the 6 crystals
LDA.l $7EF37A : CMP.b #$77 : BNE .Despawn
; If the player has the Song of Soaring, despawn
LDA.l $7EF34C : CMP.b #$03 : BCS .Despawn
LDA.b #$01 : STA.w SprSubtype, X
JSR Sprite_KaeporaGaebora_Draw
JMP .HandleSprite
.NotGaebora
JSR Sprite_EonOwl_Draw
.HandleSprite
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_EonOwl_Main
.SpriteIsNotActive
PLB
RTL
.Despawn
STZ.w SprState, X
PLB
RTL
}
```
## Initialization (`Sprite_EonOwl_Prep`)
This routine initializes the sprite upon spawning, including setting its hitbox and handling conditional despawning for the intro sequence.
* **Hitbox**: `SprHitbox, X` is set to `0`.
* **Kaepora Gaebora Initialization**: If `AreaIndex` is `$0E`, `SprTimerA, X` is set to `$20` and `SprAction, X` to `$03`.
* **Intro Despawn**: If `AreaIndex` is `$50` (Intro Map) and Link already has the Sword, the sprite despawns.
```asm
Sprite_EonOwl_Prep:
{
PHB : PHK : PLB
STZ.w SprHitbox, X
LDA.b $8A : CMP.b #$0E : BNE .NotGaebora
LDA.b #$20 : STA.w SprTimerA, X
LDA.b #$03 : STA.w SprAction, X
.NotGaebora
LDA.w AreaIndex : CMP.b #$50 : BNE .not_intro
; If Map 0x50, don't spawn after getting sword
LDA.l Sword : CMP.b #$01 : BCC .continue
STZ.w SprState, X
.continue
.not_intro
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_EonOwl_Main`)
This routine manages the various states and behaviors of both the Eon Owl and Kaepora Gaebora.
* **`EonOwl_Idle`**: The Eon Owl plays an idle animation and transitions to `EonOwl_IntroDialogue` when Link is nearby.
* **`EonOwl_IntroDialogue`**: Displays an introductory message and then transitions to `EonOwl_FlyingAway`.
* **`EonOwl_FlyingAway`**: The Eon Owl plays a flying animation, moves upwards, and despawns after a timer.
* **`KaeporaGaebora`**: Kaepora Gaebora plays an idle animation and, if Link is at a certain distance and a timer allows, displays a message and transitions to `KaeporaGaebora_Respond`.
* **`KaeporaGaebora_Respond`**: Processes the player's dialogue choice. If the player declines, it transitions back to `KaeporaGaebora`. If the player accepts, it transitions to `KaeporaGaebora_FlyAway` and grants the "Song of Soaring" (`$7EF34C`).
* **`KaeporaGaebora_FlyAway`**: Kaepora Gaebora flies upwards and despawns after a timer.
```asm
Sprite_EonOwl_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw EonOwl_Idle
dw EonOwl_IntroDialogue
dw EonOwl_FlyingAway
dw KaeporaGaebora
dw KaeporaGaebora_Respond
dw KaeporaGaebora_FlyAway
EonOwl_Idle:
{
%PlayAnimation(0,1,16)
JSL GetDistance8bit_Long : CMP #$28 : BCS .not_too_close
%GotoAction(1)
.not_too_close
RTS
}
EonOwl_IntroDialogue:
{
%PlayAnimation(0,1,16)
%ShowUnconditionalMessage($00E6)
LDA.b #$C0 : STA.w SprTimerA, X
%GotoAction(2)
RTS
}
EonOwl_FlyingAway:
{
%PlayAnimation(2,3,10)
LDA.b #$F8 : STA.w SprYSpeed, X
JSL Sprite_Move
LDA.w SprTimerA, X : CMP.b #$80 : BNE +
LDA.b #$40 : STA.w SprXSpeed, X
+
LDA.w SprTimerA, X : BNE .not_done
STZ.w SprState, X
.not_done
RTS
}
; 0x03 - Kaepora Gaebora
KaeporaGaebora:
{
%PlayAnimation(0,0,1)
JSL GetDistance8bit_Long : CMP.b #$50 : BCC .not_ready
LDA.w SprTimerA, X : BNE .not_ready
%ShowUnconditionalMessage($146)
%GotoAction(4)
.not_ready
RTS
}
KaeporaGaebora_Respond:
{
LDA $1CE8 : BNE .player_said_no
%GotoAction(3)
RTS
.player_said_no
%GotoAction(5)
LDA.b #$60 : STA.w SprTimerA, X
LDA.b #$03 : STA.l $7EF34C
RTS
}
FlyAwaySpeed = 10
KaeporaGaebora_FlyAway:
{
LDA.b #-FlyAwaySpeed : STA.w SprYSpeed, X
JSL Sprite_Move
LDA.w SprTimerA, X : BNE .not_ready
STZ.w SprState, X
.not_ready
RTS
}
}
```
## Drawing (`Sprite_EonOwl_Draw` and `Sprite_KaeporaGaebora_Draw`)
Both drawing routines handle OAM allocation and animation, using `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each has its own specific OAM data for rendering the respective character.
## Design Patterns
* **Multi-Character NPC**: A single sprite definition dynamically represents two distinct NPCs (Eon Owl and Kaepora Gaebora) based on `AreaIndex` and game state, showcasing efficient sprite reuse.
* **Conditional Spawning/Despawning**: The sprite's visibility and existence are tightly controlled by game progression, including collected items (crystals, sword) and player inventory (Song of Soaring), making it appear only when relevant to the narrative.
* **Quest Progression Integration**: The sprite's dialogue and actions are directly linked to specific quest milestones, guiding the player through the game's story.
* **NPC Interaction with Dialogue Choices**: Kaepora Gaebora presents the player with dialogue options, and the player's choice influences game outcomes, such as receiving the "Song of Soaring."
* **Flying Behavior**: Implements realistic flying animations and movement, including flying away sequences with controlled speed and timers.
* **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.

View File

@@ -0,0 +1,214 @@
# Eon Zora
## Overview
The Eon Zora is an NPC (Non-Player Character) sprite found in the Eon Abyss. Its behavior is characterized by random movement and context-sensitive dialogue that changes based on Link's current location within the game world.
## Sprite Properties
Explicit sprite properties (`!SPRID`, `!NbrTiles`, etc.) are not defined within this file. It is assumed that these properties are either inherited from a vanilla sprite ID or defined in a separate configuration file, as this file focuses on the sprite's behavior and drawing.
## Main Logic (`Sprite_EonZora_Main`)
This routine is the main entry point for the Eon Zora, executed every frame. It orchestrates the Zora's dialogue, movement, and animation.
* **Dialogue**: Calls `EonZora_HandleDialogue` to manage interactions with the player.
* **Movement**: Calls `EonZora_Walk` for random movement, followed by `JSL Sprite_Move` and `JSL Sprite_BounceFromTileCollision` for physical movement and collision handling.
* **Directional Animations**: Uses a jump table to play specific animations based on the Zora's current direction (Forward, Left, Right, Back).
```asm
Sprite_EonZora_Main:
{
JSR EonZora_HandleDialogue
JSR EonZora_Walk
JSL Sprite_Move
JSL Sprite_BounceFromTileCollision
LDA.w SprAction, X
JSL JumpTableLocal
dw EonZora_Forward
dw EonZora_Left
dw EonZora_Right
dw EonZora_Back
EonZora_Forward:
%PlayAnimation(0,1,10)
RTS
EonZora_Left:
%PlayAnimation(2,3,10)
RTS
EonZora_Right:
%PlayAnimation(4,5,10)
RTS
EonZora_Back:
%PlayAnimation(6,7,10)
RTS
}
```
## Movement Routine (`EonZora_Walk`)
This routine controls the Eon Zora's random walking behavior. It uses a timer (`SprTimerA, X`) to periodically select a new random direction and update the sprite's `SprXSpeed, X` and `SprYSpeed, X`.
```asm
EonZora_Walk:
{
LDA.w SprTimerA, X : BNE +
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X : TAY
LDA.w .speed_x, Y : STA.w SprXSpeed, X
LDA.w .speed_y, Y : STA.w SprYSpeed, X
LDA.b #$6A : STA.w SprTimerA, X
+
RTS
.speed_x
db 0, -4, 4, 0
.speed_y
db 4, 0, 0, -4
}
```
## Dialogue Handling (`EonZora_HandleDialogue`)
This routine manages the Eon Zora's dialogue, which is context-sensitive based on Link's current `AreaIndex`. It checks for specific `AreaIndex` values to display tailored messages. If no specific area matches, a default message is displayed, and interacting with it can randomly set the `FOUNDRINGS` global variable.
```asm
EonZora_HandleDialogue:
{
LDA.w AreaIndex : CMP.b #$63 : BNE .not_wisdom
%ShowSolicitedMessage($01AC)
JMP ++
.not_wisdom
CMP.b #$5B : BNE .not_power
%ShowSolicitedMessage($01AB)
JMP ++
.not_power
CMP.b #$40 : BNE .not_pyramid
%ShowSolicitedMessage($01AA)
JMP ++
.not_pyramid
CMP.b #$70 : BNE .not_underwater
%ShowSolicitedMessage($01AD)
JMP ++
.not_underwater
CMP.b #$42 : BNE .not_portal
%ShowSolicitedMessage($01AF)
JMP ++
.not_portal
%ShowSolicitedMessage($01AE) : BCC .no_talk
JSL GetRandomInt : AND.b #$06 : STA.l FOUNDRINGS
.no_talk
++
RTS
}
```
## Drawing (`Sprite_EonZora_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
```asm
Sprite_EonZora_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, 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 .sizes, X : 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, $0D
.nbr_of_tiles
db 1, 1, 1, 1, 1, 1, 0, 0
.x_offsets
dw 0, 16
dw 0, -16
dw 0, 8
dw 0, 8
dw 0, -8
dw 0, -8
dw 0
dw 0
.y_offsets
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0
dw 0
.chr
db $60, $62
db $60, $62
db $40, $41
db $43, $44
db $40, $41
db $43, $44
db $64
db $64
.properties
db $39, $39
db $79, $79
db $39, $39
db $39, $39
db $79, $79
db $79, $79
db $39
db $79
.sizes
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02
db $02
}
```
## Design Patterns
* **Context-Sensitive Dialogue**: The NPC's dialogue dynamically changes based on Link's current `AreaIndex`, providing a rich and immersive storytelling experience tailored to the player's location.
* **Random Movement**: The Zora exhibits random walking behavior, contributing to the environmental ambiance and making the world feel more alive.
* **NPC Interaction**: Provides dialogue and has the potential to grant items (randomly setting `FOUNDRINGS`), adding an element of surprise and reward to player interactions.
* **Animation-Driven Movement**: The sprite's movement states are directly tied to specific animations for each direction, ensuring visual consistency between its actions and appearance.
* **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.

View File

@@ -0,0 +1,127 @@
# Eon Zora Elder
## Overview
The Eon Zora Elder is an NPC (Non-Player Character) sprite primarily characterized by its animation-driven states. Its main function is to visually convey different moods or actions through distinct animations, such as idle, surprised, or holding a rod.
## Sprite Properties
Explicit sprite properties (`!SPRID`, `!NbrTiles`, etc.) are not defined within this file. It is assumed that these properties are either inherited from a vanilla sprite ID or defined in a separate configuration file, as this file focuses on the sprite's behavior and drawing.
## Main Logic & State Machine (`Sprite_EonZoraElder_Main`)
The Eon Zora Elder's core behavior is managed by a simple state machine that primarily controls its animations:
* **`EonZoraElder_Idle`**: Plays an idle animation (`%PlayAnimation(0,1,10)`).
* **`EonZoraElder_Surprised`**: Plays a surprised animation (`%PlayAnimation(2,3,10)`).
* **`EonZoraElder_WithRod`**: Plays an animation depicting the elder holding a rod (`%PlayAnimation(4,4,10)`).
```asm
Sprite_EonZoraElder_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw EonZoraElder_Idle
dw EonZoraElder_Surprised
dw EonZoraElder_WithRod
EonZoraElder_Idle:
%PlayAnimation(0,1,10)
RTS
EonZoraElder_Surprised:
%PlayAnimation(2,3,10)
RTS
EonZoraElder_WithRod:
%PlayAnimation(4,4,10)
RTS
}
```
## Drawing (`Sprite_EonZoraElder_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
```asm
Sprite_EonZoraElder_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, 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 .sizes, X : 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
.nbr_of_tiles
db 1, 1, 1, 2
.x_offsets
dw 0, 8
dw 0, 8
dw 0, 8
dw 0, 8, -4
.y_offsets
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0, 0
.chr
db $46, $47
db $49, $4A
db $66, $67
db $69, $6A, $6C
.properties
db $39, $39
db $39, $39
db $39, $39
db $39, $39, $39
.sizes
db $02, $02
db $02, $02
db $02, $02
db $02, $02, $02
}
```
## Design Patterns
* **Animation-Driven States**: The sprite's states are primarily used to control which animation is currently playing, allowing for visual feedback to the player (e.g., idle, surprised, holding a rod).
* **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.

333
Docs/Sprites/NPCs/Farore.md Normal file
View File

@@ -0,0 +1,333 @@
# Farore
## Overview
Farore, the Oracle of Secrets, is a pivotal NPC sprite deeply integrated into the game's narrative and cutscene system. Her behavior is highly dynamic, adapting to the player's location (indoors/outdoors) and various game progression flags. She plays a crucial role in guiding the player and controlling cinematic sequences.
## Sprite Properties
* **`!SPRID`**: `Sprite_Farore` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `2`
* **`!Harmless`**: `00` (Unusual for an NPC, might indicate specific interaction or placeholder)
* **`!HVelocity`**: `00`
* **`!Health`**: `0`
* **`!Damage`**: `0`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `01`
* **`!Shadow`**: `01`
* **`!Palette`**: `0`
* **`!Hitbox`**: `0`
* **`!Persist`**: `00`
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `0`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_Farore_Long`)
This routine acts as a dispatcher, conditionally calling different drawing and main logic routines based on whether Link is `INDOORS`. This indicates that the `Farore` sprite ID is reused for a different entity (likely "Hyrule Dream") when indoors.
```asm
Sprite_Farore_Long:
{
PHB : PHK : PLB
LDA.b INDOORS : BEQ .outdoors
JSR Sprite_HyruleDream_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_HyruleDream_Main
JMP .SpriteIsNotActive
.outdoors
JSR Sprite_Farore_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Farore_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Farore_Prep`)
This routine initializes Farore upon spawning. It sets `SprDefl, X` to `$80` to prevent despawning off-screen. It also includes conditional initialization based on `INDOORS` and a check for `$7EF300` (likely a flag for Farore's presence) to potentially despawn the sprite.
```asm
Sprite_Farore_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X ; Don't kill Farore when she goes off screen
LDA.b INDOORS : BEQ .outdoors
JSR Sprite_HyruleDream_Prep
JMP .PlayIntro
.outdoors
LDA.l $7EF300 : BEQ .PlayIntro
STZ.w SprState, X ; Kill the sprite
.PlayIntro
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Farore_Main`)
Farore's core behavior is managed by a complex state machine heavily involved in cutscenes and quest progression:
* **`IntroStart`**: Initiates a cutscene (`InCutScene = 01`) and transitions to different states based on `STORY_STATE` (`$B6`).
* **`MoveUpTowardsFarore`**: Controls Link's movement during a cutscene, slowing him down and moving him north. Transitions to `MoveLeftTowardsFarore` when Link reaches a certain Y-position.
* **`MoveLeftTowardsFarore`**: Continues Link's controlled movement, moving him west. Stops auto-movement, sets a timer, and transitions to `WaitAndMessage`.
* **`WaitAndMessage`**: Displays a message after a timer, applies speed towards the player, and transitions to `Farore_ProceedWithCutscene`.
* **`Farore_ProceedWithCutscene`**: A transitional state that leads to `FaroreFollowPlayer` after a timer.
* **`FaroreFollowPlayer`**: Farore follows Link, controlling his movement and updating various game state flags (`GAMESTATE`, `STORY_STATE`, rain sound). Transitions to `MakuArea_FaroreFollowPlayer`.
* **`MakuArea_FaroreFollowPlayer`**: Farore continues to follow Link in the Maku Area.
* **`MakuArea_FaroreWaitForKydrog`**: Farore waits in the Maku Area.
```asm
Sprite_Farore_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw IntroStart
dw MoveUpTowardsFarore
dw MoveLeftTowardsFarore
dw WaitAndMessage
dw Farore_ProceedWithCutscene
dw FaroreFollowPlayer
dw MakuArea_FaroreFollowPlayer
dw MakuArea_FaroreWaitForKydrog
; 00
IntroStart:
{
LDA #$01 : STA InCutScene
LDA $B6 : CMP.b #$01 : BEQ .maku_area
CMP.b #$02 : BEQ .waiting
%GotoAction(1)
RTS
.maku_area
%GotoAction(6)
RTS
.waiting
%GotoAction(7)
RTS
}
; 01
MoveUpTowardsFarore:
{
LDA.w WALKSPEED : STA.b $57 ; Slow Link down for the cutscene
LDA.b #$08 : STA.b $49 ; Auto-movement north
; Link's Y Position - Y = 6C
LDA.b $20 : CMP.b #$9C : BCC .linkistoofar
%GotoAction(2)
.linkistoofar
%PlayAnimation(6, 6, 8) ; Farore look towards Link
RTS
}
; 02
MoveLeftTowardsFarore:
{
; Move Link Left
LDA.w WALKSPEED : STA.b $57 ; Slow Link down for the cutscene
LDA.b #$02 : STA.b $49
; Link's X position
LDA.b $22 : CMP.b #$1A : BCS .linkistoofar
STZ.b $49 ; kill automove
LDA.b #$20
STA.w SprTimerA, X ; set timer A to 0x10
%PlayAnimation(0, 0, 8)
%GotoAction(3)
.linkistoofar
RTS
}
; 03
WaitAndMessage:
{
%PlayAnimation(1, 2, 8)
LDA.b #$15
JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_MoveVert
LDA.w SprTimerA, X : BNE +
STZ $2F
LDA #$00 : STA InCutScene
; "I am Farore, the Oracle of Secrets."
%ShowUnconditionalMessage($0E)
%GotoAction(4)
+
RTS
}
; 04
Farore_ProceedWithCutscene:
{
LDA.w SprTimerA, X : BNE ++
%GotoAction(5)
++
RTS
}
; 05
FaroreFollowPlayer:
{
LDA #$01 : STA InCutScene
LDA.w WALKSPEED : STA.b $57 ; Slow Link down for the cutscene
LDA.b #$08 : STA.b $49 ; Auto-movement north
%PlayAnimation(3, 4, 8)
LDA.b #$15
JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_MoveVert
LDA #$02 : STA $7EF3C5 ; (0 - intro, 1 - pendants, 2 - crystals)
LDA #$05 : STA $012D ; turn off rain sound
LDA #$01 : STA $B6 ; Set Story State
JSL Sprite_LoadGfxProperties
%GotoAction(6)
RTS
}
; 06
MakuArea_FaroreFollowPlayer:
{
%PlayAnimation(3, 4, 8)
LDA.b #$15
JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_MoveVert
%GotoAction(6)
RTS
}
; 07
MakuArea_FaroreWaitForKydrog:
{
%PlayAnimation(5, 5, 8)
RTS
}
}
```
## Drawing (`Sprite_Farore_Draw`)
This routine handles OAM allocation and animation for Farore. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_Farore_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 .sizes, X : 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
.nbr_of_tiles
db 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, -1
.y_offsets
dw -8, 4
dw -8, 4
dw 4, -8
dw -8, 4
dw 4, -7
dw -8, 4
dw 4, -7
.chr
db $A8, $AA
db $A8, $88
db $AA, $A8
db $8A, $8C
db $8C, $8A
db $8A, $AC
db $AA, $86
.properties
db $3B, $3B
db $3B, $7B
db $3B, $3B
db $3B, $3B
db $7B, $3B
db $3B, $3B
db $3B, $7B
.sizes
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
}
```
## Design Patterns
* **Multi-Character Sprite (Conditional Drawing/Logic)**: The sprite ID is reused for "Hyrule Dream" when indoors, demonstrating a powerful technique for resource optimization and context-sensitive character representation.
* **Cutscene Control**: Farore's logic is heavily integrated with cutscenes, controlling Link's movement, displaying messages, and managing game state transitions to create cinematic sequences.
* **Quest Progression Integration**: The sprite's appearance and behavior are tied to `STORY_STATE` and other game flags, indicating its crucial role in advancing the narrative.
* **Player Movement Manipulation**: During cutscenes, Farore's script directly controls Link's speed and auto-movement, ensuring precise choreography for story events.
* **Global State Management**: Modifies `InCutScene`, `GAMESTATE`, `STORY_STATE`, and other global variables to reflect and control the current game context.
* **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.

View File

@@ -0,0 +1,94 @@
# Followers
## Overview
The `followers.asm` file is a comprehensive collection of routines and data structures that implement a sophisticated follower system within Oracle of Secrets. It manages various NPC types, including the Zora Baby, Old Man, Kiki, and a Minecart, each with unique behaviors, interactions, and integration into the game world. This file heavily utilizes vanilla overrides to inject custom logic and expand upon existing game mechanics.
## Follower Data Memory Locations
This section defines various WRAM addresses used to store and cache follower-related data, enabling complex interactions and animations:
* **`FollowerYL`, `FollowerYH`, `FollowerXL`, `FollowerXH`, `FollowerZ`, `FollowerLayer`**: Stores position (Y, X, Z coordinates) and layer information for followers, with a cache for 20 steps of animation and movement.
* **`FollowerHeadOffset`, `FollowerHeadOffsetH`, `FollowerBodyOffset`, `FollowerBodyOffsetH`**: Stores offsets for follower head and body graphics, used to adjust their appearance based on direction (e.g., facing Link).
* **`Flwhgfxt`, `Flwhgfxth`, `Flwhgfxb`, `Flwhgfxbh`, `Flwbgfxt`, `Flwbgfxth`, `Flwbgfxb`, `Flwbgfxbh`**: Graphics data for follower head and body.
* **`Flwanimir`**: Index for reading follower animation steps.
* **`FollowerHook`**: Flag indicating when a follower is being used with the Hookshot.
* **`FollowerHookI`**: Caches `FLWANIMIW` when Hookshotting is finished.
* **`FLWGRABTIME`**: Countdown timer preventing followers from being immediately regrabbed after being dropped.
* **`FLWANIMIW`**: Index for writing follower animation steps.
* **`FollowCacheYL`, `FollowCacheYH`, `FollowCacheXL`, `FollowCacheXH`**: Cache of follower properties in SRAM.
## `Follower_WatchLink`
This routine adjusts a follower's head and body graphics offsets to make them turn and face Link, providing a more interactive and responsive NPC presence.
## Zora Baby Follower
The Zora Baby follower is a key NPC involved in specific puzzles and interactions, particularly with water switches.
* **`ZoraBaby_RevertToSprite`**: This routine spawns a `Sprite 0x39 Locksmith` (which is the Zora Baby sprite) and initializes its properties based on the follower's cached data. It sets `SprBulletproof`, `SprAction`, and `SprTimerB`, and clears relevant follower flags.
* **`CheckForZoraBabyTransitionToSprite`**: Checks if the Zora Baby is currently a follower (`$7EF3CC = $09`). If Link is standing on a star tile (`$0114 = $3B`), it calls `ZoraBaby_RevertToSprite` to transition the follower back into a regular sprite. If Link is outdoors, it clears the follower flag.
* **`CheckForZoraBabyFollower`**: A utility routine to check if the Zora Baby is currently a follower.
* **`UploadZoraBabyGraphicsPrep`**: Prepares the graphics for the Zora Baby, setting `$7EF3CC` to `$09` and calling `LoadFollowerGraphics`.
* **`ZoraBaby_CheckForWaterSwitchSprite`**: Checks for the presence of a `Sprite 0x21` (Water Gate Switch) and determines if the Zora Baby is positioned on top of it.
* **`ZoraBaby_CheckForWaterGateSwitch`**: Checks for a `Sprite 0x04` (Water Gate Switch) and performs a precise coordinate check to see if the Zora Baby is on top of it.
* **`ZoraBaby_GlobalBehavior`**: This is the main behavior routine for the Zora Baby. It makes the Zora Baby act as a barrier (`Sprite_BehaveAsBarrier`), makes it watch Link (`Follower_WatchLink`), and handles interactions like being lifted (`Sprite_CheckIfLifted`) and thrown (`ThrownSprite_TileAndSpriteInteraction_long`). Crucially, it detects if the Zora Baby is on a water switch and triggers the `ZoraBaby_PullSwitch` state.
### Zora Baby Vanilla Overrides
* **`org $09AA5E`**: Injects `JSL CheckForZoraBabyFollower` to enable the Zora Baby's swaying animation.
* **`org $09A19C`**: Injects `JSL CheckForZoraBabyTransitionToSprite` for follower basic movement.
* **`org $09A902`**: Sets the Zora Baby follower's palette to blue.
* **`org $09A8CF`**: Sets the Zora Baby character data offset.
* **`org $06BD9C`**: Defines the Zora Baby Sprite Idle OAM data.
* **`org $068D59` (`SpritePrep_Locksmith`)**: Overrides the `SpritePrep_Locksmith` routine. It makes the Zora Baby bulletproof, prevents spawning if already following, and calls `UploadZoraBabyGraphicsPrep`.
* **`org $06BCAC` (`Sprite_39_ZoraBaby`)**: Overrides `Sprite_39_Locksmith`. This is the main state machine for the Zora Baby, including states like `LockSmith_Chillin` (idle), `ZoraBaby_FollowLink`, `ZoraBaby_OfferService`, `ZoraBaby_RespondToAnswer`, `ZoraBaby_AgreeToWait`, `ZoraBaby_PullSwitch`, and `ZoraBaby_PostSwitch`. These states manage dialogue, following behavior, and interaction with switches.
## Old Man Follower
This section includes logic for the Old Man follower, particularly concerning his spawning conditions and item interactions.
* **`OldMan_ExpandedPrep`**: Prevents the Old Man sprite from spawning in his home room if Link already has him as a follower.
### Old Man Vanilla Overrides
* **`org $1EE9FF`**: Modifies the item given by the Old Man to be the Goldstar Hookshot upgrade.
* **`org $1BBD3C`**: Modifies `FindEntrance` for the Old Man.
* **`org $02D98B`**: Modifies `Underworld_LoadEntrance` for the Old Man.
* **`org $1EE8F1` (`SpritePrep_OldMan`)**: Overrides `SpritePrep_OldMan`. It makes the Old Man bulletproof, uses `OldMan_ExpandedPrep`, checks for the Lv2 Hookshot, and sets `$7EF3CC` to `$04` (Old Man follower) before calling `LoadFollowerGraphics`.
* **`org $09A4C8` (`Follower_HandleTriggerData`)**: This is a large data block defining trigger coordinates and messages for various followers, including the Old Man, Zelda, and Blind Maiden.
## Kiki Follower
This section contains logic for the Kiki follower, focusing on her reaction to Link's health.
* **`Kiki_CheckIfScared`**: If Link's health is low and Kiki is flashing, she will run away from him.
### Kiki Vanilla Overrides
* **`org $09A1C6`**: Injects `JSL Kiki_CheckIfScared` to implement Kiki's fear behavior.
* **`org $1EE2E9` (`Kiki_WalkOnRoof`)**: Defines speed data for Kiki walking on a roof.
* **`org $1EE576` (`Kiki_HopToSpot`)**: Defines target coordinates for Kiki to hop to a spot.
* **`org $1EE5E9` (`Kiki_WalkOnRoof_Ext`)**: Defines step and timer data for Kiki's extended roof walk.
## Minecart Follower
This section details the implementation of the Minecart follower, including its drawing, transition, and Link's interaction with it.
* **`FollowerDraw_CalculateOAMCoords`**: A helper routine to calculate OAM coordinates for followers.
* **`MinecartFollower_Top` / `MinecartFollower_Bottom`**: Drawing routines for the top and bottom halves of the Minecart follower.
* **`Minecart_AnimDirection`**: Data for Minecart animation direction.
* **`MinecartFollower_TransitionToSprite`**: Transitions the Minecart follower back into a regular sprite.
* **`DrawMinecartFollower`**: The main drawing routine for the Minecart follower, which also handles its transition to a sprite if Link is in the cart and not in a submodule.
* **`FollowerDraw_CachePosition`**: Caches the follower's position for drawing, adjusting coordinates relative to Link.
* **`CheckForMinecartFollowerDraw`**: Checks if the Minecart follower should be drawn.
* **`CheckForFollowerInterroomTransition` / `CheckForFollowerIntraroomTransition`**: Handles transitions for followers between rooms and within rooms.
* **`LinkState_Minecart`**: Defines Link's behavior when he is in a Minecart, including movement, collision, and animation.
* **`TileBehavior_TL_Long` / `TileBehavior_StopLeft_Long`**: Tile behaviors for Minecart tracks.
### Minecart Vanilla Overrides
* **`org $07A5F7`**: Injects `JSL LinkState_Minecart` to control Link's state when in a Minecart.
* **`org $07D938`**: Defines Minecart Track tile types.
* **`org $09A41F`**: Injects `JSL CheckForMinecartFollowerDraw`.
* **`org $028A5B`**: Injects `JSL CheckForFollowerInterroomTransition`.
* **`org $0289BF`**: Injects `JSL CheckForFollowerIntraroomTransition`.
## Design Patterns
* **Multi-Purpose File**: This file serves as a central repository for various follower-related logic, demonstrating how to manage diverse NPC behaviors within a single module.
* **Follower System**: Implements a robust and flexible follower system with features like position caching, animation, and complex interaction logic.
* **Vanilla Overrides**: Extensive use of `org` directives to modify vanilla sprite behaviors and integrate custom follower logic, showcasing advanced ROM hacking techniques.
* **Context-Sensitive Behavior**: Follower behavior dynamically changes based on game state, player actions, and environmental factors (e.g., Zora Baby on water switch, Old Man's spawning conditions, Kiki's fear of low-health Link).
* **Cutscene Integration**: Some followers (like the Zora Baby) are involved in cutscene-like sequences, demonstrating how to choreograph NPC actions within narrative events.
* **Item Gating/Progression**: The Old Man's appearance and item offerings are tied to the player's possession of specific items (e.g., Lv2 Hookshot), integrating followers into the game's progression system.
* **Player State Manipulation**: Routines like `LinkState_Minecart` directly control Link's movement and animation when interacting with followers, providing a seamless player experience.
* **16-bit OAM Calculations**: Explicitly uses `REP #$20` and `SEP #$20` for precise 16-bit OAM calculations in drawing routines, ensuring accurate sprite rendering.

View File

@@ -0,0 +1,88 @@
# Fortune Teller
## Overview
The `fortune_teller.asm` file is not a complete sprite definition but rather a set of routines and data that override and extend the behavior of the vanilla Fortune Teller NPC. Its primary function is to provide highly context-sensitive messages to Link, offering guidance or commentary based on his current inventory, collected items, and overall game progression.
## Vanilla Overrides
This file directly modifies existing vanilla code related to the Fortune Teller:
* **`org $0DC829`**: Overrides the `FortuneTellerMessage` data table, which contains the message IDs that the Fortune Teller can display.
* **`org $0DC849`**: Overrides the `FortuneTeller_PerformPseudoScience` routine, which is the core logic for determining and displaying messages.
## `FortuneTellerMessage` Data Table
This table stores a sequence of byte values, each representing a message ID. These IDs correspond to specific dialogue options that the Fortune Teller can present to Link.
```asm
org $0DC829
FortuneTellerMessage:
.low
#_0DC829: db $EA ; MESSAGE 00EA
#_0DC82A: db $EB ; MESSAGE 00EB
#_0DC82B: db $EC ; MESSAGE 00EC
#_0DC82C: db $ED ; MESSAGE 00ED
#_0DC82D: db $EE ; MESSAGE 00EE
#_0DC82E: db $EF ; MESSAGE 00EF
#_0DC82F: db $F0 ; MESSAGE 00F0
#_0DC830: db $F1 ; MESSAGE 00F1
#_0DC831: db $F6 ; MESSAGE 00F6
#_0DC832: db $F7 ; MESSAGE 00F7
#_0DC833: db $F8 ; MESSAGE 00F8
#_0DC834: db $F9 ; MESSAGE 00F9
#_0DC835: db $FA ; MESSAGE 00FA
#_0DC836: db $FB ; MESSAGE 00FB
#_0DC837: db $FC ; MESSAGE 00FC
#_0DC838: db $FD ; MESSAGE 00FD
.high
#_0DC839: db $00
; ... (rest of the table)
```
## `FortuneTeller_PerformPseudoScience` Routine
This routine is the central logic for the custom Fortune Teller. It dynamically selects which message to display to Link based on a series of checks against his inventory and various game progression flags stored in SRAM.
* **Initializations**: Performs some initial state manipulations (`STZ.w $0DC0,X`, `INC.w $0D80,X`, `STZ.b $03`).
* **Progression Check (`$7EF3D6`)**: Determines a base message category based on a custom progression flag.
* **Extensive Item and Game State Checks**: The routine then proceeds through a series of conditional checks, each corresponding to a specific item or game event. For example, it checks for:
* `$7EF344` (Mushroom/Powder)
* `$7EF37A` (Crystals, specifically if Tail Palace is beaten)
* `$7EF355` (Boots)
* `$7EF356` (Flippers)
* `$7EF345` (Fire Rod)
* `$7EF37B` (Magic Upgrade)
* `$7EF354` (Glove)
* `$7EF358` (Wolf Mask)
* `$7EF3C9` (Smithy Rescued flag)
* `$7EF352` (Cape)
* `$7EF354` (Titans Mitt)
* `$7EF359` (Sword level)
* **Message Display**: Based on these checks, it calls `FortuneTeller_PrepareNextMessage` with the appropriate message index and then `FortuneTeller_DisplayMessage` to show the message to the player.
```asm
FortuneTeller_PerformPseudoScience:
#_0DC849: STZ.w $0DC0,X
#_0DC84C: INC.w $0D80,X
#_0DC84F: STZ.b $03
#_0DC851: LDA.l $7EF3D6
#_0DC855: CMP.b #$02
#_0DC857: BCS .map_icon_past_pendants
#_0DC859: STZ.b $00
#_0DC85B: STZ.b $01
#_0DC85D: JMP.w FortuneTeller_DisplayMessage
.map_icon_past_pendants
#_0DC860: LDA.l $7EF344
#_0DC864: BNE .have_shroom_or_powder
#_0DC866: LDA.b #$02
#_0DC868: JSR FortuneTeller_PrepareNextMessage
#_0DC86B: BCC .have_shroom_or_powder
#_0DC86D: JMP.w FortuneTeller_DisplayMessage
; ... (rest of the conditional item checks)
```
## Design Patterns
* **Vanilla Override**: This file exemplifies how to directly modify and extend the behavior of existing vanilla NPCs through targeted code injection.
* **Context-Sensitive Dialogue**: The Fortune Teller's messages are highly dynamic and personalized, adapting to Link's current inventory and game progression. This creates a more engaging and responsive NPC interaction.
* **Quest Progression Tracking**: The routine extensively utilizes SRAM flags and item possession checks to track Link's progress through various quests and milestones, influencing the dialogue provided.
* **Modular Message System**: The use of `FortuneTeller_PrepareNextMessage` and `FortuneTeller_DisplayMessage` allows for a structured and modular approach to managing and displaying NPC dialogue.

179
Docs/Sprites/NPCs/Goron.md Normal file
View File

@@ -0,0 +1,179 @@
# Goron
## Overview
The Goron sprite (`!SPRID = $F2`) is a versatile NPC implementation that can represent two distinct Goron characters: the "Kalyxo Goron" and the "Eon Goron." Their specific behaviors and appearances are determined by the global `WORLDFLAG` and the current `AreaIndex`. These Gorons primarily serve as interactive NPCs, engaging Link through dialogue and potentially triggering game events.
## Sprite Properties
* **`!SPRID`**: `$F2` (Vanilla sprite ID, likely for a generic NPC)
* **`!NbrTiles`**: `04`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `02`
* **`!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_Goron_Long`)
This routine acts as a dispatcher, selecting the appropriate drawing routine based on the `WORLDFLAG` (Kalyxo Goron if `0`, Eon Goron otherwise). It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_Goron_Long:
{
PHB : PHK : PLB
LDA.w WORLDFLAG : BEQ .kalyxo
JSR Sprite_EonGoron_Draw
JMP +
.kalyxo
JSR Sprite_KalyxoGoron_Draw
+
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Goron_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Goron_Prep`)
This routine initializes the Goron upon spawning. It sets `SprDefl, X` to `$80`. The initial `SprAction, X` is determined by `WORLDFLAG` and `AreaIndex`. For Eon Gorons, it can randomly set their initial action to `EonGoron_Main`, `EonGoron_Sing`, or `EonGoron_Punch`. For Kalyxo Gorons, it checks a specific flag (`$7EF280, X`) to set their initial action to `KalyxoGoron_Main` or `KalyxoGoron_MinesOpened`.
```asm
Sprite_Goron_Prep:
{
PHB : PHK : PLB
LDA.w WORLDFLAG : BEQ +
LDA.w AreaIndex : CMP.b #$55 : BNE .not_sing
LDA.b #$04 : STA.w SprAction, X
.not_sing
JSL GetRandomInt : AND.b #$01 : BEQ .rand
LDA.b #$05 : STA.w SprAction, X
JMP ++
.rand
LDA.b #$03 : STA.w SprAction, X
JMP ++
+
PHX
LDX $8A
LDA.l $7EF280, X : CMP.b #$20 : BEQ +++
PLX
STZ.w SprAction, X
++
PLB
RTL
+++
PLX
LDA.b #$02 : STA.w SprAction, X
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Goron_Main`)
This routine manages the various states and behaviors of both Kalyxo and Eon Gorons.
* **Player Collision**: Prevents Link from passing through the Goron (`Sprite_PlayerCantPassThrough`).
* **`KalyxoGoron_Main`**: Displays messages (`%ShowSolicitedMessage`) based on the `RockMeat` item count. Can transition to `KalyxoGoron_OpenMines` under certain conditions.
* **`KalyxoGoron_OpenMines`**: Plays an animation, sets a flag (`$04C6`) to open mines, and transitions to `KalyxoGoron_MinesOpened`.
* **`KalyxoGoron_MinesOpened`**: Plays an animation.
* **`EonGoron_Main`**: Plays an animation and displays a message.
* **`EonGoron_Sing`**: Plays a singing animation and displays a message.
* **`EonGoron_Punch`**: Plays a punching animation and displays a message.
```asm
Sprite_Goron_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw KalyxoGoron_Main
dw KalyxoGoron_OpenMines
dw KalyxoGoron_MinesOpened
dw EonGoron_Main
dw EonGoron_Sing
dw EonGoron_Punch
KalyxoGoron_Main:
{
LDA.l RockMeat : BEQ +
CMP.b #$05 : BCC ++
%ShowSolicitedMessage($01A9) : BCC +++
INC.w SprAction, X
+++
RTS
+
%ShowSolicitedMessage($01A7)
RTS
++
%ShowSolicitedMessage($01A8)
RTS
}
KalyxoGoron_OpenMines:
{
%PlayAnimation(1,1,10)
LDA.b #$04 : STA $04C6
INC.w SprAction, X
RTS
}
KalyxoGoron_MinesOpened:
{
%PlayAnimation(1,1,10)
RTS
}
EonGoron_Main:
{
%PlayAnimation(0, 1, 10)
%ShowSolicitedMessage($01B0)
RTS
}
EonGoron_Sing:
{
%PlayAnimation(2, 3, 10)
%ShowSolicitedMessage($01B2)
RTS
}
EonGoron_Punch:
{
%PlayAnimation(4, 5, 10)
%ShowSolicitedMessage($01B1)
RTS
}
}
```
## Drawing (`Sprite_KalyxoGoron_Draw` and `Sprite_EonGoron_Draw`)
Both drawing routines handle OAM allocation and animation for their respective Goron types. They explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
## Design Patterns
* **Multi-Character NPC (Conditional Drawing/Logic)**: A single sprite definition is used to represent two distinct Goron characters (Kalyxo and Eon) based on `WORLDFLAG`, demonstrating efficient resource utilization and context-sensitive character representation.
* **Quest Progression Integration**: The Gorons' dialogue and actions are tied to game state (e.g., `RockMeat` item count, `AreaIndex`), indicating their role in advancing the narrative and triggering specific events like opening mines.
* **Conditional Behavior**: Extensive use of conditional logic based on `WORLDFLAG` and `AreaIndex` allows for dynamic changes in the Goron's role, dialogue, and actions.
* **NPC Interaction**: Provides rich interaction with the player through dialogue (`%ShowSolicitedMessage`) and can trigger game events.
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make the NPC a solid object that Link cannot walk through.
* **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.

View File

@@ -0,0 +1,152 @@
# Hyrule Dream
## Overview
The Hyrule Dream sprite represents a special NPC that appears indoors and plays a role in a specific questline. It interacts with Link through dialogue and grants a unique "Dream" item, with its presence and actions tied to game progression.
## Sprite Properties
Explicit sprite properties (`!SPRID`, `!NbrTiles`, etc.) are not defined within this file. It is assumed that these properties are either inherited from a vanilla sprite ID or defined in a separate configuration file, as this file focuses on the sprite's behavior and drawing.
## Main Logic (`Sprite_HyruleDream_Main`)
This routine manages the Hyrule Dream's behavior through a state machine:
* **`HyruleDream_Idle`**: The sprite plays an idle animation. When Link is within a certain proximity (`GetDistance8bit_Long`), it transitions to the `HyruleDream_Talk` state.
* **`HyruleDream_Talk`**: The sprite continues its idle animation and displays a solicited message (`%ShowSolicitedMessage($01B3)`). Upon completion of the message, it transitions to `HyruleDream_GiveItem`.
* **`HyruleDream_GiveItem`**: The sprite maintains its idle animation. After a timer (`SprTimerA, X`) expires, it grants Link the "Dream" item (`LDY #$12`, `JSL Link_ReceiveItem`), sets a flag (`$7EF410`) indicating the Dream has been obtained, and then despawns itself (`STZ.w SprState, X`).
```asm
Sprite_HyruleDream_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw HyruleDream_Idle
dw HyruleDream_Talk
dw HyruleDream_GiveItem
HyruleDream_Idle:
{
%PlayAnimation(0,0,1)
JSL GetDistance8bit_Long : CMP.b #$20 : BCS +
INC.w SprAction, X
+
RTS
}
HyruleDream_Talk:
{
%PlayAnimation(0,0,1)
%ShowSolicitedMessage($01B3) : BCC +
INC.w SprAction, X
+
RTS
}
HyruleDream_GiveItem:
{
%PlayAnimation(0,0,1)
LDA.w SprTimerA, X : BNE +
LDY #$12 : JSL Link_ReceiveItem
LDA.b #$01 : STA.l $7EF410
STZ.w SprState, X
+
RTS
}
}
```
## Initialization (`Sprite_HyruleDream_Prep`)
This routine initializes the Hyrule Dream sprite upon spawning. It sets `SprDefl, X` to `$80` (preventing despawning off-screen). Crucially, it checks the "Dream" flag (`$7EF410`). If Link has already obtained the Dream, the sprite immediately despawns (`STZ.w SprState, X`), ensuring it only appears once.
```asm
Sprite_HyruleDream_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.l $7EF410 : BNE +
STZ.w SprState, X
+
PLB
RTL
}
```
## Drawing (`Sprite_HyruleDream_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
```asm
Sprite_HyruleDream_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, 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 .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer
PLY : INY
PLX : DEX : BPL .nextTile
PLX
RTS
.start_index
db $00
.nbr_of_tiles
db 3
.x_offsets
dw -8, 8, -8, 8
.y_offsets
dw -8, -8, 8, 8
.chr
db $C0, $C2, $E0, $E2
.properties
db $3B, $7B, $3B, $7B
.sizes
db $02, $02, $02, $02
}
```
## Design Patterns
* **NPC Interaction**: The sprite is designed to engage with the player through dialogue and the granting of a unique item, driving a specific questline.
* **Quest Progression Integration**: The sprite's appearance and item-granting are directly tied to a flag (`$7EF410`) for the "Dream" item, ensuring it appears only once per playthrough.
* **Conditional Spawning/Despawning**: The sprite dynamically despawns if Link has already obtained the "Dream" item, preventing redundant interactions.
* **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.

43
Docs/Sprites/NPCs/Impa.md Normal file
View File

@@ -0,0 +1,43 @@
# Impa (Zelda)
## Overview
The `impa.asm` file is not a complete sprite definition but rather a collection of routines and vanilla overrides that modify the behavior of the character Impa (or Zelda, as indicated by some labels). Its primary purpose is to manage spawn points and manipulate game state during critical events, likely cutscenes or key progression points within the game.
## Spawn Point Definitions
The file defines a WRAM address `SPAWNPT = $7EF3C8` for a spawn point flag, along with comments detailing various spawn point IDs:
* `0x00` - Link's house
* `0x01` - Sanctuary (Hall of Secrets)
* `0x02` - Castle Prison
* `0x03` - Castle Basement
* `0x04` - Throne
* `0x05` - Old man cave
* `0x06` - Old man home
## `Impa_SetSpawnPointFlag`
This routine is responsible for setting a specific spawn point flag. It stores a value into `$7EF372` (likely the actual spawn point ID) and then sets bit `$04` in `$7EF3D6` (a custom progression flag), indicating that a particular event has occurred.
```asm
Impa_SetSpawnPointFlag:
{
STA.l $7EF372
LDA.l $7EF3D6 : ORA.b #$04 : STA.l $7EF3D6
RTL
}
```
## Vanilla Overrides
This file extensively uses `pushpc`/`pullpc` blocks and `org` directives to inject custom code into specific vanilla routines, thereby altering the game's default behavior:
* **`org $05EE46` (`Zelda_AtSanctuary`)**: Injects `JSL Impa_SetSpawnPointFlag`. This modification ensures that when Zelda (or Impa) is at the Sanctuary, a specific spawn point flag is set, influencing where Link might respawn or transition.
* **`org $05EBCF`**: Modifies a comparison involving `$7EF359` (likely Link's Sword level) with `$05`. This is noted as a `TODO`, suggesting it's an incomplete or placeholder modification.
* **`org $029E2E` (`Module15_0C`)**: Modifies an overlay that Impa activates after the intro sequence. It sets bit `$20` in `$7EF2A3` (likely an overlay control flag), potentially changing the visual environment.
* **`org $05ED43` (`Zelda_BecomeFollower`)**: Prevents Impa from setting a spawn point by clearing `$02E4` and `$7EF3C8`. This suggests a custom handling of Impa's follower state.
* **`org $05ED63`**: NOPs out 5 bytes, effectively disabling some vanilla code at this address.
* **`org $05ED10` (`Zelda_ApproachHero`)**: NOPs out 5 bytes, preventing Impa from changing the game's background music or sound, indicating custom control over audio during this event.
## Design Patterns
* **Vanilla Override**: This file is a prime example of how to extensively override vanilla code to integrate custom NPC behaviors and progression systems into an existing game.
* **Quest Progression Tracking**: Utilizes custom progression flags (`$7EF3D6`) and spawn point flags (`$7EF372`, `$7EF3C8`) to meticulously track key events and the player's progress through the game's narrative.
* **Game State Manipulation**: Directly modifies WRAM addresses to control various aspects of the game, such as visual overlays and audio, providing fine-grained control over the player's experience.
* **Modular Code Injection**: The use of `org` directives allows for precise injection of custom routines into specific points of the vanilla codebase, enabling targeted modifications without disrupting unrelated functionality.

184
Docs/Sprites/NPCs/Korok.md Normal file
View File

@@ -0,0 +1,184 @@
# Korok
## Overview
The Korok sprite (`!SPRID = Sprite_Korok`) implements a multi-variant NPC system, allowing for different Korok characters (Makar, Hollo, Rown) to appear from a single sprite definition. These Koroks exhibit random walking behavior, engage in dialogue, and are liftable, contributing to environmental interactions and minor puzzles.
## Sprite Properties
* **`!SPRID`**: `Sprite_Korok` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `08`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Impervious to all attacks)
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `01`
* **`!Palette`**: `00`
* **`!Hitbox`**: `03`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!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_Korok_Long`)
This routine acts as a dispatcher for drawing the correct Korok variant based on its `SprSubtype, X`. It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_Korok_Long:
{
PHB : PHK : PLB
LDA $0AA5 : BEQ .done
LDA.w SprSubtype, X : BEQ .draw_makar
CMP.b #$01 : BEQ .draw_hollo
CMP.b #$02 : BEQ .draw_rown
.draw_makar
JSR Sprite_Korok_DrawMakar
BRA .done
.draw_hollo
JSR Sprite_Korok_DrawHollo
BRA .done
.draw_rown
JSR Sprite_Korok_DrawRown
BRA .done
.done
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Korok_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Korok_Prep`)
This routine initializes the Korok upon spawning by randomly assigning a `SprSubtype, X` (0-3). This subtype determines which Korok variant (Makar, Hollo, or Rown) the sprite will represent.
```asm
Sprite_Korok_Prep:
{
PHB : PHK : PLB
JSL GetRandomInt : AND.b #$03 : STA.w SprSubtype, X
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Korok_Main`)
The Korok's core behavior is managed by a state machine that includes idle, walking, and liftable states.
* **`Sprite_Korok_Idle`**: The Korok plays an idle animation. Upon player interaction (`%ShowSolicitedMessage($001D)`), it randomly transitions to a walking state. It also prevents player passage (`Sprite_PlayerCantPassThrough`).
* **`Sprite_Korok_WalkingDown` / `Up` / `Left` / `Right`**: These states control the Korok's movement in different directions. Each state plays a specific walking animation, sets the appropriate speed (`KorokWalkSpeed`), moves the sprite (`Sprite_Move`), and after a timer (`SprTimerB, X`), randomly transitions to another walking state.
* **`Sprite_Korok_Liftable`**: This state handles the Korok's interaction when lifted (`Sprite_CheckIfLifted`) and thrown (`ThrownSprite_TileAndSpriteInteraction_long`).
```asm
Sprite_Korok_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw Sprite_Korok_Idle
dw Sprite_Korok_WalkingDown
dw Sprite_Korok_WalkingUp
dw Sprite_Korok_WalkingLeft
dw Sprite_Korok_WalkingRight
dw Sprite_Korok_Liftable
Sprite_Korok_Idle:
{
%PlayAnimation(0, 0, 10)
LDA $0AA5 : BNE +
PHX
JSL ApplyKorokSpriteSheets
PLX
LDA.b #$01 : STA.w $0AA5
+
%ShowSolicitedMessage($001D) : BCC .no_talk
JSL GetRandomInt : AND.b #$03
STA.w SprAction, X
RTS
.no_talk
JSL Sprite_PlayerCantPassThrough
RTS
}
Sprite_Korok_WalkingDown:
{
%PlayAnimation(0, 2, 10)
LDA.b #KorokWalkSpeed : STA.w SprYSpeed, X
JSL Sprite_Move
LDA.w SprTimerB, X : BNE +
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X
+
RTS
}
Sprite_Korok_WalkingUp:
{
%PlayAnimation(3, 5, 10)
LDA.b #-KorokWalkSpeed : STA.w SprYSpeed, X
JSL Sprite_Move
LDA.w SprTimerB, X : BNE +
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X
+
RTS
}
Sprite_Korok_WalkingLeft:
{
%PlayAnimation(6, 8, 10)
LDA.b #KorokWalkSpeed : STA.w SprXSpeed, X
JSL Sprite_Move
LDA.w SprTimerB, X : BNE +
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X
+
RTS
}
Sprite_Korok_WalkingRight:
{
%PlayAnimation(9, 11, 10)
LDA.b #-KorokWalkSpeed : STA.w SprXSpeed, X
JSL Sprite_Move
LDA.w SprTimerB, X : BNE +
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X
+
RTS
}
Sprite_Korok_Liftable:
{
JSL Sprite_Move
JSL Sprite_CheckIfLifted
JSL ThrownSprite_TileAndSpriteInteraction_long
RTS
}
}
```
## Drawing (`Sprite_Korok_DrawMakar`, `Sprite_Korok_DrawHollo`, `Sprite_Korok_DrawRown`)
Each Korok variant has its own dedicated drawing routine (`Sprite_Korok_DrawMakar`, `Sprite_Korok_DrawHollo`, `Sprite_Korok_DrawRown`). These routines handle OAM allocation and animation, and explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each routine contains its own specific OAM data for rendering the respective Korok character.
## Design Patterns
* **Multi-Variant NPC**: A single sprite definition (`Sprite_Korok`) is used to represent multiple distinct Korok characters (Makar, Hollo, Rown) based on a randomly assigned `SprSubtype`, showcasing efficient resource utilization and varied visual appearances.
* **Randomized Behavior**: The Korok's initial variant and its walking directions are randomized, adding an element of unpredictability and variety to encounters.
* **NPC Interaction**: The Korok can be interacted with through dialogue (`%ShowSolicitedMessage`) and is liftable (`Sprite_CheckIfLifted`), allowing for environmental puzzles or simple interactions.
* **Conditional Drawing**: The drawing routine dispatches to different sub-routines based on the Korok's subtype, allowing for distinct visual appearances for each variant.
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make the NPC a solid object that Link cannot walk through.
* **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.

View File

@@ -0,0 +1,137 @@
# Maku Tree
## Overview
The Maku Tree sprite (`!SPRID = Sprite_MakuTree`) represents a significant NPC in the game, likely serving as a quest giver or a key character in the narrative. Its interactions are primarily dialogue-driven and tied to game progression, culminating in the granting of a Heart Container. Notably, its graphics are not handled by a conventional sprite drawing routine within this file, suggesting it might be a background element or drawn by a separate system.
## Sprite Properties
* **`!SPRID`**: `Sprite_MakuTree` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `00` (Indicates graphics are handled externally or as a background)
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `0`
* **`!Damage`**: `0`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `0`
* **`!Hitbox`**: `$0D`
* **`!Persist`**: `00`
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `0`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_MakuTree_Long`)
This routine primarily checks if the Maku Tree sprite is active and then dispatches to its main logic routine. The absence of a direct drawing call (`JSR Sprite_MakuTree_Draw`) here suggests its visual representation is managed outside of the standard sprite drawing pipeline.
```asm
Sprite_MakuTree_Long:
{
PHB : PHK : PLB
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_MakuTree_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_MakuTree_Prep`)
This routine initializes the Maku Tree upon spawning. It sets `SprDefl, X` and includes logic to potentially play the "Maku Song" by checking a custom progression flag (`OOSPROG2`).
```asm
Sprite_MakuTree_Prep:
{
PHB : PHK : PLB
; Play the Maku Song
LDA.l OOSPROG2 : AND.b #$04 : BEQ +
LDA.b #$03 : STA.w $012C
+
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_MakuTree_Main`)
The Maku Tree's core behavior is managed by a state machine that guides its interaction with Link and quest progression.
* **Player Collision**: Prevents Link from passing through the Maku Tree (`JSL Sprite_PlayerCantPassThrough`).
* **`MakuTree_Handler`**: Checks the `MakuTreeQuest` flag to determine if Link has already met the Maku Tree, transitioning to `MakuTree_MeetLink` or `MakuTree_HasMetLink` accordingly.
* **`MakuTree_MeetLink`**: When Link is close enough, the Maku Tree displays an unconditional message (`%ShowUnconditionalMessage($20)`), updates quest flags (`MakuTreeQuest`, `MapIcon`, `$7EF3D6`), and transitions to `MakuTree_SpawnHeartContainer`.
* **`MakuTree_SpawnHeartContainer`**: Grants Link a Heart Container (`LDY #$3E : JSL Link_ReceiveItem`) and then transitions to `MakuTree_HasMetLink`.
* **`MakuTree_HasMetLink`**: Displays a solicited message (`%ShowSolicitedMessage($22)`) and updates a progression flag (`$7EF3D6`) upon message dismissal.
```asm
Sprite_MakuTree_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw MakuTree_Handler
dw MakuTree_MeetLink
dw MakuTree_SpawnHeartContainer
dw MakuTree_HasMetLink
MakuTree_Handler:
{
; Check the progress flags
LDA.l MakuTreeQuest : AND.b #$01 : BNE .has_met_link
%GotoAction(1)
RTS
.has_met_link
%GotoAction(3)
RTS
}
MakuTree_MeetLink:
{
JSL GetDistance8bit_Long : CMP #$28 : BCS .not_too_close
%ShowUnconditionalMessage($20)
LDA.b #$01 : STA.l MakuTreeQuest
LDA.b #$01 : STA.l MapIcon ; Mushroom Grotto
LDA.l $7EF3D6 : ORA.b #$02 : STA.l $7EF3D6
%GotoAction(2)
.not_too_close
RTS
}
MakuTree_SpawnHeartContainer:
{
; Give Link a heart container
LDY #$3E : JSL Link_ReceiveItem
%GotoAction(3)
RTS
}
MakuTree_HasMetLink:
{
%ShowSolicitedMessage($22) : BCC .no_talk
LDA.l $7EF3D6 : ORA.b #$02 : STA.l $7EF3D6
.no_talk
RTS
}
}
```
## Drawing
There is no `Sprite_MakuTree_Draw` routine defined within this file. This strongly suggests that the Maku Tree's graphical representation is handled as a background element or by a separate drawing system, rather than as a conventional sprite with its own OAM data.
## Design Patterns
* **Background/Static NPC**: The Maku Tree functions as a static NPC whose visual representation is likely integrated into the background, as evidenced by the absence of a dedicated sprite drawing routine.
* **Quest Gating/Progression**: The Maku Tree's interactions are deeply tied to custom quest flags (`MakuTreeQuest`, `OOSPROG2`, `$7EF3D6`), controlling when Link can meet it, engage in dialogue, and receive rewards.
* **Item Granting**: The Maku Tree serves as a source for a significant reward, granting Link a Heart Container upon meeting specific quest conditions.
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make the Maku Tree a solid, impassable object in the game world.
* **Dialogue-Driven Progression**: Dialogue (`%ShowUnconditionalMessage`, `%ShowSolicitedMessage`) is strategically used to advance the quest narrative and provide context to the player's journey.

227
Docs/Sprites/NPCs/Maple.md Normal file
View File

@@ -0,0 +1,227 @@
# Maple
## Overview
The `maple.asm` file defines the behavior for the NPC "Maple," a significant character involved in a branching "Dream" questline. Maple interacts with Link through extensive dialogue, offers explanations about game mechanics, and possesses the unique ability to put Link to sleep, triggering special dream sequences that advance the narrative and potentially grant rewards.
## Main Logic (`MapleHandler`)
This routine orchestrates Maple's complex interactions with Link, managing dialogue, quest progression, and the initiation of dream sequences.
* **Player Collision**: Prevents Link from passing through Maple (`JSL Sprite_PlayerCantPassThrough`).
* **`Maple_Idle`**: Displays a solicited message (`%ShowSolicitedMessage($01B3)`). Upon dismissal, it transitions to `Maple_HandleFirstResponse`. It also includes logic to set a flag (`$7EF351`) and a timer (`$012F`) for a specific event.
* **`Maple_HandleFirstResponse`**: Processes Link's initial dialogue response (`$1CE8`), leading to different branches: `Maple_Idle`, `Maple_ExplainHut`, or `Maple_DreamOrExplain`.
* **`Maple_DreamOrExplain`**: Displays an unconditional message (`%ShowUnconditionalMessage($01B4)`) and, based on Link's response, transitions to `Maple_ExplainPendants`, `Maple_CheckForPendant`, or back to `Maple_Idle`.
* **`Maple_ExplainHut`**: Displays an unconditional message (`%ShowUnconditionalMessage($01B5)`) and returns to `Maple_Idle`.
* **`Maple_ExplainPendants`**: Displays an unconditional message (`%ShowUnconditionalMessage($01B8)`) and returns to `Maple_Idle`.
* **`Maple_CheckForPendant`**: Checks Link's collected Pendants (`Pendants` SRAM flag) and Dreams (`Dreams` SRAM flag) to determine if a new Dream is available. If so, it sets `CurrentDream`, displays a message (`%ShowUnconditionalMessage($01B6)`), and transitions to `Maple_PutLinkToSleep`. Otherwise, it transitions to `Maple_NoNewPendant`.
* **`Maple_NoNewPendant`**: Displays an unconditional message (`%ShowUnconditionalMessage($01B7)`) and returns to `Maple_Idle`.
* **`Maple_PutLinkToSleep`**: Calls `Sprite_PutLinkToSleep` to initiate the sleep sequence and then transitions to `Maple_HandleDreams`.
* **`Maple_HandleDreams`**: After a timer (`SprTimerA, X`), calls `Link_HandleDreams` to process the dream sequence.
```asm
MapleHandler:
{
%PlayAnimation(0,1,16)
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw Maple_Idle
dw Maple_HandleFirstResponse
dw Maple_DreamOrExplain
dw Maple_ExplainHut
dw Maple_ExplainPendants
dw Maple_CheckForPendant
dw Maple_NoNewPendant
dw Maple_PutLinkToSleep
dw Maple_HandleDreams
Maple_Idle:
{
%ShowSolicitedMessage($01B3) : BCC +
INC.w SprAction, X
+
LDA.l $7EF351 : BEQ +
LDA.b #$02 : STA.l $7EF351
LDA.b #$1B : STA.w $012F
+
RTS
}
Maple_HandleFirstResponse:
{
LDA.w $1CE8 : CMP.b #$02 : BNE +
STZ.w SprAction, X
RTS
+
CMP.b #$01 : BNE .next_response
LDA.b #$03 : STA.w SprAction, X
RTS
.next_response
INC.w SprAction, X
RTS
}
Maple_DreamOrExplain:
{
%ShowUnconditionalMessage($01B4)
LDA.w $1CE8 : BEQ .check_for_pendant
CMP.b #$01 : BNE .another_time
LDA.b #$04 : STA.w SprAction, X
RTS
.check_for_pendant
LDA.b #$05 : STA.w SprAction, X
RTS
.another_time
STZ.w SprAction, X
RTS
}
Maple_ExplainHut:
{
%ShowUnconditionalMessage($01B5)
STZ.w SprAction, X
RTS
}
Maple_ExplainPendants:
{
%ShowUnconditionalMessage($01B8)
STZ.w SprAction, X
RTS
}
Maple_CheckForPendant:
{
; Check for pendant
LDA.l Pendants : AND.b #$04 : BNE .courage
LDA.l Pendants : AND.b #$02 : BNE .power
LDA.l Pendants : AND.b #$01 : BNE .wisdom
JMP .none
.courage
LDA.l Dreams : AND.b #$04 : BNE .power
LDA.b #$02 : STA.w CurrentDream : BRA +
.power
LDA.l Dreams : AND.b #$02 : BNE .wisdom
LDA.b #$01 : STA.w CurrentDream : BRA +
.wisdom
LDA.l Dreams : AND.b #$01 : BNE .none
STZ.w CurrentDream
+
%ShowUnconditionalMessage($01B6)
LDA.b #$07 : STA.w SprAction, X
LDA.b #$40 : STA.w SprTimerA, X
RTS
.none
INC.w SprAction, X
RTS
}
Maple_NoNewPendant:
{
%ShowUnconditionalMessage($01B7)
STZ.w SprAction, X
RTS
}
Maple_PutLinkToSleep:
{
JSR Sprite_PutLinkToSleep
INC.w SprAction, X
RTS
}
Maple_HandleDreams:
{
LDA.w SprTimerA, X : BNE +
JSR Link_HandleDreams
+
RTS
}
}
```
## `Sprite_PutLinkToSleep`
This routine initiates a cinematic sleep sequence for Link. It adjusts Link's coordinates, sets his state to sleeping (`$5D = $16`), spawns a blanket ancilla, adjusts Link's OAM coordinates, and applies a blinding white palette filter to transition into a dream sequence.
```asm
Sprite_PutLinkToSleep:
{
PHX
LDA.b $20 : SEC : SBC.b #$14 : STA.b $20
LDA.b $22 : CLC : ADC.b #$18 : STA.b $22
LDA.b #$16 : STA.b $5D ; Set Link to sleeping
LDA.b #$20 : JSL AncillaAdd_Blanket
LDA.b $20 : CLC : ADC.b #$04 : STA.w $0BFA,X
LDA.b $21 : STA.w $0C0E,X
LDA.b $22 : SEC : SBC.b #$08 : STA.w $0C04,X
LDA.b $23 : STA.w $0C18,X
JSL PaletteFilter_StartBlindingWhite
JSL ApplyPaletteFilter
PLX
RTS
}
```
## `Link_HandleDreams`
This routine manages the different dream sequences based on the `CurrentDream` variable. It sets specific bits in the `Dreams` SRAM flag and warps Link to a designated room using `Link_WarpToRoom`.
* **`Dream_Wisdom`**: Sets bit `0` in `Dreams`, warps Link to a room, and sets `$EE` to `$01`.
* **`Dream_Power`**: Sets bit `1` in `Dreams` and warps Link to a room.
* **`Dream_Courage`**: Sets bit `2` in `Dreams` and warps Link to a room.
```asm
Link_HandleDreams:
{
LDA.w CurrentDream
JSL JumpTableLocal
dw Dream_Wisdom
dw Dream_Power
dw Dream_Courage
Dream_Wisdom:
{
LDA.l Dreams : ORA.b #%00000001 : STA.l Dreams
LDX.b #$00
JSR Link_WarpToRoom
LDA.b #$01 : STA.b $EE
RTS
}
Dream_Power:
{
LDA.l Dreams : ORA.b #%00000010 : STA.l Dreams
LDX.b #$01
JSR Link_WarpToRoom
RTS
}
Dream_Courage:
{
LDA.l Dreams : ORA.b #%00000100 : STA.l Dreams
LDX.b #$02
JSR Link_WarpToRoom
RTS
}
}
```
## `Link_WarpToRoom`
This routine sets Link's state for warping, including setting his `LinkState` and room coordinates, and uses a `.room` data table to determine the target room for the warp.
## `Link_FallIntoDungeon`
This routine sets Link's state for falling into a dungeon, including setting the entrance ID and various state flags. It uses an `.entrance` data table to determine the target entrance.
## Vanilla Override
* **`org $068C9C`**: Sets a byte to `$0F`, likely a minor adjustment to vanilla code.
## Design Patterns
* **Complex Dialogue System**: Maple's interactions feature a highly branching and conditional dialogue system, where player choices and game progression influence the conversation flow and subsequent events.
* **Quest Gating/Progression**: Maple is a central figure in a "Dream" questline, with her dialogue and actions dynamically adapting based on Link's collected Pendants and Dreams, guiding the player through a significant narrative arc.
* **Player State Manipulation**: Maple possesses the unique ability to put Link to sleep, which triggers special dream sequences and warps him to different locations, creating immersive and story-rich transitions.
* **Cinematic Sequences**: The `Sprite_PutLinkToSleep` routine orchestrates a cinematic effect, incorporating screen transitions, visual filters, and the spawning of ancillae to enhance the player's experience during dream initiations.
* **Global State Management**: The sprite extensively modifies `Pendants`, `Dreams`, `CurrentDream`, and other global variables to meticulously track and influence quest progress, ensuring a consistent and evolving game world.

View File

@@ -0,0 +1,363 @@
# Mask Salesman
## Overview
The Mask Salesman sprite (`!SPRID = Sprite_MaskSalesman`) is a complex NPC that functions as a vendor and a quest-giver, offering masks for sale and teaching Link songs. His interactions are highly conditional, branching based on Link's inventory (Ocarina, learned songs, owned masks) and current rupee count.
## Sprite Properties
* **`!SPRID`**: `Sprite_MaskSalesman` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Impervious to all attacks)
* **`!SmallShadow`**: `01`
* **`!Shadow`**: `01`
* **`!Palette`**: `00`
* **`!Hitbox`**: `02`
* **`!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_MaskSalesman_Long`)
This routine handles the Mask Salesman's drawing and dispatches to its main logic if the sprite is active.
```asm
Sprite_MaskSalesman_Long:
{
PHB : PHK : PLB
JSR Sprite_MaskSalesman_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_MaskSalesman_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_MaskSalesman_Prep`)
This routine is empty, indicating that the Mask Salesman requires no custom initialization upon spawning.
## Main Logic & State Machine (`Sprite_MaskSalesman_Main`)
The Mask Salesman's core behavior is managed by a complex state machine that facilitates a branching dialogue and transaction system.
* **Player Collision**: Prevents Link from passing through the Mask Salesman (`JSL Sprite_PlayerCantPassThrough`).
* **`InquiryHandler`**: Plays an animation, checks Link's Ocarina status (`$7EF34C`), and displays a solicited message asking if Link wants to buy a mask. Based on Link's response (`$1CE8`) and inventory, it transitions to various states like `NoOcarina`, `HasOcarina`, `OfferBunnyHood`, `OfferStoneMask`, or `PlayerSaidNoToMask`.
* **`NoOcarina`**: Displays a message instructing Link to get the Ocarina first, then returns to `InquiryHandler`.
* **`HasOcarina`**: Displays a message acknowledging Link has the Ocarina, then transitions to `TeachLinkSong`.
* **`TeachLinkSong`**: Increments Link's learned songs count (`$7EF34C`), plays a song learned sound, and returns to `InquiryHandler`.
* **`OfferBunnyHood`**: Displays a message offering the Bunny Hood for 100 rupees, then transitions to `BoughtBunnyHood`.
* **`OfferStoneMask`**: Displays a message offering the Stone Mask for 650 rupees, then transitions to `BoughtStoneMask`.
* **`PlayerSaidNoToMask`**: Displays a message and returns to `InquiryHandler`.
* **`PlayerHasAllMasks`**: Displays a message indicating Link has all masks, then returns to `InquiryHandler`.
* **`BoughtBunnyHood`**: Processes Link's decision to buy the Bunny Hood. Checks rupee count, grants the item (`LDY #$10`, `JSL Link_ReceiveItem`), deducts rupees, displays a confirmation message, and returns to `InquiryHandler`. If rupees are insufficient, it transitions to `NotEnoughMoney`.
* **`BoughtStoneMask`**: Similar to `BoughtBunnyHood`, but for the Stone Mask (`LDY #$19`) and a higher rupee cost.
* **`NotEnoughMoney`**: Displays a message indicating insufficient funds, then returns to `InquiryHandler`.
```asm
Sprite_MaskSalesman_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw InquiryHandler
dw NoOcarina
dw HasOcarina
dw TeachLinkSong
dw OfferBunnyHood
dw OfferStoneMask
dw PlayerSaidNoToMask
dw PlayerHasAllMasks
dw BoughtBunnyHood
dw BoughtStoneMask
dw NotEnoughMoney
; 0x00
InquiryHandler:
{
%PlayAnimation(0, 1, 16)
; Player has a Lv1 Ocarina, skip to the you got it message
LDA.l $7EF34C : CMP.b #$01 : BEQ .has_ocarina
; Player has no Ocarina or Lv2 Ocarina
; Do you want to buy a mask?
%ShowSolicitedMessage($E5) : BCC .didnt_converse
LDA $1CE8 : BNE .player_said_no
; Player wants to buy a mask
LDA.l $7EF34C : CMP.b #$02 : BCS .has_song_healing
; No Ocarina yet
%GotoAction(1)
RTS
.has_ocarina
%GotoAction(2)
RTS
.has_song_healing
LDA.l $7EF348 : CMP.b #$01 : BCS .has_bunny_mask
%GotoAction(4)
RTS
.has_bunny_mask
LDA.l $7EF352 : CMP.b #$01 : BCS .has_stone_mask
%GotoAction(5)
RTS
.has_stone_mask
%GotoAction(7)
RTS
.player_said_no
%GotoAction(6)
.didnt_converse
RTS
}
; 0x01 - Link has not yet gotten the Ocarina
NoOcarina:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($E9) ; Go get the Ocarina first!
%GotoAction(0)
RTS
}
; 0x02 - Link has the Ocarina, but not all the songs
HasOcarina:
{
%PlayAnimation(0, 1, 16)
%ShowSolicitedMessage($081) ; Oh! You got it!
%GotoAction(3)
RTS
}
; 0x03
TeachLinkSong:
{
LDA #$02 : STA $7EF34C ; Increment the number of songs Link has
LDA.b #$13
STA.w $0CF8
JSL $0DBB67 ; Link_CalculateSFXPan
ORA.w $0CF8
STA $012E ; Play the song learned sound
%GotoAction(0)
RTS
}
; 0x04 - Offer Bunny Hood
OfferBunnyHood:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($07F) ; Bunny Hood for 100 rupees?
%GotoAction(8)
RTS
}
; 0x05 - Offer Stone Mask
OfferStoneMask:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($082) ; Stone Mask for 650 rupees?
%GotoAction(9)
RTS
}
; 0x06 - Player said no to buying a mask
PlayerSaidNoToMask:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($E8)
%GotoAction(0)
RTS
}
; 0x07 - Player has all the masks
PlayerHasAllMasks:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($028)
%GotoAction(0)
RTS
}
BoughtBunnyHood:
{
%PlayAnimation(0, 1, 16)
LDA $1CE8 : BNE .player_said_no
REP #$20
LDA.l $7EF360 : CMP.w #$64 ; 100 rupees
SEP #$30
BCC .not_enough_rupees
LDY.b #$10 ; Bunny Hood
STZ.w $02E9
PHX
JSL Link_ReceiveItem
PLX
REP #$20
LDA.l $7EF360
SEC : SBC.w #$64 ; Subtract 100 rupees
STA.l $7EF360
SEP #$30
%ShowUnconditionalMessage($063)
%GotoAction(0)
RTS
.not_enough_rupees
%GotoAction($0A)
RTS
.player_said_no
%GotoAction(6)
RTS
}
BoughtStoneMask:
{
%PlayAnimation(0, 1, 16)
LDA $1CE8 : BNE .player_said_no
REP #$20
LDA.l $7EF360 : CMP.w #$352 ; 850 rupees
SEP #$30
BCC .not_enough_rupees
LDY #$19 ; Stone Mask
STZ.w $02E9
PHX
JSL Link_ReceiveItem
PLX
REP #$20
LDA.l $7EF360
SEC : SBC.w #$352 ; Subtract 850 rupees
STA.l $7EF360
SEP #$30
%ShowUnconditionalMessage($055)
%GotoAction(0)
RTS
.not_enough_rupees
%GotoAction($0A)
RTS
.player_said_no
%GotoAction(6)
RTS
}
NotEnoughMoney:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($029)
%GotoAction(0)
RTS
}
}
```
## Drawing (`Sprite_MaskSalesman_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
```asm
Sprite_MaskSalesman_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 .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer
PLY : INY
PLX : DEX : BPL .nextTile
PLX
RTS
.start_index
db $00, $04
.nbr_of_tiles
db 3, 3
.x_offsets
dw -4, 12, 0, 0
dw 4, -12, 0, 0
.y_offsets
dw -8, -8, 0, -11
dw -8, -8, 0, -10
.chr
db $82, $84, $A0, $80
db $82, $84, $A0, $80
.properties
db $39, $39, $39, $39
db $79, $79, $79, $39
.sizes
db $02, $02, $02, $02
db $02, $02, $02, $02
}
```
## Design Patterns
* **Complex Dialogue and Shop System**: The Mask Salesman implements a sophisticated dialogue tree that functions as a shop, offering items (masks) and services (teaching songs) based on player choices and inventory. This creates a dynamic and interactive vendor experience.
* **Quest Gating/Progression**: Interactions with the Mask Salesman are gated by Link's possession of the Ocarina and the number of songs he has learned, integrating the NPC into the game's progression system.
* **Conditional Transactions**: The process of buying masks involves checking Link's current rupee count and deducting the cost upon a successful purchase, simulating a real in-game economy.
* **Player Choice and Branching Dialogue**: Link's responses to the Mask Salesman's inquiries directly influence the flow of conversation and the available options, leading to a personalized interaction.
* **Item Granting**: The Mask Salesman grants masks to Link and teaches him new songs, providing valuable rewards and abilities.
* **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.

View File

@@ -0,0 +1,119 @@
# Mermaid / Maple / Librarian
## Overview
The `mermaid.asm` file is a highly versatile sprite definition that implements three distinct NPC characters: the "Mermaid," "Maple," and "Librarian." This multi-purpose sprite leverages `SprSubtype` and `SprMiscE, X` to dispatch to different behaviors and drawing routines, allowing for efficient resource reuse and complex, context-sensitive interactions within the game world.
## Sprite Properties
* **`!SPRID`**: `Sprite_Mermaid` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!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_Mermaid_Long`)
This routine acts as a central dispatcher, selecting the appropriate drawing routine based on `SprMiscE, X` (0 for Mermaid, 1 for Maple, 2 for Librarian). It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_Mermaid_Long:
{
PHB : PHK : PLB
LDA.w SprMiscE, X : BEQ .MermaidDraw
CMP.b #$02 : BEQ .LibrarianDraw
JSR Sprite_Maple_Draw
JMP .Continue
.LibrarianDraw
JSR Sprite_Librarian_Draw
JMP .Continue
.MermaidDraw
JSR Sprite_Mermaid_Draw
.Continue
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Mermaid_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Mermaid_Prep`)
This routine initializes the sprite upon spawning. It sets `SprDefl, X`, `SprTimerA, X`, and `SprHitbox, X`. Crucially, it sets `SprMiscE, X` based on `SprSubtype, X` to determine which character the sprite will represent (0 for Mermaid, 1 for Maple, 2 for Librarian).
```asm
Sprite_Mermaid_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.b #$40 : STA.w SprTimerA, X
LDA.b #$07 : STA.w SprHitbox, X
; Mermaid Sprite
STZ.w SprMiscE, X
; Maple Sprite
LDA.w SprSubtype, X : CMP.b #$01 : BNE +
LDA.b #$01 : STA.w SprMiscE, X
+
; Librarian Sprite
CMP.b #$02 : BNE ++
LDA.b #$02 : STA.w SprMiscE, X
++
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Mermaid_Main`)
This routine acts as a dispatcher for the main logic, calling the appropriate handler (`MermaidHandler`, `MapleHandler`, or `LibrarianHandler`) based on `SprMiscE, X`.
### `MermaidHandler`
Manages the Mermaid's behavior through a state machine:
* **`MermaidWait`**: Plays an idle animation, prevents player passage, and displays a message on contact. Upon message dismissal, it transitions to `MermaidDive`.
* **`MermaidDive`**: Plays a diving animation, moves horizontally, and transitions to `MermaidSwim` after a timer.
* **`MermaidSwim`**: Plays a swimming animation, moves, sets `SprXSpeed, X`, spawns a splash effect, and can despawn or change direction after a timer.
### `LibrarianHandler`
Manages the Librarian's behavior, primarily focused on a map and scroll translation quest:
* **`LibrarianIdle`**: Plays an animation, prevents player passage, and displays messages based on whether Link has no maps, all maps, or new scrolls. Transitions to `Librarian_CheckResponse` if new scrolls are available.
* **`Librarian_CheckResponse`**: Processes Link's response to the translation offer, transitioning to `Librarian_OfferTranslation` or back to `LibrarianIdle`.
* **`Librarian_OfferTranslation`**: Displays a message, prevents player passage, and checks `Scrolls` and `DNGMAP1`/`DNGMAP2` to identify new scrolls. If found, it updates `Scrolls`, sets `SprMiscG, X` to the scroll ID, and transitions to `Librarian_TranslateScroll`.
* **`Librarian_TranslateScroll`**: Displays a message based on the scroll ID and transitions to `Librarian_FinishTranslation`.
* **`Librarian_FinishTranslation`**: Displays a final message and returns to `LibrarianIdle`.
## `Librarian_CheckForAllMaps` and `Librarian_CheckForNoMaps`
These helper routines check `DNGMAP1` and `DNGMAP2` (SRAM flags for dungeon maps) to determine Link's map collection status.
## Drawing (`Sprite_Mermaid_Draw`, `Sprite_Maple_Draw`, `Sprite_Librarian_Draw`)
Each character has its own dedicated drawing routine. These routines handle OAM allocation and animation, and explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each routine contains its own specific OAM data for rendering the respective character.
## Design Patterns
* **Multi-Character Sprite (Conditional Drawing/Logic)**: A single sprite definition (`Sprite_Mermaid`) is used to represent three distinct NPCs (Mermaid, Maple, Librarian) based on `SprSubtype` and `SprMiscE`, showcasing efficient resource utilization and context-sensitive character representation.
* **Quest Progression Integration**: The Librarian's dialogue and actions are tied to collected dungeon maps and scrolls, indicating its role in a translation quest, driving narrative progression.
* **Context-Sensitive Dialogue**: The Librarian's messages dynamically change based on whether Link has maps, all maps, or new scrolls, providing a personalized and evolving interaction.
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make NPCs solid objects that Link cannot walk through.
* **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.

View File

@@ -0,0 +1,181 @@
# Piratian
## Overview
The Piratian sprite (`!SPRID = $0E`) represents an NPC that initially behaves in a friendly manner, engaging Link through dialogue and moving randomly. However, it possesses an "aggro" system, becoming hostile and attacking Link if provoked. A unique aspect of this sprite is its dynamic health scaling, which adjusts based on Link's current Sword level.
## Sprite Properties
* **`!SPRID`**: `$0E` (Vanilla sprite ID, likely for a generic NPC)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `00` (Initially harmful, but `Sprite_Piratian_Friendly` suggests otherwise)
* **`!HVelocity`**: `00`
* **`!Health`**: `00` (Dynamically set in `_Prep`)
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!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_Piratian_Long`)
This routine handles the Piratian's drawing, shadow rendering, and dispatches to its main logic if the sprite is active.
```asm
Sprite_Piratian_Long:
{
PHB : PHK : PLB
JSR Sprite_Piratian_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Piratian_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Piratian_Prep`)
This routine initializes the Piratian upon spawning. Its health (`SprHealth, X`) is dynamically set based on Link's current Sword level (`$7EF359`), providing a form of difficulty scaling. `SprMiscA, X` is initialized to `0` (likely an aggro flag), and a specific bit of `SprNbrOAM, X` is set, potentially for drawing behavior.
```asm
Sprite_Piratian_Prep:
{
PHB : PHK : PLB
LDA.l $7EF359 : TAY
LDA.w .health, Y : STA.w SprHealth, X
STZ.w SprMiscA, X
LDA.w SprNbrOAM, X : ORA.b #$80 : STA.w SprNbrOAM, X
PLB
RTL
.health
db $08, $0A, $0C, $0F
}
```
## Main Logic & State Machine (`Sprite_Piratian_Main`)
This routine orchestrates the Piratian's movement and animation, calling `Sprite_Piratian_Move` and then dispatching to various animation states based on `SprAction, X`.
* **`Piratian_MoveDown` / `Up` / `Left` / `Right`**: Each state plays a specific walking animation.
* **`SkullHead`**: Plays a specific animation, likely when the Piratian is defeated or in a particular state.
```asm
Sprite_Piratian_Main:
{
JSR Sprite_Piratian_Move
LDA.w SprAction, X
JSL JumpTableLocal
dw Piratian_MoveDown
dw Piratian_MoveUp
dw Piratian_MoveLeft
dw Piratian_MoveRight
dw SkullHead
Piratian_MoveDown:
{
%PlayAnimation(0,1,16)
RTS
}
Piratian_MoveUp:
{
%PlayAnimation(2,3,16)
RTS
}
Piratian_MoveLeft:
{
%PlayAnimation(4,5,16)
RTS
}
Piratian_MoveRight:
{
%PlayAnimation(6,7,16)
RTS
}
SkullHead:
{
%PlayAnimation(8,9,16)
RTS
}
}
```
## Movement and Interaction (`Sprite_Piratian_Move`)
This routine handles the Piratian's movement, collision, damage reactions, and implements its "aggro" system.
* **Random Movement**: If `SprTimerA, X` is `0`, the Piratian randomly selects a new direction (`Sprite_SelectNewDirection`) and updates its animation state (`SprAction, X`).
* **Physics & Collision**: Moves the sprite (`JSL Sprite_MoveXyz`), handles tile collision (`JSL Sprite_BounceFromTileCollision`), damage flash (`JSL Sprite_DamageFlash_Long`), and interaction with thrown sprites (`JSL ThrownSprite_TileAndSpriteInteraction_long`).
* **Aggro Logic**: If the Piratian takes damage from Link (`JSL Sprite_CheckDamageFromPlayer`), it sets `SprMiscA, X` to `01` (aggro flag), changes its drawing behavior (`SprNbrOAM, X`), sets timers, and begins to attack Link (`Sprite_ProjectSpeedTowardsPlayer`, `Sprite_CheckDamageToPlayer`).
* **Friendly Behavior**: If not in an aggro state, it calls `Sprite_Piratian_Friendly` for dialogue interaction.
```asm
Sprite_Piratian_Move:
{
LDA.w SprTimerA, X : BNE +
JSL Sprite_SelectNewDirection
TYA
CMP.b #$03 : BCC ++
SEC : SBC.b #$03
++
STA.w SprAction, X
+
JSL Sprite_MoveXyz
JSL Sprite_BounceFromTileCollision
JSL Sprite_DamageFlash_Long
JSL ThrownSprite_TileAndSpriteInteraction_long
JSL Sprite_CheckDamageFromPlayer : BCC .no_dano
LDA.b #$01 : STA.w SprMiscA, X
LDA.w SprNbrOAM, X : AND.b #$7F : STA.w SprNbrOAM, X
%SetTimerA($60)
%SetTimerF($20)
.no_dano
LDA.w SprMiscA, X : BEQ .no_aggro
LDA.b #$10 : STA.w SprTimerA, X
LDA.b #$08
JSL Sprite_ProjectSpeedTowardsPlayer
JSL Sprite_CheckDamageToPlayer
JMP .return
.no_aggro
JSR Sprite_Piratian_Friendly
.return
RTS
}
```
## Friendly Interaction (`Sprite_Piratian_Friendly`)
This routine handles the Piratian's friendly dialogue. If `SprTimerD, X` is `0`, it displays a message on contact (`%ShowMessageOnContact($01BB)`). Upon message dismissal, it sets `SprTimerD, X` to `$FF`.
## Drawing (`Sprite_Piratian_Draw`)
The drawing routine uses the `%DrawSprite()` macro to render the Piratian's graphics based on defined OAM data tables.
## Design Patterns
* **Dynamic Health Scaling**: The Piratian's health is dynamically adjusted based on Link's current Sword level, providing a form of adaptive difficulty.
* **Aggro System**: The Piratian features an "aggro" system where it transitions from a friendly, dialogue-based NPC to a hostile enemy if Link attacks it, adding depth to interactions.
* **Random Movement**: The Piratian moves randomly, contributing to its NPC-like behavior and making its movements less predictable.
* **NPC Interaction**: The Piratian can be interacted with through dialogue when in its friendly state, offering context or hints.
* **Conditional Behavior**: The sprite's behavior changes significantly based on its "friendly" or "aggro" state, demonstrating complex state management.
* **16-bit OAM Calculations**: Although `%DrawSprite()` is used, the underlying drawing routines likely utilize `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,95 @@
# Ranch Girl
## Overview
The `ranch_girl.asm` file defines the behavior for the "Ranch Girl" NPC, who is involved in a "Chicken Easter Egg" quest. This character plays a crucial role in granting Link the Ocarina and teaching him a song. Her behavior is implemented as a vanilla override, modifying the existing `ChickenLady` routine.
## Vanilla Overrides
This file directly modifies the vanilla `ChickenLady` routine at `org $01AFECF` to inject custom logic for the Ranch Girl's interactions.
## `RanchGirl_Message`
This routine handles the dialogue displayed by the Ranch Girl. It checks Link's Ocarina status (`$7EF34C`).
* If Link already possesses the Ocarina, it displays message `$010E`.
* Otherwise, it displays message `$017D` and sets `SprMiscD, X` to `$01`, indicating the start of the Ocarina quest.
```asm
RanchGirl_Message:
{
LDA $7EF34C : CMP.b #$01 : BCS .has_ocarina
%ShowUnconditionalMessage($017D)
LDA #$01 : STA.w SprMiscD, X
RTL
.has_ocarina
%ShowUnconditionalMessage($010E)
RTL
}
```
## `RanchGirl_TeachSong`
This routine is responsible for teaching Link a song (specifically the "Song of Storms") and granting him the Ocarina. It checks the Ocarina quest flag (`SprMiscD, X`) and Link's current Ocarina status (`$7EF34C`).
* If the conditions are met, it plays the "Song of Storms" sound, gives Link the Ocarina (`LDY #$14`, `JSL Link_ReceiveItem`), and sets `$7EF34C` to `$01`.
```asm
RanchGirl_TeachSong:
{
LDA.w SprMiscD, X : CMP.b #$01 : BNE .not_started
LDA $10 : CMP.b #$0E : BEQ .running_dialog
LDA $7EF34C : CMP.b #$01 : BCS .has_song
; Play the song of storms
LDA.b #$2F
STA.w $0CF8
JSL $0DBB67 ; Link_CalculateSFXPan
ORA.w $0CF8
STA $012E ; Play the song learned sound
; Give Link the Ocarina
LDY #$14
; Clear the item receipt ID
STZ $02E9
PHX
JSL Link_ReceiveItem
PLX
LDA #$01 : STA $7EF34C ; The item gives 02 by default, so decrement that for now
.not_started
.running_dialog
.has_song
LDA.b $1A : LSR #4 : AND.b #$01 : STA.w $0DC0,X
RTL
}
```
## `ChickenLady` (Vanilla Override)
This is the main entry point for the Ranch Girl's custom behavior, overriding the vanilla `ChickenLady` routine. It sets `SprMiscC, X` to `$01`, calls vanilla drawing and activity check routines, and then executes `RanchGirl_Message` and `RanchGirl_TeachSong`.
```asm
org $01AFECF
ChickenLady:
{
JSR .main
RTL
.main
LDA.b #$01 : STA.w SprMiscC, X
JSL SpriteDraw_RaceGameLady
JSR Sprite_CheckIfActive_Bank1A
LDA.w SprTimerA, X : CMP.b #$01 : BNE .no_message
JSL RanchGirl_Message
.no_message
JSL RanchGirl_TeachSong
.return
RTS
}
```
## Design Patterns
* **Vanilla Override**: This file directly modifies a vanilla routine (`ChickenLady`) to implement custom NPC behavior, demonstrating a common ROM hacking technique.
* **Quest Gating/Progression**: The Ranch Girl's dialogue and the granting of the Ocarina are tied to Link's possession of the Ocarina and the state of the Ocarina quest, integrating her into the game's progression system.
* **Item Granting**: The Ranch Girl serves as a source for the Ocarina, a key item in the game.
* **Game State Manipulation**: Directly modifies `$7EF34C` (Ocarina flag) and `SprMiscD, X` (quest flag) to track and influence game state.

250
Docs/Sprites/NPCs/Tingle.md Normal file
View File

@@ -0,0 +1,250 @@
# Tingle
## Overview
The Tingle sprite (`!SPRID = Sprite_Tingle`) implements the iconic map salesman character. Tingle's primary function is to sell a map to Link, with his interactions and dialogue flow being conditional on Link's rupee count and whether the map has already been purchased.
## Sprite Properties
* **`!SPRID`**: `Sprite_Tingle` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Impervious to all attacks)
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `01`
* **`!Palette`**: `00`
* **`!Hitbox`**: `02`
* **`!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_Tingle_Long`)
This routine handles Tingle's drawing, shadow rendering, and dispatches to his main logic if the sprite is active.
```asm
Sprite_Tingle_Long:
{
PHB : PHK : PLB
JSR Sprite_Tingle_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Tingle_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Tingle_Prep`)
This routine is empty, indicating that Tingle requires no custom initialization upon spawning.
## Main Logic & State Machine (`Sprite_Tingle_Main`)
Tingle's core behavior is managed by a state machine that facilitates a dialogue and transaction flow for selling a map.
* **Player Collision**: Prevents Link from passing through Tingle (`JSL Sprite_PlayerCantPassThrough`).
* **`Tingle_Idle`**: Plays an animation. Checks if Link has already bought the map (`$7EF3D6` bit `$01`). If so, it transitions to `Tingle_AlreadyBoughtMap`. Otherwise, it displays a solicited message (`%ShowSolicitedMessage($01A4)`) asking if Link wants to buy a map. Based on Link's response (`$1CE8`), it transitions to `Tingle_BuyMap` or `Tingle_PlayerSaidNo`.
* **`Tingle_BuyMap`**: Plays an animation. Checks if Link has enough rupees (`$7EF360`). If sufficient, it deducts the rupees, sets the map bought flag (`$7EF3D6` bit `$01`), displays a confirmation message (`%ShowUnconditionalMessage($01A5)`), and transitions to `Tingle_AlreadyBoughtMap`. If rupees are insufficient, it transitions to `Tingle_NotEnoughMoney`.
* **`Tingle_PlayerSaidNo`**: Plays an animation, displays a message (`%ShowUnconditionalMessage($01A6)`), and returns to `Tingle_Idle`.
* **`Tingle_AlreadyBoughtMap`**: Plays an animation, displays a message (`%ShowUnconditionalMessage($01A3)`) confirming the map has been bought, and returns to `Tingle_Idle`.
* **`Tingle_NotEnoughMoney`**: Plays an animation, displays a message (`%ShowUnconditionalMessage($029)`) about insufficient funds, and returns to `Tingle_Idle`.
```asm
Sprite_Tingle_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw Tingle_Idle
dw Tingle_BuyMap
dw Tingle_PlayerSaidNo
dw Tingle_AlreadyBoughtMap
dw Tingle_NotEnoughMoney
; 0x00
Tingle_Idle:
{
%PlayAnimation(0, 1, 16)
; Player has already bought the map
LDA.l $7EF3D6 : AND.b #$01 : BNE .already_bought_map
%ShowSolicitedMessage($01A4) : BCC .didnt_converse
LDA $1CE8 : BEQ .buy_map
; Player said no
%GotoAction(2)
RTS
.buy_map
%GotoAction(1)
RTS
.already_bought_map
%GotoAction(3)
RTS
.didnt_converse
RTS
}
; 0x01
Tingle_BuyMap:
{
%PlayAnimation(0, 1, 16)
REP #$20
LDA.l $7EF360 : CMP.w #$0064 ; 100 rupees
SEP #$30
BCC .not_enough_money
; Deduct rupees
REP #$20
LDA.l $7EF360
SEC : SBC.w #$0064
STA.l $7EF360
SEP #$30
; Set map bought flag
LDA.l $7EF3D6 : ORA.b #$01 : STA.l $7EF3D6
%ShowUnconditionalMessage($01A5)
%GotoAction(3)
RTS
.not_enough_money
%GotoAction(4)
RTS
}
; 0x02
Tingle_PlayerSaidNo:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($01A6)
%GotoAction(0)
RTS
}
; 0x03
Tingle_AlreadyBoughtMap:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($01A3)
%GotoAction(0)
RTS
}
; 0x04
Tingle_NotEnoughMoney:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($029)
%GotoAction(0)
RTS
}
}
```
## Drawing (`Sprite_Tingle_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
```asm
Sprite_Tingle_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 .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer
PLY : INY
PLX : DEX : BPL .nextTile
PLX
RTS
.start_index
db $00, $04
.nbr_of_tiles
db 3, 3
.x_offsets
dw -4, 12, 0, 0
dw 4, -12, 0, 0
.y_offsets
dw -8, -8, 0, -11
dw -8, -8, 0, -10
.chr
db $82, $84, $A0, $80
db $82, $84, $A0, $80
.properties
db $39, $39, $39, $39
db $79, $79, $79, $39
.sizes
db $02, $02, $02, $02
db $02, $02, $02, $02
}
```
## Design Patterns
* **Shop System**: Tingle implements a basic shop system for selling a map, including price checks and rupee deduction, providing a functional in-game vendor.
* **Quest Gating/Progression**: The availability of the map and Tingle's dialogue are conditional on whether Link has already purchased the map (`$7EF3D6` bit `$01`), ensuring a logical progression of events.
* **Conditional Transactions**: The process of buying the map involves checking Link's rupee count and deducting the cost upon a successful purchase, simulating a real in-game economy.
* **Player Choice and Branching Dialogue**: Link's responses (`$1CE8`) to Tingle's inquiries directly influence the flow of conversation and the available options, leading to a personalized interaction.
* **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.

210
Docs/Sprites/NPCs/Vasu.md Normal file
View File

@@ -0,0 +1,210 @@
# Vasu / Error
## Overview
The `vasu.asm` file defines the behavior for a multi-character NPC sprite that can represent either "Vasu," the ring appraiser, or a special "Error" sprite. The specific character displayed and its interactions are determined by `SprSubtype, X`. Vasu offers a service to appraise rings, with conditional dialogue and transactions based on Link's inventory and rupee count.
## Sprite Properties
* **`!SPRID`**: `Sprite_Vasu` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `03`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `09`
* **`!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_Vasu_Long`)
This routine acts as a dispatcher for drawing, selecting `Sprite_Vasu_Draw` for Vasu (`SprSubtype, X = 0`) or `Sprite_Error_Draw` for the Error sprite. It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_Vasu_Long:
{
PHB : PHK : PLB
LDA.w SprSubtype, X : BNE +
JSR Sprite_Vasu_Draw
JMP ++
+
JSR Sprite_Error_Draw
++
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Vasu_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Vasu_Prep`)
This routine initializes the sprite upon spawning. It sets `SprDefl, X` to `$80`. If the sprite is Vasu (`SprSubtype, X = 0`), it sets `SprAction, X` to `$04`.
```asm
Sprite_Vasu_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.w SprSubtype, X : BEQ +
LDA.b #$04 : STA.w SprAction, X
+
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Vasu_Main`)
This routine manages the various states and interactions for both Vasu and the Error sprite.
* **Player Collision**: Prevents Link from passing through (`JSL Sprite_PlayerCantPassThrough`).
* **`Vasu_Idle`**: Plays an animation and displays a solicited message (`%ShowSolicitedMessage($00A9)`). Upon message dismissal, it transitions to `Vasu_MessageHandler`.
* **`Vasu_MessageHandler`**: Plays an animation and processes Link's choice (`MsgChoice`). It can lead to `Vasu_AppraiseRing`, `Vasu_ExplainRings` (displays message `$00AA` and returns to `Vasu_Idle`), or return to `Vasu_Idle` if Link chooses "nevermind."
* **`Vasu_AppraiseRing`**: Plays an animation. Checks `FOUNDRINGS` (SRAM flag for found rings). If no rings are found, it displays a message (`%ShowUnconditionalMessage($00AD)`) and returns to `Vasu_Idle`. If rings are found, it checks `MAGICRINGS` (SRAM flag for owned rings). If Link has no rings yet, it offers the first appraisal for free (`%ShowUnconditionalMessage($00AB)`). Otherwise, it checks for 20 rupees (`$7EF360`). If Link has enough, it deducts the rupees, updates `MAGICRINGS` by ORing with `FOUNDRINGS`, and transitions to `Vasu_RingAppraised`. If not enough rupees, it displays a message (`%ShowUnconditionalMessage($0189)`) and returns to `Vasu_Idle`.
* **`Vasu_RingAppraised`**: Plays an animation, displays a message (`%ShowUnconditionalMessage($00AC)`), and returns to `Vasu_Idle`.
* **`Error_Idle`**: Plays an animation and displays a solicited message (`%ShowSolicitedMessage($0121)`) "I am Error." Upon message dismissal, it randomly sets `FOUNDRINGS`.
```asm
Sprite_Vasu_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw Vasu_Idle
dw Vasu_MessageHandler
dw Vasu_AppraiseRing
dw Vasu_RingAppraised
dw Error_Idle
Vasu_Idle:
{
%PlayAnimation(0,1,20)
%ShowSolicitedMessage($00A9) : BCC .didnt_talk
%GotoAction(1)
.didnt_talk
RTS
}
Vasu_MessageHandler:
{
%PlayAnimation(0,1,20)
LDA.w MsgChoice : BEQ .appraise_rings
CMP.b #$01 : BEQ .explain_rings
; Player said nevermind.
%GotoAction(0)
RTS
.explain_rings
%ShowUnconditionalMessage($00AA)
%GotoAction(0)
RTS
.appraise_rings
LDA.b #$40 : STA.w SprTimerB, X
%GotoAction(2)
RTS
}
Vasu_AppraiseRing:
{
%PlayAnimation(0,1,20)
; Check if the player has found any rings to appraise
REP #$30
LDA.l FOUNDRINGS
AND.w #$00FF
SEP #$30
BEQ .no_rings
; Check if the player has any rings, if not give them one for free
LDA.l MAGICRINGS : BEQ .no_rings_yet
REP #$20
LDA.l $7EF360
CMP.w #$14 ; 20 rupees
SEP #$30
BCC .not_enough_rupees
REP #$20
LDA.l $7EF360
SEC
SBC.w #$14 ; Subtract 20 rupees
STA.l $7EF360
SEP #$30
JMP .appraise_me
.not_enough_rupees
%ShowUnconditionalMessage($0189) ; 'You don't have enough rupees!'
%GotoAction(0)
RTS
.no_rings_yet
%ShowUnconditionalMessage($00AB) ; 'First one is free!'
JMP .appraise_me
.no_rings
%ShowUnconditionalMessage($00AD) ; 'You don't have any rings!'
%GotoAction(0)
RTS
.appraise_me
; Check the found rings and set the saved rings
; Get the bit from found rings and set it in MAGICRINGS
LDA.l FOUNDRINGS
ORA.l MAGICRINGS
STA.l MAGICRINGS
%GotoAction(3)
RTS
}
Vasu_RingAppraised:
{
%PlayAnimation(0,1,20)
%ShowUnconditionalMessage($00AC) ; 'Come back later for more appraisals!'
%GotoAction(0)
RTS
}
Error_Idle:
{
%PlayAnimation(0,1,24)
; "I am Error"
%ShowSolicitedMessage($0121) : BCC +
JSL GetRandomInt : AND.b #$06 : STA.l FOUNDRINGS
+
RTS
}
}
```
## Drawing (`Sprite_Vasu_Draw` and `Sprite_Error_Draw`)
Both drawing routines handle OAM allocation and animation for their respective characters. They explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each routine contains its own specific OAM data for rendering the character.
## Design Patterns
* **Multi-Character Sprite (Conditional Drawing/Logic)**: A single sprite definition (`Sprite_Vasu`) is used to represent two distinct characters (Vasu and the "Error" sprite) based on `SprSubtype`, showcasing efficient resource utilization and varied visual appearances.
* **Shop/Service System**: Vasu implements a service where he appraises rings for a fee (or for free the first time), integrating a transactional element into NPC interactions.
* **Quest Gating/Progression**: Vasu's interactions are conditional on Link having found rings (`FOUNDRINGS`) and his rupee count, ensuring that the appraisal service is available only when relevant.
* **Conditional Transactions**: The appraisal process involves checking Link's rupee count and deducting the cost, simulating a real in-game economy.
* **Player Choice and Branching Dialogue**: Link's choices (`MsgChoice`) directly influence the flow of conversation, leading to different outcomes and information from Vasu.
* **Item Management**: Vasu interacts with `FOUNDRINGS` and `MAGICRINGS` (SRAM flags) to manage Link's ring collection, updating his inventory based on appraisals.
* **Easter Egg/Hidden Content**: The "Error" sprite, with its unique dialogue, likely serves as an Easter egg or a placeholder for debugging, adding a touch of humor or mystery.
* **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.

View File

@@ -0,0 +1,263 @@
# Village Dog / Eon Dog
## Overview
The `village_dog.asm` file defines the behavior for two distinct dog NPCs: the "Village Dog" and the "Eon Dog." Their appearance and behavior are dynamically determined by the global `WORLDFLAG`. These dogs exhibit random movement, react to Link's proximity, can be lifted and thrown, and offer context-sensitive dialogue based on Link's current form.
## Sprite Properties
* **`!SPRID`**: `Sprite_VillageDog` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `08`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `01`
* **`!Palette`**: `00`
* **`!Hitbox`**: `09`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `01` (Checks both layers for collision)
* **`!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_VillageDog_Long`)
This routine acts as a dispatcher for drawing, selecting `Sprite_VillageDog_Draw` for the Village Dog (`WORLDFLAG = 0`) or `Sprite_EonDog_Draw` for the Eon Dog. It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_VillageDog_Long:
{
PHB : PHK : PLB
LDA.w WORLDFLAG : BEQ .village
JSR Sprite_EonDog_Draw
JMP +
.village
JSR Sprite_VillageDog_Draw
+
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_VillageDog_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_VillageDog_Prep`)
This routine initializes the dog upon spawning. If it's an Eon Dog (`WORLDFLAG` is not `0`), it sets `SprAction, X` to `$07` and `SprTimerA, X` to `$40`.
```asm
Sprite_VillageDog_Prep:
{
PHB : PHK : PLB
LDA.w WORLDFLAG : BEQ .village
LDA.b #$07 : STA.w SprAction, X
LDA.b #$40 : STA.w SprTimerA, X
.village
PLB
RTL
}
```
## `HandleTossedDog`
This routine manages the vertical movement of a dog that has been tossed. If `SprHeight, X` is not `0`, it decrements it, simulating gravity.
## `LiftOrTalk`
This routine determines whether Link can lift the dog or engage in dialogue. It checks Link's current form (`$02B2`). If Link is in Wolf or Minish form, it calls `ShowMessageIfMinish`. Otherwise, it checks if the dog is lifted (`JSL Sprite_CheckIfLifted`) and handles interactions with thrown sprites (`JSL ThrownSprite_TileAndSpriteInteraction_long`).
## Main Logic & State Machine (`Sprite_VillageDog_Main`)
This routine manages the various states and behaviors of both Village Dogs and Eon Dogs.
* **`Dog_Handler`**: Plays a sitting animation, calls `HandleTossedDog`, and if Link is nearby, sets a timer and transitions to `Dog_LookLeftAtLink` or `Dog_LookRightAtLink`. It also calls `LiftOrTalk`.
* **`Dog_LookLeftAtLink` / `Dog_LookRightAtLink`**: Plays an animation of the dog looking towards Link. After a timer, it transitions to `Dog_MoveLeftTowardsLink` or `Dog_MoveRightTowardsLink`.
* **`Dog_MoveLeftTowardsLink` / `Dog_MoveRightTowardsLink`**: Plays a walking animation, calls `HandleTossedDog`, and if Link is nearby, transitions to `Dog_WagTailLeft` or `Dog_WagTailRight`. It handles tile collisions, applies speed towards Link, and calls `LiftOrTalk`. After a timer, it returns to `Dog_Handler`.
* **`Dog_WagTailLeft` / `Dog_WagTailRight`**: Plays a wagging tail animation, calls `LiftOrTalk` and `HandleTossedDog`. After a timer, it returns to `Dog_Handler`.
* **`EonDog_Handler` / `EonDog_Right`**: These states are specific to the Eon Dog, playing animations and calling `EonDog_Walk`.
```asm
Sprite_VillageDog_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw Dog_Handler ; 00
dw Dog_LookLeftAtLink ; 01
dw Dog_LookRightAtLink ; 02
dw Dog_MoveLeftTowardsLink ; 03
dw Dog_MoveRightTowardsLink ; 04
dw Dog_WagTailLeft ; 05
dw Dog_WagTailRight ; 06
dw EonDog_Handler ; 07
dw EonDog_Right ; 08
; 0
Dog_Handler:
{
%PlayAnimation(8,8,8) ; Sitting
JSR HandleTossedDog
LDA $0309 : AND #$03 : BNE .lifting
LDA #$20 : STA.w SprTimerD, X
JSL Sprite_IsToRightOfPlayer : TYA : BEQ .walk_right
%GotoAction(1)
JMP .lifting
.walk_right
%GotoAction(2)
.lifting
JSR LiftOrTalk
JSL Sprite_Move
RTS
}
; 01
Dog_LookLeftAtLink:
{
%PlayAnimation(9,9,8)
JSR HandleTossedDog
LDA.w SprTimerD, X : BNE +
; Load the timer for the run
LDA.b #$60 : STA.w SprTimerD, X
%GotoAction(3)
+
RTS
}
; 02
Dog_LookRightAtLink:
{
%PlayAnimation(10,10,8)
JSR HandleTossedDog
LDA.w SprTimerD, X : BNE +
; Load the timer for the run
LDA.b #$60 : STA.w SprTimerD, X
%GotoAction(4)
+
RTS
}
; 03
Dog_MoveLeftTowardsLink:
{
%PlayAnimation(2,4,6)
JSR HandleTossedDog
; Check if the dog is near link, then wag the tail
JSR CheckIfPlayerIsNearby : BCC +
%GotoAction(5)
+
; Check for collision
JSL Sprite_CheckTileCollision
LDA $0E70, X : BEQ .no_collision
%GotoAction(0)
.no_collision
LDA.b #$0A
JSL Sprite_ApplySpeedTowardsPlayer
STZ $06 : STZ $07
JSL Sprite_MoveLong
JSR LiftOrTalk
LDA.w SprTimerD, X : BNE +
%GotoAction(0)
+
RTS
}
; 04
Dog_MoveRightTowardsLink:
{
%PlayAnimation(5,7,6)
JSR HandleTossedDog
JSR CheckIfPlayerIsNearby : BCC +
%GotoAction(6)
+
; Check for collision
JSL Sprite_CheckTileCollision
LDA $0E70, X : BEQ .no_collision
%GotoAction(0)
.no_collision
LDA.b #$0A
JSL Sprite_ApplySpeedTowardsPlayer
STZ $06 : STZ $07
JSL Sprite_MoveLong
JSR LiftOrTalk
LDA.w SprTimerD, X : BNE ++
%GotoAction(0)
++
RTS
}
; 05
Dog_WagTailLeft:
{
%PlayAnimation(0,1, 8)
JSR LiftOrTalk
JSR HandleTossedDog
LDA.w SprTimerD, X : BNE +
%GotoAction(0)
+
RTS
}
; 06
Dog_WagTailRight:
{
%PlayAnimation(11,12,8)
JSR LiftOrTalk
JSR HandleTossedDog
LDA.w SprTimerD, X : BNE +
%GotoAction(0)
+
RTS
}
EonDog_Handler:
{
%PlayAnimation(0,1,8)
JSR EonDog_Walk
RTS
}
EonDog_Right:
{
%PlayAnimation(2,3,8)
JSR EonDog_Walk
RTS
}
}
```
## `EonDog_Walk`
This routine handles the Eon Dog's random movement. It moves the sprite (`JSL Sprite_MoveLong`), handles tile collision (`JSL Sprite_BounceFromTileCollision`), and after a timer, randomly selects a new direction and updates its speed and action.
## `CheckIfPlayerIsNearby`
This routine checks if Link is within a specific rectangular area around the dog, returning with the carry flag set if true.
## `ShowMessageIfMinish`
This routine displays a context-sensitive message based on Link's current form (`$02B2`). If Link is in Minish form, it displays message `$18`; otherwise, it displays message `$1B`.
## Drawing (`Sprite_VillageDog_Draw` and `Sprite_EonDog_Draw`)
Both drawing routines handle OAM allocation and animation for their respective dog types. They explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each routine contains its own specific OAM data for rendering the character.
## Design Patterns
* **Multi-Character NPC (Conditional Drawing/Logic)**: A single sprite definition (`Sprite_VillageDog`) is used to represent two distinct dog characters (Village Dog and Eon Dog) based on `WORLDFLAG`, showcasing efficient resource utilization and varied visual appearances.
* **Random Movement**: The dogs exhibit random movement patterns, contributing to the environmental ambiance and making their movements less predictable.
* **Player Interaction**: The dogs can be lifted and thrown (`LiftOrTalk`), and they react to Link's presence by looking at him and wagging their tails, adding to the interactive elements of the game world.
* **Conditional Dialogue**: The `ShowMessageIfMinish` routine provides context-sensitive dialogue based on Link's current form, enhancing the narrative and player experience.
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make the dogs solid objects that Link cannot walk through.
* **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.

View File

@@ -0,0 +1,38 @@
# Village Elder
## Overview
The `village_elder.asm` file defines the behavior for the "Village Elder" NPC. This is a straightforward NPC whose primary function is to interact with Link through dialogue. The content of the dialogue is conditional, changing based on whether Link has previously met the elder, as tracked by a custom progression flag (`OOSPROG`).
## Main Logic (`Sprite_VillageElder_Main`)
This routine manages the Village Elder's interactions and dialogue flow:
* **Animation**: Plays a specific animation (`%PlayAnimation(2,3,16)`).
* **Player Collision**: Prevents Link from passing through the elder (`JSL Sprite_PlayerCantPassThrough`).
* **Progression Check (`OOSPROG`)**: It checks the `OOSPROG` (Oracle of Secrets Progression) flag. Specifically, it checks if bit `$10` is set, which indicates that Link has already met the elder.
* **First Meeting**: If Link has not yet met the elder, it displays a solicited message (`%ShowSolicitedMessage($143)`). Upon dismissal of this message, it sets bit `$10` in `OOSPROG` to mark that Link has now met the elder.
* **Subsequent Meetings**: If Link has already met the elder, a different solicited message (`%ShowSolicitedMessage($019)`) is displayed.
```asm
Sprite_VillageElder_Main:
{
%PlayAnimation(2,3,16)
JSL Sprite_PlayerCantPassThrough
REP #$30
LDA.l OOSPROG : AND.w #$00FF
SEP #$30
AND.b #$10 : BNE .already_met
%ShowSolicitedMessage($143) : BCC .no_message
LDA.l OOSPROG : ORA.b #$10 : STA.l OOSPROG
.no_message
RTS
.already_met
%ShowSolicitedMessage($019)
RTS
}
```
## Design Patterns
* **Simple NPC Interaction**: The Village Elder provides basic dialogue interaction with Link.
* **Quest Progression Tracking**: Utilizes a custom progression flag (`OOSPROG`) to track whether Link has met the elder, allowing for dynamic changes in dialogue based on game state.
* **Player Collision**: Implements `JSL Sprite_PlayerCantPassThrough` to make the elder a solid object that Link cannot walk through.

218
Docs/Sprites/NPCs/Zora.md Normal file
View File

@@ -0,0 +1,218 @@
# Zora (Generic Sea Zora Handler)
## Overview
The `zora.asm` file serves as a centralized handler for various Zora NPCs within the game. It acts as a dispatcher, directing execution to specific drawing and main logic routines for the Zora Princess, Eon Zora, Eon Zora Elder, and a generic Sea Zora. This dynamic dispatch is based on the current `ROOM`, `WORLDFLAG`, and `SprSubtype`, allowing for a single sprite definition to manage a diverse cast of Zora characters.
## Main Structure (`Sprite_Zora_Long`)
This routine is a complex dispatcher that determines which Zora variant to draw and process based on several game state variables:
* **Zora Princess**: If the current `ROOM` is `$0105`, it calls `Sprite_ZoraPrincess_Draw` and sets `SprMiscG, X` to `$01`.
* **Eon Zora**: If `WORLDFLAG` is not `0`, it calls `Sprite_EonZora_Draw`, `Sprite_DrawShadow`, and sets `SprMiscG, X` to `$02`.
* **Eon Zora Elder**: If `SprSubtype, X` is not `0` (and not the Princess or Eon Zora), it calls `Sprite_EonZoraElder_Draw` and sets `SprMiscG, X` to `$03`.
* **Generic Sea Zora**: Otherwise, it calls `Sprite_Zora_Draw`, `Sprite_DrawShadow`, and sets `SprMiscG, X` to `0`.
* After drawing, it calls `Sprite_CheckActive` and then `Sprite_Zora_Handler` if the sprite is active.
```asm
Sprite_Zora_Long:
{
PHB : PHK : PLB
; Check what Zora we are drawing
REP #$30
LDA.w ROOM : CMP.w #$0105 : BNE .not_princess
SEP #$30
JSR Sprite_ZoraPrincess_Draw
LDA.b #$01 : STA.w SprMiscG, X
JMP +
.not_princess
LDA.w WORLDFLAG : AND.w #$00FF : BEQ .eon_draw
SEP #$30
JSR Sprite_EonZora_Draw
JSL Sprite_DrawShadow
LDA.b #$02 : STA.w SprMiscG, X
JMP +
.eon_draw
SEP #$30
LDA.w SprSubtype, X : BNE .special_zora
JSR Sprite_Zora_Draw
JSL Sprite_DrawShadow
STZ.w SprMiscG, X
JMP +
.special_zora
JSR Sprite_EonZoraElder_Draw
LDA.b #$03 : STA.w SprMiscG, X
+
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Zora_Handler
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Zora_Prep`)
This routine is empty, indicating that custom initialization for the Zora handler is minimal or handled by the individual Zora sprite routines.
## Main Logic Dispatcher (`Sprite_Zora_Handler`)
This routine dispatches to the appropriate main logic routine for the specific Zora variant based on the value of `SprMiscG, X`:
* `$01`: Calls `Sprite_ZoraPrincess_Main`
* `$00`: Calls `Sprite_Zora_Main` (Generic Sea Zora)
* `$02`: Calls `Sprite_EonZora_Main`
* `$03`: Calls `Sprite_EonZoraElder_Main`
```asm
Sprite_Zora_Handler:
{
LDA.w SprMiscG, X
CMP.b #$01 : BNE .not_princess
JSR Sprite_ZoraPrincess_Main
RTS
.not_princess
JSL JumpTableLocal
dw Sprite_Zora_Main
dw Sprite_ZoraPrincess_Main
dw Sprite_EonZora_Main
dw Sprite_EonZoraElder_Main
}
```
## `Sprite_Zora_Main` (Generic Sea Zora)
This routine defines the behavior for a generic Sea Zora NPC.
* **Head Tracking**: Calls `Zora_TrackHeadToPlayer` to make the Zora face Link.
* **Player Collision**: Prevents Link from passing through the Zora (`JSL Sprite_PlayerCantPassThrough`).
* **Dialogue**: Calls `Zora_HandleDialogue` for context-sensitive dialogue interactions.
* **Animation**: Uses a jump table for animation states: `Zora_Forward`, `Zora_Right`, `Zora_Left`, each playing a specific animation.
```asm
Sprite_Zora_Main:
{
JSR Zora_TrackHeadToPlayer
JSL Sprite_PlayerCantPassThrough
JSR Zora_HandleDialogue
LDA.w SprAction, X
JSL JumpTableLocal
dw Zora_Forward
dw Zora_Right
dw Zora_Left
Zora_Forward:
{
%PlayAnimation(0,0,10)
RTS
}
Zora_Right:
{
%PlayAnimation(1,1,10)
RTS
}
Zora_Left:
{
%PlayAnimation(1,1,10)
RTS
}
}
```
## `Zora_TrackHeadToPlayer`
This routine makes the Zora face Link by setting `SprAction, X` to `0` (forward) or `1` (right/left) based on Link's horizontal position relative to the Zora.
## `Zora_HandleDialogue`
This routine handles context-sensitive dialogue for the generic Sea Zora. It checks the `Crystals` SRAM flag (specifically bit `$20`) to determine if a certain crystal has been collected. Based on this and the Zora's `SprAction, X`, it displays different solicited messages (`$01A6`, `$01A5`, or `$01A4`).
## Drawing (`Sprite_Zora_Draw`)
This routine handles OAM allocation and animation for the generic Sea Zora. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
```asm
Sprite_Zora_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, 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 .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer
PLY : INY
PLX : DEX : BPL .nextTile
PLX
RTS
.start_index
db $00, $02, $04
.nbr_of_tiles
db 1, 1, 1
.x_offsets
dw 0, 0
dw 0, 0
dw 0, 0
.y_offsets
dw -8, 0
dw -8, 0
dw -8, 0
.chr
db $DE, $EE
db $DC, $EC
db $DC, $EC
.properties
db $35, $35
db $35, $35
db $75, $75
.sizes
db $02, $02
db $02, $02
db $02, $02
}
```
## Design Patterns
* **Centralized NPC Handler**: This file acts as a central dispatcher for multiple Zora-type NPCs (Zora Princess, Eon Zora, Eon Zora Elder, and generic Sea Zora), demonstrating efficient management of diverse character behaviors from a single entry point.
* **Multi-Character Sprite (Conditional Drawing/Logic)**: A single sprite ID is used to represent various Zora characters, with their specific drawing and main logic routines dynamically selected based on game state variables like `ROOM`, `WORLDFLAG`, and `SprSubtype`.
* **Context-Sensitive Dialogue**: The generic Sea Zora's dialogue changes based on collected crystals and its current `SprAction`, providing dynamic and responsive interactions with the player.
* **Player Collision**: Implements `JSL Sprite_PlayerCantPassThrough` to make the Zora NPCs solid objects that Link cannot walk through.
* **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.

View File

@@ -0,0 +1,219 @@
# Zora Princess
## Overview
The Zora Princess sprite (`!SPRID = Sprite_ZoraPrincess`) is a key NPC involved in a quest that culminates in Link receiving the Zora Mask. Her interactions are conditional, primarily triggered by Link playing the "Song of Healing," and her presence is tied to whether Link has already obtained the Zora Mask.
## Sprite Properties
* **`!SPRID`**: `Sprite_ZoraPrincess` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `9`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `02`
* **`!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_ZoraPrincess_Long`)
This routine handles the Zora Princess's drawing and dispatches to her main logic if the sprite is active.
```asm
Sprite_ZoraPrincess_Long:
{
PHB : PHK : PLB
JSR Sprite_ZoraPrincess_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_ZoraPrincess_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_ZoraPrincess_Prep`)
This routine initializes the Zora Princess upon spawning. It checks the Zora Mask flag (`$7EF302`). If Link already possesses the Zora Mask, the sprite immediately despawns (`STZ.w SprState, X`), ensuring the quest is a one-time event. It also sets `SprDefl, X` and `SprTileDie, X` to `0`.
```asm
Sprite_ZoraPrincess_Prep:
{
PHB : PHK : PLB
LDA.l $7EF302 : BEQ .doesnt_have_mask
STZ.w SprState, X ; Kill the sprite
.doesnt_have_mask
LDA #$00 : STA.w SprDefl, X
LDA #$00 : STA.w SprTileDie, X
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_ZoraPrincess_Main`)
This routine manages the Zora Princess's interactions and the process of granting the Zora Mask.
* **Player Collision**: Prevents Link from passing through the Zora Princess (`JSL Sprite_PlayerCantPassThrough`).
* **`WaitForLink`**: Plays an animation and displays a solicited message (`%ShowSolicitedMessage($0C5)`). Upon message dismissal, it transitions to `CheckForSongOfHealing`.
* **`CheckForSongOfHealing`**: Plays an animation and checks the `SongFlag`. If the "Song of Healing" has been played, it clears the `SongFlag`, sets a timer (`SprTimerD, X`), and transitions to `ThanksMessage`.
* **`ThanksMessage`**: Plays an animation. After a timer (`SprTimerD, X`), it displays an unconditional message (`%ShowUnconditionalMessage($0C6)`) and transitions to `GiveZoraMask`.
* **`GiveZoraMask`**: After a timer (`SprTimerD, X`), it grants Link the Zora Mask (`LDY #$0F`, `JSL Link_ReceiveItem`), sets the Zora Mask obtained flag (`$7EF302` to `$01`), and despawns the sprite (`STZ.w SprState, X`).
```asm
Sprite_ZoraPrincess_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw WaitForLink
dw CheckForSongOfHealing
dw ThanksMessage
dw GiveZoraMask
WaitForLink:
{
%PlayAnimation(0, 1, 10)
%ShowSolicitedMessage($0C5) : BCC .no_hablaba
%GotoAction(1)
.no_hablaba
RTS
}
CheckForSongOfHealing:
{
%PlayAnimation(0, 1, 10)
LDA.b SongFlag : BEQ .ninguna_cancion
STZ.b SongFlag
LDA.b #$C0 : STA.w SprTimerD, X
%GotoAction(2)
.ninguna_cancion
RTS
}
ThanksMessage:
{
%PlayAnimation(0, 1, 10)
LDA.w SprTimerD, X : BNE +
%ShowUnconditionalMessage($0C6)
LDA.b #$C0 : STA.w SprTimerD, X
%GotoAction(3)
+
RTS
}
GiveZoraMask:
{
LDA.w SprTimerD, X : BNE +
LDY #$0F : STZ $02E9 ; Give the Zora Mask
JSL Link_ReceiveItem
LDA #$01 : STA.l $7EF302
LDA.b #$00 : STA.w SprState, X
+
RTS
}
}
```
## Drawing (`Sprite_ZoraPrincess_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
```asm
Sprite_ZoraPrincess_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 .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer
PLY : INY
PLX : DEX : BPL .nextTile
PLX
RTS
.start_index
db $00, $04
.nbr_of_tiles
db 3, 11
.x_offsets
dw -4, 4, -4, 4
dw 4, 4, 4, 4, -4, -4, -4, -4, 12, 12, 12, 12
.y_offsets
dw -8, -8, 8, 8
dw -8, 0, 8, 16, -8, 0, 8, 16, -8, 0, 8, 16
.chr
db $C0, $C1, $E0, $E1
db $C1, $D1, $E1, $F1, $C3, $D3, $E3, $F3, $C3, $D3, $E3, $F3
.properties
db $33, $33, $33, $33
db $33, $33, $33, $33, $33, $33, $33, $33, $73, $73, $73, $73
.sizes
db $02, $02, $02, $02
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
}
```
## Design Patterns
* **Quest Gating/Progression**: The Zora Princess's appearance and the granting of the Zora Mask are conditional on Link not already possessing the mask and playing the "Song of Healing," integrating her into a specific questline.
* **NPC Interaction**: The Zora Princess interacts with the player through dialogue and grants a key item (the Zora Mask), which is essential for progression.
* **Conditional Spawning/Despawning**: The sprite dynamically despawns if Link has already obtained the Zora Mask, ensuring that the quest is a one-time event and preventing redundant interactions.
* **Item Granting**: The Zora Princess serves as the source for the Zora Mask, a significant item that likely grants new abilities or access to new areas.
* **Player Collision**: Implements `JSL Sprite_PlayerCantPassThrough` to make the Zora Princess a solid object that Link cannot walk through.
* **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.

View File

@@ -0,0 +1,172 @@
# Collectible Sprites (Pineapple, Seashell, Sword/Shield, Rock Sirloin)
## Overview
The Collectible sprite (`!SPRID = $52`) is a versatile implementation designed to represent various collectible items within the game, including Pineapples, Seashells, the starting Sword/Shield, and Rock Sirloin. Its specific appearance and behavior are dynamically determined by the `SprAction, X` state and the current `AreaIndex`, allowing for context-sensitive item placement and interaction.
## Sprite Properties
* **`!SPRID`**: `$52` (Vanilla sprite ID, likely for a generic collectible)
* **`!NbrTiles`**: `03`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!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_Collectible_Long`)
This routine acts as a dispatcher for drawing, selecting the appropriate drawing routine based on the `AreaIndex`:
* If `AreaIndex` is `$58` (Intro Sword area), it calls `Sprite_SwordShield_Draw`.
* If `AreaIndex` is `$4B` (Lupo Mountain area), it calls `Sprite_RockSirloin_Draw`.
* Otherwise, it calls `Sprite_Pineapple_Draw`.
* It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_Collectible_Long:
{
PHB : PHK : PLB
LDA.b $8A : CMP.b #$58 : BNE .not_intro_sword
JSR Sprite_SwordShield_Draw
BRA +
.not_intro_sword
LDA.b $8A : CMP.b #$4B : BNE .not_lupo_mountain
JSR Sprite_RockSirloin_Draw
BRA +
.not_lupo_mountain
JSR Sprite_Pineapple_Draw
+
JSL Sprite_DrawShadow
JSL Sprite_CheckActive
BCC .SpriteIsNotActive
JSR Sprite_Collectible_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Collectible_Prep`)
This routine initializes the collectible sprite upon spawning, with conditional logic based on the `AreaIndex`:
* **Intro Sword**: If `AreaIndex` is `$58`, it checks Link's Sword flag (`$7EF359`). If Link already has the Sword, the sprite despawns. It also sets `SprAction, X` to `$02` (SwordShield).
* **Rock Sirloin**: If `AreaIndex` is `$4B`, it sets `SprAction, X` to `$03` (RockSirloin).
```asm
Sprite_Collectible_Prep:
{
PHB : PHK : PLB
; Don't spawn the sword if we have it.
LDA.b $8A : CMP.b #$58 : BNE .not_intro_sword
LDA.l $7EF359 : BEQ +
STZ.w SprState, X
+
LDA.b #$02 : STA.w SprAction, X
.not_intro_sword
LDA.b $8A : CMP.b #$4B : BNE .not_lupo_mountain
LDA.b #$03 : STA.w SprAction, X
.not_lupo_mountain
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Collectible_Main`)
This routine manages the behavior of various collectible items through a jump table based on `SprAction, X`:
* **`Pineapple`**: Moves the sprite (`JSL Sprite_Move`). If Link touches it (`JSL Sprite_CheckDamageToPlayer`), it increments the `Pineapples` custom item count and despawns the sprite.
* **`Seashell`**: Similar to Pineapple, but increments the `Seashells` custom item count.
* **`SwordShield`**: Plays an animation, moves the sprite. If Link touches it, it grants Link the Sword (`LDY.b #$00`, `JSL Link_ReceiveItem`) and despawns the sprite.
* **`RockSirloin`**: Moves the sprite. It checks Link's Glove flag (`$7EF354`). If Link has the Glove, it checks for player contact. If touched, it handles interaction with thrown sprites (`JSL ThrownSprite_TileAndSpriteInteraction_long`), increments the `RockMeat` custom item count, and despawns the sprite.
```asm
Sprite_Collectible_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw Pineapple
dw Seashell
dw SwordShield
dw RockSirloin
Pineapple:
{
JSL Sprite_Move
JSL Sprite_CheckDamageToPlayer : BCC +
LDA.l Pineapples : INC A : STA.l Pineapples
STZ.w SprState, X
+
RTS
}
Seashell:
{
JSL Sprite_Move
JSL Sprite_CheckDamageToPlayer : BCC +
LDA.l Seashells : INC A : STA.l Seashells
STZ.w SprState, X
+
RTS
}
SwordShield:
{
%PlayAnimation(0,0,1)
JSL Sprite_Move
JSL Sprite_CheckDamageToPlayer : BCC +
LDY.b #$00 : STZ $02E9
JSL Link_ReceiveItem
STZ.w SprState, X
+
RTS
}
RockSirloin:
{
JSL Sprite_Move
LDA.l $7EF354 : BEQ .do_you_even_lift_bro
JSL Sprite_CheckDamageToPlayer : BCC +
JSL ThrownSprite_TileAndSpriteInteraction_long
LDA.l RockMeat : INC A : STA.l RockMeat
STZ.w SprState, X
+
.do_you_even_lift_bro
RTS
}
}
```
## Drawing (`Sprite_Pineapple_Draw`, `Sprite_SwordShield_Draw`, `Sprite_RockSirloin_Draw`)
Each collectible type has its own dedicated drawing routine. These routines handle OAM allocation and animation, and explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each routine contains its own specific OAM data for rendering the respective item.
## Design Patterns
* **Multi-Item Collectible**: A single sprite definition (`!SPRID = $52`) is used to represent multiple distinct collectible items, with their specific appearance and behavior determined by `SprAction, X` and `AreaIndex`. This allows for efficient reuse of sprite slots for various in-game items.
* **Context-Sensitive Spawning/Drawing**: The sprite's initial appearance and drawing routine are dynamically selected based on the `AreaIndex`, enabling specific items to appear in designated locations within the game world.
* **Item Granting**: Collectibles grant items to Link upon contact, directly influencing his inventory and progression.
* **Quest Progression Integration**: The Sword/Shield collectible despawns if Link already possesses the Sword, and the Rock Sirloin requires Link to have the Glove to interact with it, integrating these items into the game's quest and progression systems.
* **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.

View File

@@ -0,0 +1,147 @@
# Deku Leaf / Whirlpool
## Overview
The `deku_leaf.asm` file defines a versatile sprite (`!SPRID = Sprite_DekuLeaf`) that can function as two distinct in-game objects: the "Deku Leaf" and a "Whirlpool." Its specific behavior and visual representation are dynamically determined by the current `AreaIndex`, allowing it to serve different purposes in various locations.
## Sprite Properties
* **`!SPRID`**: `Sprite_DekuLeaf` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `00` (Graphics are handled externally or as a background)
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `$0D`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!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_DekuLeaf_Long`)
This routine acts as a dispatcher for drawing, selecting `Sprite_Whirlpool_Draw` if `AreaIndex` is `$3D` (Whirlpool area), and `Sprite_DekuLeaf_Draw` otherwise. It also dispatches to the main logic if the sprite is active.
```asm
Sprite_DekuLeaf_Long:
{
PHB : PHK : PLB
LDA $8A : CMP.b #$3D : BEQ .whirlpool
JSR Sprite_DekuLeaf_Draw
JMP +
.whirlpool
JSR Sprite_Whirlpool_Draw
+
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_DekuLeaf_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_DekuLeaf_Prep`)
This routine initializes the sprite upon spawning. If `AreaIndex` is `$3D` (Whirlpool area), it sets `SprAction, X` to `$01` (`Whirlpool_Main`), indicating its role as a whirlpool.
```asm
Sprite_DekuLeaf_Prep:
{
PHB : PHK : PLB
LDA $8A : CMP.b #$3D : BNE .not_whirlpool
LDA.b #$01 : STA.w SprAction, X
.not_whirlpool
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_DekuLeaf_Main`)
This routine manages the behavior of both the Deku Leaf and the Whirlpool through a jump table based on `SprAction, X`:
* **`WaitForPlayer` (Deku Leaf)**: Plays an idle animation. It checks if Link is on the leaf (`JSR CheckIfPlayerIsOn`). If so, it sets a flag (`$71`) and, if Link is in Minish form, spawns a poof garnish. Otherwise, it clears the flag.
* **`Whirlpool_Main`**: Plays an animation. If Link is on the whirlpool and the underwater flag (`$0AAB`) is set, it resets various Link state flags (`$55`, `$0AAB`, `$0351`, `$037B`, `$02B2`). If Link's state is not `$0B` (Mirror), it saves Link's coordinates and sets `GameMode` to `$23` to initiate a warp, similar to the Mirror effect.
```asm
Sprite_DekuLeaf_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw WaitForPlayer
dw Whirlpool_Main
WaitForPlayer:
{
%StartOnFrame(0)
%PlayAnimation(0, 0, 10)
JSR CheckIfPlayerIsOn : BCC +
LDA.b #$01 : STA.b $71
LDA.w $02B2 : CMP.b #$01 : BNE ++
JSL Sprite_SpawnPoofGarnish
++
RTS
+
STZ.b $71
RTS
}
Whirlpool_Main:
{
%PlayAnimation(0, 2, 10)
JSR CheckIfPlayerIsOn : BCC .not_on
LDA $0AAB : BEQ .not_on
STZ $55 ; Reset cape flag
STZ $0AAB ; Reset underwater flag
STZ $0351 ; Reset ripple flag
STZ $037B ; Reset invincibility flag
STZ $02B2 ; Reset mask flag
LDA.b $10 : CMP.b #$0B : BEQ .exit
LDA.b $8A : AND.b #$40 : STA.b $7B : BEQ .no_mirror_portal
LDA.b $20 : STA.w $1ADF
LDA.b $21 : STA.w $1AEF
LDA.b $22 : STA.w $1ABF
LDA.b $23 : STA.w $1ACF
.no_mirror_portal
LDA.b #$23
#SetGameModeLikeMirror:
STA.b $11
STZ.w $03F8
LDA.b #$01 : STA.w $02DB
STZ.b $B0
STZ.b $27 : STZ.b $28
LDA.b #$14 : STA.b $5D
.not_on
.exit
RTS
}
}
```
## Drawing (`Sprite_DekuLeaf_Draw` and `Sprite_Whirlpool_Draw`)
Each object type has its own dedicated drawing routine. These routines handle OAM allocation and animation, and explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each routine contains its own specific OAM data for rendering the respective object.
## Design Patterns
* **Multi-Object Sprite (Conditional Drawing/Logic)**: A single sprite definition (`Sprite_DekuLeaf`) is used to represent two distinct objects (Deku Leaf and Whirlpool) based on `AreaIndex`, showcasing efficient resource utilization and varied functionality.
* **Context-Sensitive Behavior**: The sprite's behavior changes entirely based on the `AreaIndex`, allowing it to function as a traversal item (Deku Leaf) or a warp point (Whirlpool), adapting to different game contexts.
* **Player Interaction**: The Deku Leaf allows Link to stand on it for traversal, while the Whirlpool provides a warp mechanism, both offering unique forms of player interaction.
* **Game State Manipulation**: The Whirlpool modifies various Link state flags to initiate a warp, demonstrating direct control over the player's game state during transitions.
* **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.

View File

@@ -0,0 +1,208 @@
# Ice Block (Pushable)
## Overview
The Ice Block sprite (`!SPRID = $D5`) is an interactive object designed as a puzzle element that Link can push. It features complex logic for detecting Link's push, applying movement with momentum, and interacting with switch tiles. This sprite is impervious to most attacks and behaves like a solid statue.
## Sprite Properties
* **`!SPRID`**: `$D5` (Vanilla sprite ID, likely for a pushable block)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Impervious to all attacks)
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `09`
* **`!Persist`**: `00`
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `00`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `01` (Behaves like a solid statue)
* **`!DeflectProjectiles`**: `01` (Deflects all projectiles)
* **`!ImperviousArrow`**: `01` (Impervious to arrows)
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Constants
* **`!ICE_BLOCK_SPEED`**: `16` (Speed at which the ice block moves when pushed)
* **`!PUSH_CONFIRM_FRAMES`**: `10` (Number of frames Link must maintain a push for it to be confirmed)
* **`!ALIGN_TOLERANCE`**: `4` (Pixel tolerance for Link's alignment with the block)
* **`!WRAM_FLAG_0642`**: `$0642` (WRAM address for a flag related to switch activation)
* **`!WRAM_TILE_ATTR`**: `$0FA5` (WRAM address for tile attributes)
* **`!SPRITE_LOOP_MAX`**: `$0F` (Max index for sprite loops)
* **`!SPRITE_TYPE_STATUE`**: `$1C` (Sprite ID for a generic statue)
* **`!SPRITE_STATE_ACTIVE`**: `$09` (Sprite state for active sprites)
* **`!TILE_ATTR_ICE`**: `$0E` (Tile attribute for ice, currently unused)
* **`!SWITCH_TILE_ID_1` to `!SWITCH_TILE_ID_4`**: IDs for various switch tiles.
* **`!SWITCH_TILE_COUNT_MINUS_1`**: `$03`
## Main Structure (`Sprite_IceBlock_Long`)
This routine handles the Ice Block's drawing and dispatches to its main logic if active. It also manages Link's interaction with the block, setting Link's speed and actions when pushing.
```asm
Sprite_IceBlock_Long:
{
PHB : PHK : PLB
LDA.w SprMiscC, X : BEQ .not_being_pushed
STZ.w SprMiscC, X
STZ.b LinkSpeedTbl
STZ.b $48 ; Clear push actions bitfield
.not_being_pushed
LDA.w SprTimerA, X : BEQ .retain_momentum
LDA.b #$01 : STA.w SprMiscC, X
LDA.b #$84 : STA.b $48 ; Set statue and push block actions
LDA.b #$04 : STA.b LinkSpeedTbl ; Slipping into pit speed
.retain_momentum
JSR Sprite_IceBlock_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_IceBlock_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_IceBlock_Prep`)
This routine initializes the Ice Block upon spawning. It caches the sprite's initial position in `SprMiscD, X` through `SprMiscG, X`. It sets `SprDefl, X` to `$04` (designating it as a pushable statue) and initializes `SprMiscB, X` to `0` (movement state).
```asm
Sprite_IceBlock_Prep:
{
PHB : PHK : PLB
; Cache Sprite position
LDA.w SprX, X : STA.w SprMiscD, X
LDA.w SprY, X : STA.w SprMiscE, X
LDA.w SprXH, X : STA.w SprMiscF, X
LDA.w SprYH, X : STA.w SprMiscG, X
LDA.b #$04 : STA.w SprDefl, X ; Set as pushable statue
LDA.w SprHitbox, X : ORA.b #$09 : STA.w SprHitbox, X
; Initialize movement state tracking
STZ.w SprMiscB, X ; Clear movement state
PLB
RTL
}
```
## Main Logic (`Sprite_IceBlock_Main`)
This routine manages the Ice Block's behavior, including push detection, movement, and interaction with switches.
* **Animation**: Plays a static animation (`%PlayAnimation(0, 0, 1)`).
* **Sprite-to-Sprite Collision**: Calls `IceBlock_HandleSpriteToSpriteCollision` to manage interactions with other sprites.
* **Damage Reaction**: If the block takes damage, its position, speed, and movement state are reset.
* **Switch Detection**: Calls `Sprite_IceBlock_CheckForSwitch`. If the block is on a switch, it stops movement, sets `!WRAM_FLAG_0642` to `01`, and resets its movement state.
* **Push Logic**: This is a core part of the routine. If the block is not moving, it checks if Link is in contact and correctly aligned (`IceBlock_CheckLinkPushAlignment`). If so, a push timer (`SprTimerA, X`) is initiated. If the timer expires while Link is still pushing, the block snaps to the grid, applies push speed (`Sprite_ApplyPush`), and begins moving. If the block is already moving, it continues to move (`JSL Sprite_Move`) and checks for tile collisions, stopping if an obstacle is encountered.
```asm
Sprite_IceBlock_Main:
{
%PlayAnimation(0, 0, 1)
JSR IceBlock_HandleSpriteToSpriteCollision ; Renamed from Statue_BlockSprites
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
LDA.w SprMiscD, X : STA.w SprX, X
LDA.w SprY, X : STA.w SprY, X
LDA.w SprXH, X : STA.w SprXH, X
LDA.w SprYH, X : STA.w SprYH, X
STZ.w SprXSpeed, X : STZ.w SprYSpeed, X
STZ.w SprTimerA, X : STZ.w SprMiscA, X
STZ.w SprMiscB, X ; Reset movement state when hit
.no_damage
STZ.w !WRAM_FLAG_0642
JSR Sprite_IceBlock_CheckForSwitch : BCC .no_switch
STZ.w SprXSpeed, X : STZ.w SprYSpeed, X
LDA.b #$01 : STA.w !WRAM_FLAG_0642
STZ.w SprMiscB, X ; Reset movement state when hitting switch
.no_switch
; If the block is currently moving, apply movement and check for collisions
LDA.w SprMiscB, X
BNE .block_is_moving
; --- Block is NOT moving, check for push initiation ---
JSL Sprite_CheckDamageToPlayerSameLayer : BCC .NotInContact
; Link is in contact. Now check if he's properly aligned and facing the block.
JSR IceBlock_CheckLinkPushAlignment
BCC .NotInContact ; Link is not aligned or facing correctly.
; Link is aligned and facing the block. Start or continue the push timer.
LDA.w SprTimerA, X
BNE .timer_is_running ; Timer already started, let it count down.
; Start the timer for the first time.
LDA.b #!PUSH_CONFIRM_FRAMES
STA.w SprTimerA, X
RTS ; Wait for next frame
.timer_is_running
; Timer is running. Has it reached zero? (SprTimerA is decremented by engine)
LDA.w SprTimerA, X
BNE .NotInContact ; Not zero yet, keep waiting.
; --- PUSH CONFIRMED ---
; Timer reached zero while still in contact and aligned.
; Snap to grid before setting speed for clean movement.
LDA.w SprX, X : AND.b #$F8 : STA.w SprX, X
LDA.w SprY, X : AND.b #$F8 : STA.w SprY, X
JSR Sprite_ApplyPush ; Set speed based on Link's direction.
LDA.b #$01 : STA.w SprMiscB, X ; Set "is moving" flag.
RTS
.NotInContact
; No contact or improper alignment, reset push timer.
STZ.w SprTimerA, X
RTS
.block_is_moving
JSL Sprite_Move
JSL Sprite_Get_16_bit_Coords
JSL Sprite_CheckTileCollision
; ----udlr , u = up, d = down, l = left, r = right
LDA.w SprCollision, X : AND.b #$0F : BEQ + ; If no collision, continue moving
STZ.w SprXSpeed, X : STZ.w SprYSpeed, X ; Stop movement
STZ.w SprMiscB, X ; Reset movement state
+
RTS
}
```
## `IceBlock_CheckLinkPushAlignment`
This complex routine precisely determines if Link is correctly aligned and facing the ice block to initiate a push. It calculates the relative positions of Link and the block, considers Link's facing direction, and uses `!ALIGN_TOLERANCE` to allow for slight pixel variations. It returns with the carry flag set for success or clear for failure.
## `Sprite_ApplyPush`
This routine sets the Ice Block's `SprXSpeed, X` or `SprYSpeed, X` based on Link's facing direction (`SprMiscA, X`) and the predefined `!ICE_BLOCK_SPEED`.
## `IceBlock_CheckForGround`
This routine is currently unused but was intended to check if the tile beneath the sprite was a sliding ice tile.
## `Sprite_IceBlock_CheckForSwitch`
This routine checks if any of the four corners of the Ice Block are currently positioned on a switch tile (identified by `!SWITCH_TILE_ID_1` to `!SWITCH_TILE_ID_4`). It returns with the carry flag set if any corner is on a switch tile.
## `IceBlock_HandleSpriteToSpriteCollision`
This routine (renamed from `Statue_BlockSprites`) manages collisions between the Ice Block and other active sprites. It iterates through other sprites, checks for collision, and applies recoil or other effects to them.
## Drawing (`Sprite_IceBlock_Draw`)
This routine handles OAM allocation and animation for the Ice Block. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
## Design Patterns
* **Interactive Puzzle Element**: The Ice Block is a core interactive puzzle element that Link can manipulate by pushing, requiring precise player input and environmental interaction.
* **Precise Collision and Alignment Detection**: Implements detailed logic to ensure Link is correctly positioned and facing the block before a push is registered, providing a robust and fair interaction mechanism.
* **Movement with Momentum**: The block retains momentum after being pushed, sliding across the terrain until it encounters an obstacle, adding a realistic physics element.
* **Switch Activation**: The block can activate switch tiles upon contact, integrating it into environmental puzzles and triggering game events.
* **Sprite-to-Sprite Collision**: Handles interactions with other sprites, applying recoil effects to them, demonstrating complex inter-sprite dynamics.
* **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.

View File

@@ -0,0 +1,166 @@
# Mine Switch
## Overview
The Mine Switch sprite (`!SPRID = Sprite_Mineswitch`) is an interactive puzzle element, typically found in the Goron Mines. It functions as a lever-style switch that Link can activate by attacking it, altering the state of minecart tracks or other game elements. This sprite supports both a regular on/off switch and a speed-controlling switch, with its behavior and appearance changing based on its current state.
## Sprite Properties
* **`!SPRID`**: `Sprite_Mineswitch` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `01`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `00`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `01`
* **`!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_LeverSwitch_Long`)
This routine handles the Mine Switch's drawing and dispatches to its main logic if the sprite is active.
```asm
Sprite_LeverSwitch_Long:
{
PHB : PHK : PLB
JSR Sprite_LeverSwitch_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_LeverSwitch_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_LeverSwitch_Prep`)
This routine initializes the Mine Switch upon spawning. It sets `SprDefl, X` to `0`. It retrieves the switch's on/off state from `SwitchRam`, indexed by `SprSubtype, X`, and sets `SprAction, X` and `SprFrame, X` accordingly. It also sets `SprTileDie, X` to `0` and `SprBulletproof, X` to `0`.
```asm
Sprite_LeverSwitch_Prep:
{
PHB : PHK : PLB
LDA.b #$00 : STA.w SprDefl, X
; Get the subtype of the switch so that we can get its on/off state.
LDA.w SprSubtype, X : TAY
LDA.w SwitchRam, Y : STA.w SprAction, X : STA.w SprFrame, X
LDA.b #$00 : STA.w SprTileDie, X
STZ.w SprBulletproof, X
PLB
RTL
}
```
## Constants
* **`SwitchRam = $0230`**: A WRAM address that stores the state (on/off) of each individual switch, indexed by its `SprSubtype`.
## Main Logic & State Machine (`Sprite_LeverSwitch_Main`)
This routine manages the Mine Switch's behavior through a jump table, supporting different types of switches:
* **Player Collision**: Prevents Link from passing through the switch (`JSL Sprite_PlayerCantPassThrough`).
* **`SwitchOff`**: Plays an animation. If Link attacks it (`JSL Sprite_CheckDamageFromPlayer`) and a timer (`SprTimerA, X`) allows, it plays a sound (`$25`), turns the switch on (`STA.w SwitchRam, Y` to `01`), sets a timer, and transitions to `SwitchOn`.
* **`SwitchOn`**: Plays an animation. If Link attacks it and a timer allows, it plays a sound (`$25`), turns the switch off (`STA.w SwitchRam, Y` to `00`), sets a timer, and transitions to `SwitchOff`.
* **`SpeedSwitchOff`**: Plays an animation. If Link attacks it, it plays a sound (`$25`), sets `$36` to `01` (likely a global speed flag for minecarts), and transitions to `SpeedSwitchOn`.
* **`SpeedSwitchOn`**: Plays an animation. If Link attacks it, it plays a sound (`$25`), clears `$36`, and transitions to `SpeedSwitchOff`.
```asm
Sprite_LeverSwitch_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL UseImplicitRegIndexedLocalJumpTable
dw SwitchOff
dw SwitchOn
dw SpeedSwitchOff
dw SpeedSwitchOn
SwitchOff:
{
%PlayAnimation(0,0,4)
LDA.w SprTimerA, X : BNE .NoDamage
JSL Sprite_CheckDamageFromPlayer : BCC .NoDamage
LDA #$25 : STA $012F
; Get the subtype of the switch so that we can get its on/off state.
LDA.w SprSubtype, X : TAY
; Turn the switch on.
LDA #$01 : STA.w SwitchRam, Y
LDA #$10 : STA.w SprTimerA, X
%GotoAction(1)
.NoDamage
RTS
}
SwitchOn:
{
%PlayAnimation(1,1,4)
LDA.w SprTimerA, X : BNE .NoDamage
JSL Sprite_CheckDamageFromPlayer : BCC .NoDamage
LDA #$25 : STA $012F
; Get the subtype of the switch so that we can get its on/off state.
LDA.w SprSubtype, X : TAY
; Turn the switch off.
LDA #$00 : STA.w SwitchRam, Y
LDA #$10 : STA.w SprTimerA, X
%GotoAction(0)
.NoDamage
RTS
}
SpeedSwitchOff:
{
%PlayAnimation(0,0,4)
JSL Sprite_CheckDamageFromPlayer : BCC .NoDamage
LDA.b #$25 : STA $012F
LDA.b #$01 : STA $36
%GotoAction(3)
.NoDamage
RTS
}
SpeedSwitchOn:
{
%PlayAnimation(1,1,4)
JSL Sprite_CheckDamageFromPlayer : BCC .NoDamage
LDA #$25 : STA $012F
STZ.w $36
%GotoAction(2)
.NoDamage
RTS
}
}
```
## Drawing (`Sprite_LeverSwitch_Draw`)
This routine handles OAM allocation and animation for the Mine Switch. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
## Design Patterns
* **Interactive Puzzle Element**: The Mine Switch is a key interactive puzzle element that Link can activate by attacking it, triggering changes in the game environment.
* **State-Based Behavior**: The switch has distinct "on" and "off" states, with different animations and effects, providing clear visual feedback to the player.
* **Subtype-Driven State**: The `SprSubtype` is used to index into `SwitchRam`, allowing each individual switch to maintain its own independent state, enabling complex puzzle designs with multiple switches.
* **Speed Control**: The "Speed Switch" variant directly controls a global speed flag (`$36`), likely affecting the speed of minecarts or other moving objects, adding another layer of interaction to the minecart system.
* **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.

View File

@@ -0,0 +1,113 @@
# Minecart
## Overview
The Minecart sprite (`!SPRID = Sprite_Minecart`) is a highly complex and interactive object primarily used in the Goron Mines. It allows Link to ride it through a network of tracks, with its movement dictated by various track tile types, player input, and seamless dungeon transitions. The Minecart system features persistent state across rooms and intricate collision detection.
## Sprite Properties
* **`!SPRID`**: `Sprite_Minecart` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `08`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Impervious to all attacks)
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `14`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!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`
## Constants
* **`!LinkInCart`**: `$35` (Flag indicating Link is currently riding the Minecart)
* **`!MinecartSpeed`**: `20` (Normal movement speed)
* **`!DoubleSpeed`**: `30` (Faster movement speed, possibly for boosts)
* **Directions**: `North`, `East`, `South`, `West` (Used for `!MinecartDirection` and `SprMiscB`)
* **Sprite Facing Directions**: `Up`, `Down`, `Left`, `Right` (Used for `!SpriteDirection`)
* **`!MinecartDirection`**: `$0DE0` (Maps to `SprMiscC`, stores the current movement direction)
* **`!SpriteDirection`**: `$0DE0` (Stores the sprite's visual facing direction)
* **Track Persistence**: A system for saving and loading minecart state across rooms:
* **`!MinecartTrackRoom`**: `$0728` (Stores the room ID where a specific track was left)
* **`!MinecartTrackX`**: `$0768` (Stores the X position of a track)
* **`!MinecartTrackY`**: `$07A8` (Stores the Y position of a track)
* **Active Cart Tracking**: Variables to manage the currently active minecart:
* **`!MinecartTrackCache`**: `$07E8` (Stores the ID of the track Link is currently on)
* **`!MinecartDirectionCache`**: `$07E9` (Stores the direction during room transitions)
* **`!MinecartCurrent`**: `$07EA` (Stores the sprite slot index of the current minecart)
## Collision Setup (Tile Types)
Defines various tile types that represent different parts of the minecart track, including straight sections, corners, intersections, stop tiles, and dynamic switch tiles. These are crucial for guiding the minecart's movement and interaction with the environment.
## Main Structure (`Sprite_Minecart_Long`)
This routine handles the Minecart's multi-layered drawing (top and bottom portions) and dispatches to its main logic if the sprite is active.
```asm
Sprite_Minecart_Long:
{
PHB : PHK : PLB
JSR Sprite_Minecart_DrawTop ; Draw behind Link
JSR Sprite_Minecart_DrawBottom ; Draw in front of Link
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Minecart_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Minecart_Prep`)
This routine initializes the Minecart upon spawning. It updates cached coordinates, manages track persistence (initializing track data if not already set), and handles despawning if the cart is not in its designated room or its coordinates don't match. It sets various sprite properties and determines the initial movement direction based on the tile the minecart is placed on.
## Main Logic & State Machine (`Sprite_Minecart_Main`)
This routine manages the Minecart's complex behavior through a state machine:
* **`Minecart_WaitHoriz` / `Minecart_WaitVert`**: The cart waits in a horizontal or vertical orientation. If Link is on the cart (`CheckIfPlayerIsOn`) and presses the B button, it saves the track ID, cancels Link's dash, sets `LinkSomaria` and `!LinkInCart`, adjusts Link's position, and transitions to a movement state (`Minecart_MoveEast`, `Minecart_MoveWest`, `Minecart_MoveNorth`, `Minecart_MoveSouth`).
* **`Minecart_MoveNorth` / `MoveEast` / `MoveSouth` / `MoveWest`**: The cart moves in the specified direction. It plays animations, sets speed (`!MinecartSpeed` or `!DoubleSpeed`), moves the sprite, drags Link along (`JSL DragPlayer`), handles the player camera, and processes track tiles (`HandleTileDirections`).
* **`Minecart_Release`**: Stops the cart, releases Link, and transitions back to a `Minecart_Wait` state.
## Helper Routines
* **`HandlePlayerCameraAndMoveCart`**: Manages Link's animation, camera, and plays cart sound effects.
* **`StopCart`**: Stops the cart, releases Link, rounds its coordinates, and saves its position to track variables for persistence.
* **`InitMovement`**: Caches Link's coordinates for movement calculations.
* **`Minecart_SetDirectionNorth` / `East` / `South` / `West`**: Set the cart's direction, animation, and update track caches.
* **`HandleTileDirections`**: A crucial routine that checks the tile the minecart is currently on and determines its next action, handling out-of-bounds, stop tiles, player input at intersections, corner tiles, and dynamic switch tiles.
* **`CheckForOutOfBounds`**: Determines if the cart is on an out-of-bounds tile.
* **`CheckForStopTiles`**: Checks for stop tiles and sets the cart's next direction.
* **`CheckForPlayerInput`**: Detects player input on intersection tiles to allow Link to choose the cart's direction.
* **`CheckForCornerTiles`**: Handles direction changes when the cart encounters corner tiles.
* **`HandleDynamicSwitchTileDirections`**: Manages movement on dynamic switch tiles, which can alter the cart's path.
* **`CheckTrackSpritePresence`**: Checks for the presence and collision of a `Sprite $B0` (Switch Track) with the minecart.
* **`CheckIfPlayerIsOn`**: Determines if Link is overlapping the minecart.
* **`ResetTrackVars`**: Resets all minecart track-related variables.
* **`Minecart_HandleToss` / `Minecart_HandleTossedCart` / `Minecart_HandleLiftAndToss`**: Routines for handling the minecart being tossed or lifted by Link.
## Drawing (`Sprite_Minecart_DrawTop` and `Sprite_Minecart_DrawBottom`)
These routines draw the Minecart in two separate portions (top and bottom) to create the illusion of Link riding inside it. They utilize `JSL Sprite_PrepOamCoord` and `OAM_AllocateFromRegionB`/`C` for OAM allocation, and explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
## Vanilla Overrides
* **`RoomTag_ShutterDoorRequiresCart`**: Modifies a room tag to require Link to be in a cart to open a shutter door, integrating the Minecart into dungeon puzzles.
* **`org $028260`**: Injects `JSL ResetTrackVars` to ensure minecart track variables are reset at specific points.
## Design Patterns
* **Complex Interactive Object**: The Minecart is a highly interactive object with intricate movement, collision, and state management, providing a unique traversal mechanic.
* **Track-Based Movement**: Movement is precisely governed by specific track tile types (stops, corners, intersections), requiring careful design of the minecart routes.
* **Player Input for Direction**: Link can influence the minecart's direction at intersections, adding an element of player control to the ride.
* **Persistent State**: Minecart position and direction are saved across room transitions, ensuring continuity and allowing for complex multi-room puzzles.
* **Multi-Part Drawing**: The minecart is drawn in two separate parts to allow Link to appear "inside" it, enhancing visual immersion.
* **Player State Manipulation**: The minecart directly controls Link's state (`!LinkInCart`, `LinkSomaria`, `LinkState`), seamlessly integrating the ride into Link's overall actions.
* **Dynamic Room Transitions**: Handles seamless transitions between rooms while Link is in the minecart, maintaining the flow of gameplay.
* **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.

View File

@@ -0,0 +1,76 @@
# Pedestal (Magic Pedestal Plaque)
## Overview
The `pedestal.asm` file defines the custom behavior for a "Magic Pedestal" sprite, which is an interactive object that responds to Link's actions. This implementation overrides a vanilla pedestal plaque sprite (`Sprite_B3_PedestalPlaque`) to trigger specific game events based on Link's inventory, input, and the current `AreaIndex`.
## Vanilla Overrides
* **`org $1EE05F`**: Injects `JSL CheckForBook` into the `Sprite_B3_PedestalPlaque` routine. This means the custom logic defined in `CheckForBook` will execute when the vanilla pedestal plaque sprite is processed.
## `CheckForBook`
This routine is the primary entry point for the custom pedestal logic. It checks several conditions to determine if Link is interacting with the pedestal in a specific way:
* **Link's Action**: Checks `$2F` (Link's current action/state).
* **Player Contact**: Checks for damage to player (`JSL Sprite_CheckDamageToPlayer`), which in this context likely means Link is in contact with the pedestal.
* **Item Held**: Checks if Link is holding a specific item (`$0202` compared to `$0F`, which likely corresponds to a book item).
* **Player Input**: Checks if Link is pressing the Y button (`BIT.b $F4`).
* **State Manipulation**: If Link is holding the book and pressing Y, it sets `$0300` to `0`, `$037A` to `$20`, and `$012E` to `0` (these are likely related to Link's animation or state changes).
* **Event Trigger**: Calls `JSR PedestalPlaque` to execute area-specific logic.
```asm
CheckForBook:
{
LDA.b $2F : BNE .exit
JSL Sprite_CheckDamageToPlayer : BCC .exit
LDA.w $0202 : CMP.b #$0F : BNE .not_holding_book
LDY.b #$01 : BIT.b $F4 : BVS .not_pressing_y
.not_holding_book
LDY.b #$00
.not_pressing_y
CPY.b #$01 : BNE .no_book_pose
STZ.w $0300
LDA.b #$20
STA.w $037A
STZ.w $012E
.no_book_pose
JSR PedestalPlaque
.exit
LDA.b AreaIndex : CMP.b #$30
RTL
}
```
## `PedestalPlaque`
This routine contains the area-specific logic for the pedestal, triggering different events based on the current `AreaIndex`:
* **Zora Temple (`AreaIndex = $1E`)**: Checks a flag (`$7EF29E` bit `$20`) and `SongFlag` (`$03`). If specific conditions are met (e.g., a certain event has not occurred and a particular song has been played), it sets `$04C6` to `$01` (likely a flag to open a gate or trigger an event) and clears `SongFlag`.
* **Goron Desert (`AreaIndex = $36`)**: No specific logic defined in this file.
* **Fortress Secrets (`AreaIndex = $5E`)**: No specific logic defined in this file.
```asm
PedestalPlaque:
{
LDA.b AreaIndex : CMP.b #$1E : BEQ .zora_temple
CMP.b #$36 : BEQ .goron_desert
CMP.b #$5E : BEQ .fortress_secrets
JMP .return
.zora_temple
LDA.l $7EF29E : AND.b #$20 : BNE .return
LDA.b SongFlag : CMP.b #$03 : BNE .return
LDA.b #$01 : STA $04C6
STZ.b SongFlag
JMP .return
.goron_desert
.fortress_secrets
.return
RTS
}
```
## Design Patterns
* **Vanilla Override**: This file directly modifies the vanilla pedestal plaque sprite to implement custom interactive behavior, demonstrating how to integrate new puzzle mechanics into existing game elements.
* **Context-Sensitive Interaction**: The pedestal responds specifically when Link is holding a particular item (a book) and pressing a button, creating a unique and logical interaction for puzzle solving.
* **Quest Progression Integration**: The pedestal triggers events based on the `AreaIndex` and various game state flags (e.g., `SongFlag`, `$7EF29E`), indicating its role in advancing specific quests and unlocking new areas.
* **Game State Manipulation**: Directly modifies WRAM addresses (`$04C6`, `SongFlag`) to trigger game events, such as opening gates or clearing flags, which are crucial for puzzle resolution and progression.

View File

@@ -0,0 +1,281 @@
# Portal Sprite
## Overview
The Portal sprite (`!SPRID = Sprite_Portal`) implements a sophisticated two-way warping system within the game. It allows Link to instantly travel between designated Blue and Orange portals, which can be placed in both dungeons and the overworld. This sprite features complex logic for portal activation, collision detection with Link, and seamless management of Link's state during warps.
## Sprite Properties
* **`!SPRID`**: `Sprite_Portal` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `01`
* **`!Harmless`**: `00`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `00`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!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_Portal_Long`)
This routine handles the Portal's drawing and dispatches to its main logic if the sprite is active.
```asm
Sprite_Portal_Long:
{
PHB : PHK : PLB
JSR Sprite_Portal_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Portal_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Portal_Prep`)
This routine initializes the Portal upon spawning. It sets `SprDefl, X` to `0` (ensuring persistence outside the camera view), modifies `SprHitbox, X` properties, sets `SprTileDie, X` to `0`, and makes the portal bulletproof (`SprBulletproof, X` to `$FF`).
```asm
Sprite_Portal_Prep:
{
PHB : PHK : PLB
; Persist outside of camera
LDA #$00 : STA.w SprDefl, X
LDA.w SprHitbox, X : AND.b #$C0 : STA.w SprHitbox, X
STZ.w SprTileDie, X
LDA.b #$FF : STA.w SprBulletproof, X
PLB
RTL
}
```
## Portal Data Memory Locations
* **`BluePortal_X`, `BluePortal_Y`, `OrangePortal_X`, `OrangePortal_Y`**: WRAM addresses storing the X and Y coordinates of the Blue and Orange portals, respectively.
* **`BlueActive`, `OrangeActive`**: Flags indicating whether the Blue and Orange portals are currently active.
* **`OrangeSpriteIndex`, `BlueSpriteIndex`**: Store the sprite indices of the Orange and Blue portals.
## Main Logic & State Machine (`Sprite_Portal_Main`)
This routine manages the various states and behaviors of the portals, including their creation, activation, and warping functionality.
* **`StateHandler`**: Calls `CheckForDismissPortal` and `RejectOnTileCollision`. It then checks `$7E0FA6` (likely a flag indicating which portal is being spawned). If `$7E0FA6` is `0`, it sets up an Orange Portal (stores coordinates, sets `SprSubtype, X` to `01`, and transitions to `OrangePortal`). Otherwise, it sets up a Blue Portal (stores coordinates, sets `SprSubtype, X` to `02`, and transitions to `BluePortal`).
* **`BluePortal` / `OrangePortal`**: Plays an animation. It checks if Link has been warped (`$11` compared to `$2A`). It then checks for overlap with Link's hitbox (`CheckIfHitBoxesOverlap`). If Link overlaps, it determines if Link is in a dungeon or overworld (`$1B`) and transitions to the appropriate warp state (`BluePortal_WarpDungeon`, `OrangePortal_WarpDungeon`, `BluePortal_WarpOverworld`, `OrangePortal_WarpOverworld`).
* **`BluePortal_WarpDungeon` / `OrangePortal_WarpDungeon`**: Warps Link's coordinates (`$20`, `$22`), sets camera scroll boundaries, stores the other portal's coordinates, sets its `SprTimerD, X`, sets `$11` to `$14`, and returns to the respective portal state.
* **`BluePortal_WarpOverworld` / `OrangePortal_WarpOverworld`**: Warps Link's coordinates (`$20`, `$22`), sets camera scroll boundaries, applies Link's movement to the camera (`JSL ApplyLinksMovementToCamera`), stores the other portal's coordinates, sets its `SprTimerD, X`, sets `$5D` to `$01`, and returns to the respective portal state.
```asm
Sprite_Portal_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw StateHandler
dw BluePortal
dw OrangePortal
dw BluePortal_WarpDungeon
dw OrangePortal_WarpDungeon
dw BluePortal_WarpOverworld
dw OrangePortal_WarpOverworld
StateHandler:
{
JSR CheckForDismissPortal
JSR RejectOnTileCollision
LDA $7E0FA6 : BNE .BluePortal
LDA #$01 : STA $0307
TXA : STA.w OrangeSpriteIndex
LDA.w SprY, X : STA.w OrangePortal_X
LDA.w SprX, X : STA.w OrangePortal_Y
LDA.b #$01 : STA.w SprSubtype, X
%GotoAction(2)
RTS
.BluePortal
LDA #$02 : STA $0307
TXA : STA.w BlueSpriteIndex
LDA.w SprY, X : STA.w BluePortal_X
LDA.w SprX, X : STA.w BluePortal_Y
LDA.b #$02 : STA.w SprSubtype, X
%GotoAction(1)
RTS
}
BluePortal:
{
%StartOnFrame(0)
%PlayAnimation(0,1,8)
LDA $11 : CMP.b #$2A : BNE .not_warped_yet
STZ $11
.not_warped_yet
CLC
LDA.w SprTimerD, X : BNE .NoOverlap
JSL Link_SetupHitBox
JSL $0683EA ; Sprite_SetupHitbox_long
JSL CheckIfHitBoxesOverlap : BCC .NoOverlap
CLC
LDA $1B : BEQ .outdoors
%GotoAction(3) ; BluePortal_WarpDungeon
.NoOverlap
RTS
.outdoors
%GotoAction(5) ; BluePortal_WarpOverworld
RTS
}
OrangePortal:
{
%StartOnFrame(2)
%PlayAnimation(2,3,8)
LDA $11 : CMP.b #$2A : BNE .not_warped_yet
STZ $11
.not_warped_yet
CLC
LDA.w SprTimerD, X : BNE .NoOverlap
JSL Link_SetupHitBox
JSL $0683EA ; Sprite_SetupHitbox_long
JSL CheckIfHitBoxesOverlap : BCC .NoOverlap
CLC
; JSL $01FF28 ; Player_CacheStatePriorToHandler
LDA $1B : BEQ .outdoors
%GotoAction(4) ; OrangePortal_WarpDungeon
.NoOverlap
RTS
.outdoors
%GotoAction(6) ; OrangePortal_WarpOverworld
RTS
}
BluePortal_WarpDungeon:
{
LDA $7EC184 : STA $20
LDA $7EC186 : STA $22
LDA $7EC188 : STA $0600
LDA $7EC18A : STA $0604
LDA $7EC18C : STA $0608
LDA $7EC18E : STA $060C
PHX
LDA.w OrangeSpriteIndex : TAX
LDA #$40 : STA.w SprTimerD, X
LDA.w SprY, X : STA $7EC184
STA.w BluePortal_Y
LDA.w SprX, X : STA $7EC186
STA.w BluePortal_X
PLX
LDA #$14 : STA $11
%GotoAction(1) ; Return to BluePortal
RTS
}
OrangePortal_WarpDungeon:
{
LDA $7EC184 : STA $20
LDA $7EC186 : STA $22
; Camera Scroll Boundaries
LDA $7EC188 : STA $0600 ; Small Room North
LDA $7EC18A : STA $0604 ; Small Room South
LDA $7EC18C : STA $0608 ; Small Room West
LDA $7EC18E : STA $060C ; Small Room South
PHX
LDA.w BlueSpriteIndex : TAX
LDA #$40 : STA.w SprTimerD, X
LDA.w SprY, X : STA $7EC184
STA.w OrangePortal_Y
LDA.w SprX, X : STA $7EC186
STA.w OrangePortal_X
PLX
LDA #$14 : STA $11
%GotoAction(2) ; Return to OrangePortal
RTS
}
BluePortal_WarpOverworld:
{
LDA.w OrangePortal_X : STA $20
LDA.w OrangePortal_Y : STA $22
LDA $7EC190 : STA $0610
LDA $7EC192 : STA $0612
LDA $7EC194 : STA $0614
LDA $7EC196 : STA $0616
JSL ApplyLinksMovementToCamera
PHX ; Infinite loop prevention protocol
LDA.w OrangeSpriteIndex : TAX
LDA #$40 : STA.w SprTimerD, X
PLX
LDA #$01 : STA $5D
;LDA #$2A : STA $11
%GotoAction(1) ; Return to BluePortal
RTS
}
OrangePortal_WarpOverworld:
{
LDA.w BluePortal_X : STA $20
LDA.w BluePortal_Y : STA $22
LDA $7EC190 : STA $0610
LDA $7EC192 : STA $0612
LDA $7EC194 : STA $0614
LDA $7EC196 : STA $0616
JSL ApplyLinksMovementToCamera
PHX
LDA.w BlueSpriteIndex : TAX
LDA #$40 : STA.w SprTimerD, X
PLX
LDA #$01 : STA $5D
;LDA #$2A : STA $11
%GotoAction(2) ; Return to BluePortal
RTS
}
}
```
## Helper Routines
* **`CheckForDismissPortal`**: Checks a ticker (`$06FE`). If it exceeds `02`, it despawns the active portals (Blue and Orange) and decrements the ticker. Otherwise, it increments the ticker. This ticker needs to be reset during room and map transitions.
* **`RejectOnTileCollision`**: Checks for tile collision. If a portal is placed on an invalid tile (tile attribute `0` or `48`), it despawns the portal, plays an error sound (`SFX2.3C`), and decrements the ticker (`$06FE`).
## Drawing (`Sprite_Portal_Draw`)
This routine handles OAM allocation and animation for the Portal. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
## Design Patterns
* **Two-Way Warping System**: Implements a complex two-way portal system that allows Link to instantly travel between two designated points, enhancing exploration and puzzle design.
* **Context-Sensitive Warping**: Portals can intelligently warp Link between dungeons and the overworld, adapting to the current game context and providing seamless transitions.
* **Persistent Portal Locations**: Portal coordinates are stored in WRAM, allowing them to be placed and remembered across game sessions, enabling dynamic puzzle setups.
* **Link State Management**: Modifies Link's coordinates, camera boundaries, and game mode during warps, ensuring a smooth and consistent player experience during transitions.
* **Collision Detection**: Utilizes `CheckIfHitBoxesOverlap` to accurately detect when Link enters a portal, triggering the warp sequence.
* **Error Handling**: Includes logic to dismiss portals if they are placed on invalid tiles, preventing game-breaking scenarios and providing feedback to the player.
* **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.

View File

@@ -0,0 +1,232 @@
# Switch Track
## Overview
The Switch Track sprite (`!SPRID = Sprite_SwitchTrack`) is an interactive object designed to function as a rotating segment of a minecart track. Its visual appearance and implied path change dynamically based on its `SprAction` (which represents its mode of rotation) and the on/off state of a corresponding switch, stored in `SwitchRam`.
## Sprite Properties
* **`!SPRID`**: `Sprite_SwitchTrack` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `00`
* **`!HVelocity`**: `00`
* **`!Health`**: `01`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `00`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!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_RotatingTrack_Long`)
This routine handles the Switch Track's drawing and dispatches to its main logic if the sprite is active.
```asm
Sprite_RotatingTrack_Long:
{
PHB : PHK : PLB
JSR Sprite_RotatingTrack_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_RotatingTrack_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_RotatingTrack_Prep`)
This routine initializes the Switch Track upon spawning. It sets `SprDefl, X` to `$80`. It then calculates the tile attributes of the tile directly above the switch track and sets `SprAction, X` based on the `SPRTILE` value (normalized by subtracting `$D0`). This `SprAction, X` likely determines the initial mode or orientation of the track.
```asm
Sprite_RotatingTrack_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
; Setup Minecart position to look for tile IDs
; We use AND #$F8 to clamp to a 8x8 grid.
; Subtract 8 from the Y position to get the tile right above instead.
LDA.w SprY, X : AND #$F8 : SEC : SBC.b #$08 : STA.b $00
LDA.w SprYH, X : STA.b $01
LDA.w SprX, X : AND #$F8 : STA.b $02
LDA.w SprXH, X : STA.b $03
; Fetch tile attributes based on current coordinates
LDA.b #$00 : JSL Sprite_GetTileAttr
LDA.w SPRTILE : SEC : SBC.b #$D0 : STA.w SprAction, X
PLB
RTL
}
```
## Constants
* **`SwitchRam = $0230`**: A WRAM address that stores the state (on/off) of each individual switch, indexed by its `SprSubtype`. This allows for multiple independent switch tracks.
## Main Logic & State Machine (`Sprite_RotatingTrack_Main`)
This routine manages the visual state of the Switch Track based on its `SprAction` (mode of rotation) and the corresponding switch state in `SwitchRam`.
* **Modes**: The `SprAction, X` determines the mode of rotation, with four defined modes:
* `0` = TopLeft -> TopRight
* `1` = BottomLeft -> TopLeft
* `2` = TopRight -> BottomRight
* `3` = BottomRight -> BottomLeft
* **State-Based Animation**: For each mode, the `SprFrame, X` (animation frame) is set based on the on/off state of the switch (`SwitchRam, Y`). This visually changes the track's orientation.
```asm
Sprite_RotatingTrack_Main:
{
; Get the subtype of the track so that we can get its on/off state.
LDA.w SprSubtype, X : TAY
LDA.w SprAction, X
JSL UseImplicitRegIndexedLocalJumpTable
dw TopLeftToTopRight
dw BottomLeftToTopLeft
dw TopRightToBottomRight
dw BottomRightToBottomLeft
; 00 = TopLeft -> TopRight
TopLeftToTopRight:
{
LDA.w SwitchRam, Y : BNE .part2
LDA.b #$00 : STA.w SprFrame, X
RTS
.part2
LDA.b #$01 : STA.w SprFrame, X
RTS
}
; 01 = BottomLeft -> TopLeft
BottomLeftToTopLeft:
{
LDA.w SwitchRam, Y : BNE .part2_c
LDA.b #$03 : STA.w SprFrame, X
RTS
.part2_c
LDA.b #$00 : STA.w SprFrame, X
RTS
}
; 02 = TopRight -> BottomRight
TopRightToBottomRight:
{
LDA.w SwitchRam, Y : BNE .part2_a
LDA.b #$01 : STA.w SprFrame, X
RTS
.part2_a
LDA.b #$02 : STA.w SprFrame, X
RTS
}
; 03 = BottomRight -> BottomLeft
BottomRightToBottomLeft:
{
LDA.w SwitchRam, Y : BEQ .part2_b
LDA.b #$03 : STA.w SprFrame, X
RTS
.part2_b
LDA.b #$02 : STA.w SprFrame, X
RTS
}
}
```
## Drawing (`Sprite_RotatingTrack_Draw`)
This routine handles OAM allocation and animation for the Switch Track. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
```asm
Sprite_RotatingTrack_Draw:
{
JSL Sprite_PrepOamCoord
LDA.b #$04 : JSL OAM_AllocateFromRegionB
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 : 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 : 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
.nbr_of_tiles
db 0, 0, 0, 0
.chr
db $44
db $44
db $44
db $44
.properties
db $3D
db $7D
db $FD
db $BD
}
```
## Design Patterns
* **Interactive Puzzle Element**: The Switch Track is a key puzzle element that changes its orientation based on an external switch (likely the `mineswitch` sprite), directly influencing the path of minecarts.
* **State-Based Animation**: The track's animation frame (`SprFrame, X`) is directly controlled by the on/off state of a corresponding switch in `SwitchRam`, providing clear visual feedback to the player about its current configuration.
* **Subtype-Driven State**: The `SprSubtype` is used to index into `SwitchRam`, allowing each individual Switch Track to maintain its own independent state. This enables complex puzzle designs with multiple, distinct switch tracks.
* **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.