- 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.
282 lines
9.9 KiB
Markdown
282 lines
9.9 KiB
Markdown
# 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.
|