Files
oracle-of-secrets/Docs/Sprites/Objects/PortalSprite.md
scawful aede7551a3 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.
2025-10-03 01:52:48 -04:00

9.9 KiB

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.

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

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