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

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