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