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:
172
Docs/Sprites/Objects/Collectible.md
Normal file
172
Docs/Sprites/Objects/Collectible.md
Normal 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.
|
||||
147
Docs/Sprites/Objects/DekuLeaf.md
Normal file
147
Docs/Sprites/Objects/DekuLeaf.md
Normal 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.
|
||||
208
Docs/Sprites/Objects/IceBlock.md
Normal file
208
Docs/Sprites/Objects/IceBlock.md
Normal 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.
|
||||
166
Docs/Sprites/Objects/MineSwitch.md
Normal file
166
Docs/Sprites/Objects/MineSwitch.md
Normal 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.
|
||||
113
Docs/Sprites/Objects/Minecart.md
Normal file
113
Docs/Sprites/Objects/Minecart.md
Normal 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.
|
||||
76
Docs/Sprites/Objects/Pedestal.md
Normal file
76
Docs/Sprites/Objects/Pedestal.md
Normal 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.
|
||||
281
Docs/Sprites/Objects/PortalSprite.md
Normal file
281
Docs/Sprites/Objects/PortalSprite.md
Normal 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.
|
||||
232
Docs/Sprites/Objects/SwitchTrack.md
Normal file
232
Docs/Sprites/Objects/SwitchTrack.md
Normal 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.
|
||||
Reference in New Issue
Block a user