73 Commits

Author SHA1 Message Date
scawful
7453dbdfdd add minecart track starting positions and update ranch girl sprite inclusion 2025-12-09 08:02:38 -05:00
scawful
57012b2656 Ice block push direction validation and documentation
Add side validation to prevent players from manipulating the ice block
by changing direction while in contact. Uses Sprite_DirectionToFacePlayer
to verify Link's position matches his facing direction before allowing push.

Key changes:
- IceBlock_ValidatePushSide: Anti-cheat that validates Link is on the
  correct side of the block for his facing direction
- Direction locking: Push direction locked in SprMiscA until block stops
- Comprehensive documentation of mechanics and sprite RAM usage
- Section headers for code organization

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 08:00:33 -05:00
scawful
f508f9a19d menu scroll fixes 2025-12-08 22:31:08 -05:00
scawful
740571ca7f upgrade submenus with hints, indicators, journal with X and rings with Y in menu 2025-12-08 22:12:05 -05:00
scawful
9b4ee7a9a1 add journal flags to deku scrub, mask salesman, ranch girl 2025-12-08 22:08:18 -05:00
scawful
2d62553065 add docs for asm style guide and oracle system architecture 2025-12-08 22:07:30 -05:00
scawful
6c6e6a0bc6 Add menu journal with main quest and side quest hints and progression tracking 2025-12-08 21:46:33 -05:00
scawful
851da89644 Fix: Zora Sanctuary Waterfall trigger and Lost Woods transition logic 2025-12-08 16:42:19 -05:00
scawful
9a8c6d919a Fix lost woods for ZSCustomOverworld v3 2025-12-08 14:32:20 -05:00
scawful
1aa5878ab1 Fix: Resolve BG color brightness regression, persist Time System tint, and refactor Minecart data 2025-12-08 14:04:12 -05:00
scawful
1c19788ba9 Fix HUD artifact: Revert FloorIndicator overflow from Song of Storms commit
The changes in commit 841ef2d added ~15 bytes to FloorIndicator, exceeding
its 156-byte size limit ($0AFD0C-$0AFDA7) and causing ROM corruption that
manifested as a brown/black tile artifact below the HUD item box.

Reverted changes:
- BNE+JMP pattern back to BEQ (saves 3 bytes)
- Removed Song of Storms rain check from FloorIndicator (saves 12 bytes)

Song of Storms functionality in ZSCustomOverworld.asm remains intact.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-07 13:53:15 -05:00
scawful
d01a4b83a0 Fix ActivateSubScreen: prevent .turnOn from falling through to clear
The .turnOn path was setting $1D=1 then falling through to .normal
which cleared $1D - immediately undoing the overlay enable.

Fix: Add BRA .exit after setting $1D in .turnOn path.
Also remove the unnecessary module $0E check - the real bug was
the fall-through, not menu state interference.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 23:48:19 -05:00
scawful
791ebaf552 Fix menu navigation: restore original up/down behavior
- Keep loop counters in FindNextItem/FindPrevItem (prevents infinite recursion)
- Restore FindNextDownItem/FindNextUpItem to original logic:
  - Move ONE row, then delegate to FindNextItem if slot empty
- Fix boundary check in FindNextUpItem: use BEQ/BPL for signed comparison
  (CMP #$01 : BCS was wrong - unsigned comparison treats $FE as 254 >= 1)

The original design: Up/Down navigate rows, delegate to horizontal scan
if target slot is empty. The loop counters in horizontal navigation
prevent infinite loops when all items are empty.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 23:39:21 -05:00
scawful
841ef2d017 Fix Song of Storms: Rain persists across transitions, dismissal works from any area
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-06 23:07:32 -05:00
scawful
d41dcdadb9 Fix ZSOW vs Day/Night Sprites: Use Oracle_CheckIfNight and move ZSOW include to end 2025-11-22 19:39:18 -05:00
scawful
a8ff3ef0f9 Update tracker: Mark Time System refactor DONE and ZSOW bug ACTIVE 2025-11-22 19:25:37 -05:00
scawful
ad2c00c359 Fix regression in ColorSubEffect: Use 16-bit immediate loading for color constants 2025-11-22 19:22:59 -05:00
scawful
93bd42be8b Refactor Time System: Introduce TimeState struct and modularize RunClock 2025-11-22 19:10:15 -05:00
scawful
52a5ed4b02 Refactor menu system: Add constants and deduplicate input logic 2025-11-22 19:03:56 -05:00
scawful
3ceab24c7c Fix Menu System issues: Journal tilemap, Ocarina selector, and input regression
- Fixed: Journal tilemap not updating by ensuring 5 (Palette/Refresh) flag is set in Menu_Journal and other submenus.
- Fixed: Ocarina menu not clearing main menu selector by realigning Menu_ItemCursorPositions to match Menu_AddressIndex, resolving a data mismatch.
- Fixed: Input entirely breaking in the menu (regression from previous commit) by reverting bash116 update flag from 3 back to 2, which seems to prevent VBlank timing issues.
- Updated Docs/GEMINI.md with recent debugging insights covering processor status mismatch, input polling, VRAM update flags, data table mismatches, and custom NMI handlers.
2025-11-22 17:50:29 -05:00
scawful
2b504d987f Fix Time System BG color tinting and overlay clearing bugs
- Fixed: BG color resetting to untinted value on screen transitions (InitColorLoad2).
- Fixed: Addressing mode error in ColorSubEffect causing incorrect tinting.
- Fixed: Active overlays being cleared when hour advances due to static table lookup and incomplete logic in Overworld_LoadBGColorAndSubscreenOverlay.
- Updated handoff documentation with resolution details.
2025-11-22 16:41:58 -05:00
scawful
8b23049e28 Fix menu system crashes and stability issues
- Fix IrisSpotlight crash ($00F361): Removed errant $0116/$17 writes
  from menu_select_item.asm that corrupted VRAM upload index
- Fix journal stack corruption: Added missing PHB in Journal_CountUnlocked
- Fix P register mismatches: Added SEP #$30 to Menu_RefreshQuestScreen,
  Menu_ScrollFrom, Menu_DrawRingPrompt
- Fix MagicBag crashes: Fixed data bank corruption in error path,
  fixed uninitialized Y register in cursor movement
- Relocate StoryState from volatile $7C to SRAM $7EF39E
- Add bounds checking to HouseTag_Main jump table
- Use long addressing (.l) for SRAM access in custom_tag.asm

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 03:20:35 -05:00
scawful
e485439628 Overhaul menu: Add RHS selector, expand journal, document states 2025-11-21 19:02:36 -05:00
scawful
5cd1248c30 feat: Implement consumable item effects (Pineapple, Honeycomb) and consumption logic with error handling 2025-11-21 17:49:28 -05:00
scawful
117dd70d2c Add implementation plan for Castle Ambush & Guard Capture System, including probe detection and capture mechanics 2025-10-03 16:49:36 -04:00
scawful
5aba4e7311 Update GEMINI.md to enhance documentation structure and clarity, adding comprehensive references for core systems, development guidelines, and sprite documentation. 2025-10-03 16:15:57 -04:00
scawful
ff54149660 Update SpriteCreationGuide 2025-10-03 16:07:58 -04:00
scawful
84fa424871 Refactor Overlords documentation for improved clarity and formatting
- Updated the table formatting for better readability in the Overlords.md file.
- Ensured consistent alignment of columns in the jump table and WRAM description sections.
- Enhanced descriptions for clarity regarding overlord types and their respective data storage.
2025-10-03 16:07:42 -04:00
scawful
26d35364af Add advanced technical documentation for ZScream custom overworld
- Introduced comprehensive documentation covering internal hook architecture, memory management, graphics loading pipeline, sprite loading system, cross-namespace integration, performance considerations, and debugging strategies.
- Detailed sections on adding custom features and modifying existing behaviors, including examples and code snippets.
- Included a complete hook list and memory map quick reference for developers.
2025-10-03 15:50:34 -04:00
scawful
4289e134aa Refactor logging statements to use %log_end macro for consistency across item and menu scripts 2025-10-03 14:36:19 -04:00
scawful
f2b92e816b Refactor sprite logging macros and enhance debug output for better traceability 2025-10-03 13:49:45 -04:00
scawful
07e5717ea2 Enhance sprite creation guide with multi-layered drawing techniques and quest integration details 2025-10-03 01:52:57 -04:00
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
scawful
8c3bf9d95b Add documentation for custom enemy sprites: Eon Scrub, Keese, Leever, and Octorok
- Created detailed markdown files for Eon Scrub, Keese, Leever, and Octorok sprites.
- Included sprite properties, main logic, drawing routines, and design patterns for each sprite.
- Highlighted unique behaviors, state machines, and interactions with the player for each enemy type.
- Ensured clarity in the implementation details and provided assembly code snippets for reference.
2025-10-03 00:31:19 -04:00
scawful
1cc7d84782 Add detailed sprite analysis for Puffstool, Sea Urchin, Thunder Ghost and more
- Introduced comprehensive documentation for the Puffstool sprite, covering properties, core routines, and key behaviors.
- Added analysis for the Sea Urchin sprite, detailing its initialization, state management, and drawing routines.
- Included a thorough examination of the Thunder Ghost sprite, highlighting its dynamic health, lightning attack mechanics, and movement patterns.
2025-10-02 23:55:31 -04:00
scawful
6780dd0d45 Add sprite analysis documentation for various bosses and mini-bosses
- Created detailed documentation for the Kydrog Boss sprite, outlining its phases, behaviors, and mechanics.
- Added analysis for the Manhandla sprite, including its transformation into Big Chuchu and phase management.
- Documented the Octoboss sprite, highlighting its unique mechanics and interactions with a "brother" Octoboss.
- Provided an overview of the Twinrova boss sprite, detailing its transformation and phase-based attacks.
- Included analysis for the Vampire Bat mini-boss, emphasizing its enhanced behavior compared to standard Keese.
- Documented the Wolfos mini-boss, focusing on its integration into a mask quest and unique pacification mechanics.
2025-10-02 21:24:44 -04:00
scawful
0f1e0a8c75 Document unresolved conflicts in sprite loading integration and outline future work for dynamic sprite sets in ZSCustomOverworld. 2025-10-02 14:24:43 -04:00
scawful
55ab99d6f9 Simplify GEMINI search heuristics 2025-10-02 13:16:21 -04:00
scawful
6ba634caa4 Add comprehensive documentation for sprites and systems
- Introduced detailed analysis for the Minecart system, highlighting its state machine, track system, and areas for improvement.
- Created an NPCs analysis document, summarizing various NPC sprites and their functionalities.
- Added an Objects analysis document, covering interactive elements like collectibles, ice blocks, and minecarts.
- Documented the Overlord sprite system, detailing its role in spawning other sprites and managing events.
- Compiled a Dungeons & Indoor Areas document, outlining custom room tags, enhanced mechanics, and advanced collision systems.
- Developed an Overworld Systems Analysis, focusing on the ZSCustomOverworld architecture and its core features.
- Added a Time System document, explaining the in-game clock and day/night cycle management.
- Documented the ZScream Custom Overworld, detailing its data-driven approach and key features.
2025-10-02 12:44:30 -04:00
scawful
27ffaf16d8 Refactor project structure and enhance documentation for "Oracle of Secrets" development. Introduced new epics for core infrastructure, system integration, dungeon polish, quest implementation, and boss enhancements. Improved technical debt tracking with actionable tasks, including restructuring code for better maintainability and readability. Updated known bugs and conflicts, and outlined new features and content for dungeons and quests. Enhanced overall organization and clarity of project documentation. 2025-10-02 12:20:15 -04:00
scawful
90a4a44e72 Add documentation on bosses, npcs, objects, overlords, and the overworld 2025-10-01 23:42:09 -04:00
scawful
15784d397e Update documentation: restructure and expand guides for Dungeons, Items, Masks, Menu, and Music systems 2025-10-01 23:21:57 -04:00
scawful
eeeb9ffeb4 Add detailed documentation for memory mapping, quest flow, and sprite creation
- Created MemoryMap.md to outline WRAM and SRAM structures, including key variables and custom regions.
- Added QuestFlow.md to document main quest progression and major side-quests, detailing triggers, events, rewards, and flags.
- Introduced SpriteCreationGuide.md to provide a comprehensive guide for creating custom sprites, including file setup, properties, main structure, initialization, logic, and drawing routines.
- Updated oracle.org to include new documentation links and improve project organization with infrastructure suggestions.
2025-10-01 22:40:58 -04:00
scawful
8d4d2b8f41 Temporarily comment out sections for porting to ZSOWv3 in ocarina.asm, ZSCustomOverworld.asm, and time_system.asm 2025-10-01 13:29:54 -04:00
scawful
ed1a30adce Add SNES hardware registers and patches documentation 2025-10-01 12:41:57 -04:00
scawful
3066aae151 Add debug printing macros and replace print statements with %print_debug in multiple files 2025-10-01 12:39:51 -04:00
scawful
4137d5bf7c Remove CameraCache definition from overlays.asm 2025-09-20 17:29:49 -04:00
scawful
3c36567947 Fix LostWoods hook bug, inject into ZSCustomOverworld 2025-09-14 13:45:04 -04:00
scawful
c5474fae5d Fix special area time based custom bg color by removing unnecessary buffer operations 2025-09-14 13:43:49 -04:00
scawful
0a68bb4f3d Fix RomToPaletteBuffer in Day/Night system for ZSCustomOverworld 2025-09-14 12:26:16 -04:00
scawful
6f6e17df85 Add lost woods hook to ZSCustomOverworld 2025-09-14 12:23:46 -04:00
scawful
c177991040 Remove manual overrides to special overworld area and spawn points 2025-09-14 12:21:30 -04:00
scawful
6f088cb976 Add SetImpervious macro and update symbols for camera cache and sprite properties 2025-09-14 12:13:27 -04:00
scawful
3834e4df0f Upgrade to ZSOverworld ASM ver3
- Adds tall and wide areas
- Adds support for special overworld maps
2025-09-14 11:00:37 -04:00
scawful
3492713a68 Wolfos: refactor animation handling and streamline movement logic 2025-08-03 17:57:38 -04:00
scawful
22a847bae9 Wolfos: add macros, refactor movement 2025-07-22 08:58:34 -04:00
scawful
2c682aec88 SwitchTrack: Remove call to main during prep 2025-06-24 18:17:42 -04:00
scawful
cbe2f2b8c9 update comments 2025-06-24 18:15:40 -04:00
scawful
8e619ee5e6 Add Menu_Journal to Menu module 2025-06-24 18:15:05 -04:00
scawful
e00ff48be3 Add menu journal code skeleton and tilemaps 2025-06-24 18:14:52 -04:00
scawful
b286103f75 Add Menu_CheckForSpecialMenus fn for Menu_ItemScreen 2025-06-24 18:14:11 -04:00
scawful
c25f4027eb Remove outdated Guia del Probador Beta documentation file 2025-04-12 12:57:48 -04:00
scawful
905ee8582c Refactor Overworld ASM files: replace ZSCustomOverworld_Latest with ZSCustomOverworld, delete obsolete ZCustomOverworld and ZCustomOverworld2 files. 2025-04-12 12:57:21 -04:00
scawful
2892b9c5f0 Move morningstar gfx bin 2025-03-25 20:29:19 -04:00
scawful
e28cdd0f04 Complete Minecart mechanics implementation; mark all tasks as done 2025-03-24 21:03:33 -04:00
scawful
9eace5a53a Remove commented-out code in book_of_secrets and portal_rod for cleaner readability 2025-03-24 21:02:45 -04:00
scawful
62b4ad4e99 Update minecart and pedestal sprite positions; clean up switch track code 2025-03-24 21:02:28 -04:00
scawful
706dcc0710 Refactor Lanmola sprite functions for improved readability and maintainability 2025-03-20 18:41:48 -04:00
scawful
56c098a884 Cleanup switch track 2025-02-16 10:53:07 -05:00
scawful
ee09b6dacc Add comment for checking MinecartTrack positions with breakpoint 2025-02-16 10:41:21 -05:00
scawful
8e6d63c6f8 Remove unused CheckForTrackTiles and update comments in minecart 2025-02-13 09:30:43 -05:00
scawful
5a192edf13 Move UpdateCachedCoords, RoundCoords to sprite_functions 2025-02-13 09:18:40 -05:00
Justin Scofield
875a9b5e89 Merge pull request #105 from scawful/NewMinecart
Add updated minecart collision, switch track, multi cart, T junction tracks
2025-02-12 18:18:24 -05:00
147 changed files with 32401 additions and 12947 deletions

47
Core/ZS ROM MAP.txt Normal file
View File

@@ -0,0 +1,47 @@
Expanded space used by ZScream as of 02/28/2025
Addresses are PC unless stated otherwise.
ZS reserves everything up to 1.5MB or up to 0x150000
And an additional 3 banks at the end of the 2.0MB 0x1E8000 to 0x1FFFFF
0x100000 - 0x107FFF: 1 Bank
Nothing?
0x108000 - 0x10FFFF: 1 Bank
Title screen data
Dungeon map data
0x110000 - 0x117FFF: 1 Bank
Default room header location
Seems to have been some old dungeon object data expansion but that has since been moved.
0x118000 - 0x11FFFF: 1 Bank
Seems to have been some old dungeon object data expansion but that has since been moved.
0x120000 - 0x127FFF: 1 Bank
Expanded overlay data
0x128000 - 0x12FFFF: 1 Bank
Custom collision data
0x130000 - 0x137FFF: 1 Bank
Overworld map data overflow
0x138000 - 0x13FFFF: 1 Bank
Expanded Dungeon object data
0x140000 - 0x147FFF: 1 Bank
Custom overworld data
0x148000 - 0x14FFFF: 1 Bank
Expanded Dungeon object data
0x1E0000 - 0x1E7FFF: 1 Bank
Custom ASM Patches
0x1E8000 - 0x1EFFFF: 1 Bank
Expanded Tile16 space
0x1F0000 - 0x1FFFFF: 2 Banks
Edpanded Tile32 space

159
Core/patches.asm Normal file
View File

@@ -0,0 +1,159 @@
; This file contains all direct patches to the original ROM.
; It is included from Oracle_main.asm.
; UnderworldTransition_ScrollRoom
org $02BE5E : JSL Graphics_Transfer
; Whirlpool
org $1EEEE4 : JSL DontTeleportWithoutFlippers
; SpriteDraw_Roller
org $058EE6 : JSL PutRollerBeneathLink
; =========================================================
; Sprite Recoil and Death
; TODO: Sprite_AttemptKillingOfKin
; Kydreeok Head die like Sidenexx
org $06F003 : CMP.b #$CF
; Remove sidenexx death from booki
org $06EFFF : NOP #4
; Make Dark Link die like sidenexx
org $06F003 : CMP.b #$C1
; Make Helmet ChuChu recoil link
org $06F37D : CMP.b #$05
; Make Kydreeok head recoil Link
org $06F381 : CMP.b #$CF
; =========================================================
InCutScene = $7EF303
; Player2JoypadReturn
org $0083F8
LDA InCutScene : BEQ .notInCutscene
STZ $F0
STZ $F2
STZ $F4
STZ $F6
STZ $F8
STZ $FA ; kill all input
.notInCutscene
RTS
assert pc() <= $00841E
; =========================================================
org $1EF27D
ShopItem_Banana:
{
JSR $F4CE ; SpriteDraw_ShopItem
JSR $FE78 ; Sprite_CheckIfActive_Bank1E
JSL $1EF4F3 ; Sprite_BehaveAsBarrier
JSR $F391 ; ShopItem_CheckForAPress
BCC .exit
LDA.l Bananas : CMP.b #$0A : BCS .error
LDA.b #$1E : LDY.b #$00
JSR $F39E ; ShopItem_HandleCost
BCC $F1A1 ; ShopItem_GiveFailureMessage
STZ.w SprState,X
INC.b Bananas
LDY.b #$42 : JSR $F366 ; ShopItem_HandleReceipt
.exit
RTS
.error
JSR $F38A ; ShopItem_PlayBeep
}
assert pc() <= $1EF2AB
; =========================================================
; Shop item heart OAM
; SpriteDraw_ShopItem
org $1EF42E
dw -4, 16 : db $03, $02, $00, $00 ; 3
dw -4, 16 : db $03, $02, $00, $00 ; 3
dw 4, 16 : db $30, $02, $00, $00 ; 0
dw 0, 0 : db $E5, $03, $00, $02 ; item
dw 4, 11 : db $38, $03, $00, $00 ; shadow
; =========================================================
; Octoballoon_FormBabby
; Reduce by half the number of babies spawned
org $06D814 : LDA.b #$02
; SpritePrep_HauntedGroveOstritch
org $068BB2 : NOP #11
; HauntedGroveRabbit_Idle
org $1E9A8F : NOP #5
; MedallionTablet (Goron)
org $05F274 : LDA.l $7EF378 ; Unused SRAM
org $08C2E3 : dw $006F ; BUTTER SWORD DIALOGUE
; Fix the capital 'B' debug item cheat.
org $0CDC26 : db $80 ; replace a $F0 (BEQ) with a $80 (BRA).
; Update Catfish Item Get to Bottle
org $1DE184 : LDA.b #$16 : STA.w $0D90, X
; Follower_Disable
; Don't disable Kiki so we can switch maps with him.
org $09ACF3 : LDA.l $7EF3CC : CMP.b #$0E
; Kiki, don't care if we're not in dark world
org $099FEB : LDA.b $8A : AND.b #$FF
org $1EE48E : NOP #6
; Kiki activate cutscene 3 (tail palace)
org $1EE630 : LDA.b #$03 : STA.w $04C6
; Kid at ranch checks for flute
org $05FF7D : LDA.l $7EF34C : CMP.b #$01
; Raven Damage (LW/DW)
org $068963 : db $81, $84
; Running Man draw palette
org $05E9CD
SpriteDraw_RunningBoy:
#_05E9CD: dw 0, -8 : db $2C, $00, $00, $02
#_05E9D5: dw 0, 0 : db $EE, $0E, $00, $02
#_05E9DD: dw 0, -7 : db $2C, $00, $00, $02
#_05E9E5: dw 0, 1 : db $EE, $4E, $00, $02
#_05E9ED: dw 0, -8 : db $2A, $00, $00, $02
#_05E9F5: dw 0, 0 : db $CA, $0E, $00, $02
#_05E9FD: dw 0, -7 : db $2A, $00, $00, $02
#_05EA05: dw 0, 1 : db $CA, $4E, $00, $02
#_05EA0D: dw 0, -8 : db $2E, $00, $00, $02
#_05EA15: dw 0, 0 : db $CC, $0E, $00, $02
#_05EA1D: dw 0, -7 : db $2E, $00, $00, $02
#_05EA25: dw 0, 1 : db $CE, $0E, $00, $02
#_05EA2D: dw 0, -8 : db $2E, $40, $00, $02
#_05EA35: dw 0, 0 : db $CC, $4E, $00, $02
#_05EA3D: dw 0, -7 : db $2E, $40, $00, $02
#_05EA45: dw 0, 1 : db $CE, $4E, $00, $02
; Sword Barrier Sprite Prep
; Skip overworld flag check, sprite is indoors now
org $06891B : NOP #12

View File

@@ -138,6 +138,27 @@ Sprite_InvertSpeed_Y:
STA.w SprYSpeed, X
RTL
UpdateCachedCoords:
{
LDA.w SprY, X : STA.w SprCachedY+0
LDA.w SprX, X : STA.w SprCachedX+0
LDA.w SprYH, X : STA.w SprCachedY+1
LDA.w SprXH, X : STA.w SprCachedX+1
RTS
}
RoundCoords:
{
; Clamp the Y coord to the nearest multiple of 8.
LDA.b $00 : CLC : ADC.b #$04 : AND.b #$F8 : STA.b $00 : STA.w SprY, X
; Clamp the X coord to the nearest multiple of 8.
LDA.b $02 : CLC : ADC.b #$04 : AND.b #$F8 : STA.b $02 : STA.w SprX, X
JSR UpdateCachedCoords
RTS
}
; =========================================================
Sprite_SelectNewDirection:

View File

@@ -205,6 +205,12 @@ macro SetHarmless(value)
STA.w SprNbrOAM, X
endmacro
macro SetImpervious(value)
LDA.w SprDefl, X
EOR.b #(<value>)<<2
STA.w SprDefl, X
endmacro
; Set Room Flag (Chest 6)
; Do not use if you have more than 5 chests or a small key under a pot
; in that room unless you want it to be already opened/taken

View File

@@ -2,7 +2,7 @@
; Game state
; 0x00 - Very start; progress cannot be saved in this state
; 0x01 - Uncle reached
; 0x02 - Zelda rescued
; 0x02 - Farore intro over | Zelda rescued
; 0x03 - Agahnim defeated
GameState = $7EF3C5
@@ -85,6 +85,16 @@ PrevScroll = $7EF39A
; d - done
MagicBeanProg = $7EF39B
JournalState = $7EF39C
; State machine for Link's House intro sequence (custom_tag.asm)
; 0x00 - Telepathic Plea phase
; 0x01 - Wake Up Player phase
; 0x02 - End (intro complete)
StoryState = $7EF39E
; 7EF403 - 7EF4FD Unused block
; .... .cpw
; c - courage
; p - power
@@ -466,8 +476,28 @@ FOLLOWERING = $7EF3D3
UNUSED_7EF3D4 = $7EF3D4
UNUSED_7EF3D5 = $7EF3D5
UNUSED_7EF3D6 = $7EF3D6
UNUSED_7EF3D7 = $7EF3D7
UNUSED_7EF3D8 = $7EF3D8
; Side Quest Progress Flags
; .dgo mwcn
; n - Met Mask Salesman (shown "need Ocarina" dialogue)
; c - Found cursed Cucco at ranch (shown first dialogue)
; w - Found withering Deku Scrub (shown first dialogue)
; m - Got Mushroom from Toadstool Woods
; o - Old Man Mountain quest active
; g - Goron quest active (collecting rock meat)
; d - (reserved)
SideQuestProg = $7EF3D7
; Side Quest Progress Flags 2
; .bts pfmr
; r - Ranch Girl transformed back (dialogue shown)
; m - Mask Salesman taught Song of Healing
; f - Fortune teller visited (any fortune)
; p - Potion shop visited with mushroom
; s - Deku Scrub soul freed (before mask given)
; t - Tingle met (any map purchased)
; b - Bean beanstalk grown (final stage)
SideQuestProg2 = $7EF3D8
; Player name
NAME1L = $7EF3D9

View File

@@ -1,4 +1,3 @@
struct Sprite $7E0BA0
{
.BulletProof: skip 16
@@ -61,3 +60,16 @@ struct Sprite $7E0BA0
}
endstruct
struct TimeState $7EE000
{
.Hours: skip 1
.Minutes: skip 1
.Speed: skip 1
.Padding: skip 13 ; Pad to $7EE010
.BlueVal: skip 2
.GreenVal: skip 2
.RedVal: skip 2
.TempColor: skip 2
.SubColor: skip 2
}
endstruct

View File

@@ -9,8 +9,6 @@ MenuScrollHDirection: skip 2
MenuItemValueSpoof: skip 2
ShortSpoof: skip 1
MusicNoteValue: skip 2
OverworldLocationPointer: skip 2
HasGoldstar: skip 1
GoldstarOrHookshot: skip 1
Neck_Index: skip 1
Neck1_OffsetX: skip 1
@@ -43,6 +41,9 @@ CurrentSong = $030F
; 03 - Song of Storms
SongFlag = $FE
; Overlay camera cache variable
CameraCache = $0632
; =========================================================
; The record format for the low table is 4 bytes:
; byte OBJ*4+0: xxxxxxxx
@@ -89,12 +90,12 @@ SprFrame = $0D90 ; Indexes the SprGfx for drawing
SprGfx = $0DC0 ; Determine the GFX used for the sprite
SprMiscA = $0DA0 ; Direction, position, or other misc usage
SprMiscB = $0DB0 ; Various usages, truly auxiliary
SprMiscB = $0DB0 ; Prober parent sprite ID, misc
SprMiscC = $0DE0 ; Cardinal direction the sprite is facing
SprMiscD = $0E90 ; Pikit stolen item, misc usage
SprMiscE = $0EB0 ; Head direction 0123 -> udlr
SprMiscF = $0EC0 ;
SprMiscG = $0ED0 ;
SprMiscF = $0EC0 ; Clones
SprMiscG = $0ED0 ; Probe whistle sfx
SprCustom = $1CC0 ;
@@ -144,7 +145,7 @@ SprState = $0DD0
; nios pppt
; n - if set, don't draw extra death anim
; i - impervious to attacks and collision? TODO
; i - impervious to attacks and collision (0: normal | 1: clink)
; o - shadow size (0: normal | 1: small)
; s - shadow (0: no shadow | 1: shadow)
; p - palette used for OAM props

131
Docs/Core/Link.md Normal file
View File

@@ -0,0 +1,131 @@
# Bank $07: Core Player (Link) Engine Analysis
**File**: `ALTTP/bank_07.asm`
**Address Range**: `$078000` - `$07FFFF`
This bank is dedicated entirely to the player character, Link. It contains his core state machine, which governs everything from movement and physics to item usage and interaction with the world. It is executed every frame that the player has control and is not in a cutscene.
---
### 1. Main Entry Point: `Link_Main`
* **Routine**: `Link_Main` (`#_078000`)
* **Purpose**: This is the top-level function for all player-related code, called once per frame from the main game loop when the game is in a playable state (e.g., Overworld or Underworld).
* **Functionality**:
1. It first checks if the player is in a state that prevents control (e.g., a cutscene, indicated by `$02E4` being non-zero).
2. If the player has control, it calls `Link_ControlHandler`, which is the heart of the player engine.
3. After the main handler runs, it calls `HandleSomariaAndGraves` to process interactions with those specific objects, which need to be checked every frame.
---
### 2. The Player State Machine: `Link_ControlHandler`
* **Routine**: `Link_ControlHandler` (`#_07807F`)
* **Purpose**: This function acts as a state machine dispatcher. It reads Link's current state from a single, critical WRAM variable and jumps to the appropriate logic handler for that state.
* **Critical WRAM Variable**: `$7E005D` (`LINKDO`) - This byte holds Link's current state ID. Modifying this value directly forces Link into a different state.
* **Execution Flow**:
1. **Damage Check**: Before any other action, the handler checks if Link has taken damage (`$7E0373`, `HURTME`). If so, it processes the damage, checks for the Magic Cape (`$7E0055`), reduces health, and can trigger a state change to `LinkState_Recoil` or the death sequence.
2. **State Dispatch**: It reads the value of `LINKDO`, multiplies it by two (since each entry is a 2-byte address), and uses it as an index into the `.vectors` jump table (`#_078041`). It then performs a `JMP` to the corresponding state handler routine.
---
### 3. Link State Vector Table
This table at `#_078041` defines all 31 possible states for Link. Understanding this is key to modifying player behavior.
| State ID | Label (`#_07....`) | Description |
|:---:|---|---|
| `0x00` | `LinkState_Default` | The normal on-foot state for walking, standing, and most basic interactions. |
| `0x01` | `LinkState_Pits` | Handles the logic for falling into a pit. |
| `0x02` | `LinkState_Recoil` | Handles being knocked back by an enemy or obstacle. |
| `0x03` | `LinkState_SpinAttack` | Manages the spin attack animation and hitbox. |
| `0x04` | `LinkState_Swimming` | The state for swimming in water. |
| `0x05` | `LinkState_OnIce` | Handles movement physics for icy surfaces. |
| `0x06` | `LinkState_Recoil` | A duplicate pointer to the recoil state, likely for a different impact type. |
| `0x07` | `LinkState_Zapped` | A special recoil state for electrical damage. |
| `0x08` | `LinkState_UsingEther` | Handles the animation and logic for using the Ether medallion. |
| `0x09` | `LinkState_UsingBombos` | Handles the animation and logic for using the Bombos medallion. |
| `0x0A` | `LinkState_UsingQuake` | Handles the animation and logic for using the Quake medallion. |
| `0x0B` | `LinkState_HoppingSouthOW` | Manages the multi-frame action of hopping off a ledge to the south. |
| `0x0C` | `LinkState_HoppingHorizontallyOW` | Manages hopping off a ledge to the east or west. |
| `0x0D` | `LinkState_HoppingDiagonallyUpOW` | Manages hopping off a ledge diagonally up-left or up-right. |
| `0x0E` | `LinkState_HoppingDiagonallyDownOW`| Manages hopping off a ledge diagonally down-left or down-right. |
| `0x0F` | `LinkState_0F` | A generic ledge-hopping state. |
| `0x10` | `LinkState_0F` | (Duplicate) A generic ledge-hopping state. |
| `0x11` | `LinkState_Dashing` | The state for running with the Pegasus Boots. |
| `0x12` | `LinkState_ExitingDash` | The brief turn-around animation after a dash collides with a wall. |
| `0x13` | `LinkState_Hookshotting` | Manages Link's state while the hookshot is extended. |
| `0x14`| `LinkState_CrossingWorlds` | Handles the Magic Mirror animation and world transition. |
| `0x15` | `LinkState_ShowingOffItem` | The "item get" pose when Link holds an item above his head. |
| `0x16` | `LinkState_Sleeping` | For the beginning of the game when Link is in bed. |
| `0x17` | `LinkState_Bunny` | The state for when Link is transformed into a bunny in the Dark World. |
| `0x18` | `LinkState_HoldingBigRock` | The state for lifting a heavy, dark-colored rock (requires Titan's Mitt). |
| `0x19` | `LinkState_ReceivingEther` | The cutscene for receiving the Ether medallion from the tablet. |
| `0x1A` | `LinkState_ReceivingBombos` | The cutscene for receiving the Bombos medallion from the tablet. |
| `0x1B` | `LinkState_ReadingDesertTablet` | The cutscene for reading the Desert Palace tablet. |
| `0x1C` | `LinkState_TemporaryBunny` | The brief bunny transformation sequence when entering the Dark World. |
| `0x1D` | `LinkState_TreePull` | The state for pulling on the tree in the haunted grove for the race game. |
| `0x1E` | `LinkState_SpinAttack` | (Duplicate) A second entry for the spin attack state. |
---
### 4. Analysis of Core States
#### `LinkState_Default` (`#_078109`)
This is the most complex state and serves as the foundation for player control. It is a large routine that dispatches to numerous sub-handlers.
* **Initial Checks**:
* `Link_HandleBunnyTransformation`: Checks if Link should transform into a bunny.
* Checks for recoil/damage (`$7E004D`) and branches to a simplified physics handler if necessary.
* **Action Dispatching**: If not recoiling, it checks for player input and calls the appropriate action handler.
* `Link_HandleToss`: Checks if Link is throwing a carried object.
* `Link_HandleAPress`: Handles context-sensitive actions for the A button (talk, read, lift, open, dash).
* `Link_HandleYItem`: Manages using the currently selected item (bow, boomerang, rods, etc.).
* `Link_HandleSwordCooldown`: Manages sword swings and charging a spin attack.
* **Physics and Collision**: If no other action is taken, it processes movement.
* `ResetAllAcceleration`: Clears speed values if Link is standing still.
* `Link_HandleDiagonalCollision` & `Link_HandleCardinalCollision`: Check for collisions with walls and objects.
* `JSL Link_HandleVelocity`: The main physics engine. Applies acceleration, deceleration, and the final movement vector to Link's coordinates.
* **Animation & Camera**:
* `JSL Link_HandleMovingAnimation_FullLongEntry`: Updates Link's sprite graphics based on his direction and action.
* `HandleIndoorCameraAndDoors`: Manages camera scrolling and door transitions indoors.
#### `LinkState_Recoil` (`#_0786B5`)
This state demonstrates how control is temporarily taken from the player.
* **Z-Axis Movement**: It uses `$7E0024` (Link's Z-position) and `$7E0029` (knockback Z-velocity) to handle Link being knocked into the air and falling back down.
* **Timer-Based**: The duration of the recoil is controlled by a countdown timer in `$7E0046` (`INPAIN`). Once the timer reaches zero, Link's state is typically returned to `LinkState_Default`.
* **Collision & Landing**: While in recoil, it still checks for collisions. It also has special checks for landing, such as `Link_SplashUponLanding` if he falls into water, which can change his state to `LinkState_Swimming`.
#### `LinkState_Bunny` (`#_0783A1`)
This state shows a persistent change in abilities.
* **Simplified Controls**: The bunny state has a much simpler control handler. It still allows for movement but disables all item and sword usage.
* **State Check**: It constantly checks for the condition that allows Link to transform back: the presence of the Moon Pearl (`$7EF357`). If the pearl is obtained or Link leaves the Dark World, it triggers the transformation back to the default state.
---
### 5. Key WRAM Variables for Link
This bank relies on a large number of WRAM addresses to function. Understanding these is critical to debugging or modifying player logic.
| Address | Label | Description |
|:---:|---|---|
| `$7E005D` | `LINKDO` | **Link's State**: The primary state ID, used as an index for the state machine. |
| `$7E0020/21`| `POSY` | Link's 16-bit Y-coordinate. |
| `$7E0022/23`| `POSX` | Link's 16-bit X-coordinate. |
| `$7E0024` | `POSZ` | Link's 8-bit Z-coordinate (height). |
| `$7E0026` | - | Link's facing direction. |
| `$7E0027/28`| - | Link's Y/X velocity. |
| `$7E002A/2B`| - | Link's Y/X sub-pixel position. |
| `$7E003A` | - | Action flags (bitfield for sword charging, etc.). |
| `$7E0046` | `INPAIN` | Recoil/invincibility timer after taking damage. |
| `$7E004D` | - | A flag indicating Link is in a recoil state. |
| `$7E0303` | - | The ID of the currently selected Y-button item. |
| `$7E0373` | `HURTME` | Damage value to be applied to Link on the next frame. |
| `$7E037B` | - | A flag that temporarily disables taking damage. |
| `$7E0372` | - | A flag indicating Link is currently dashing. |

181
Docs/Core/MemoryMap.md Normal file
View File

@@ -0,0 +1,181 @@
# Memory Map
This document provides a detailed map of the WRAM and SRAM memory regions, serv| `$7EF358` | `WolfMask` | Flag indicating if the player has obtained the Wolf Mask. |
### Repurposed Vanilla SRAM Blocks
The following blocks were marked "unused" in vanilla ALTTP but are now utilized for OOS custom data:
#### **Block $7EF38A-$7EF3C4** - Collectibles & Custom Progression
*This is a large block of vanilla unused SRAM now used for various collectibles, side-quests, and tracking systems.*
| Address | Label | Description | Verified |
|----------|---------------------|--------------------------------------------------------------------------|----------|
| `$7EF38A` | `FishingRod` | Flag indicating if the player has the Fishing Rod. | ✓ |
| `$7EF38B` | `Bananas` | Number of bananas collected for side-quest. | ✓ |
| `$7EF38D` | `Pineapples` | Number of pineapples collected. | ✓ |
| `$7EF38F` | `RockMeat` | Number of rock meat items collected. | ✓ |
| `$7EF391` | `Seashells` | Number of secret seashells collected. | ✓ |
| `$7EF393` | `Honeycomb` | Number of honeycombs collected. | ✓ |
| `$7EF395` | `DekuSticks` | Number of Deku sticks collected. | ✓ |
| `$7EF396` | `TingleMaps` | Tingle map collection tracking. | ✓ |
| `$7EF397` | `TingleId` | Tingle identification value. | ✓ |
| `$7EF398` | `Scrolls` | Bitfield tracking lore scroll collection (7 dungeons: `.dgi zktm`). | ✓ |
| `$7EF39A` | `PrevScroll` | Tracks the previous scroll for re-reading old hints. | ✓ |
| `$7EF39B` | `MagicBeanProg` | Multi-day growth cycle tracking for magic bean side-quest (`.dts fwpb`). | ✓ |
| `$7EF39C` | `JournalState` | Current state of the player's journal. | ✓ |
| `$7EF39D` | `SRAM_StormsActive` | Flag indicating if the Song of Storms effect is active. | ✓ |
| `$7EF39E` | `StoryState` | State machine for Link's House intro sequence (0-2). | ✓ |
#### **Block $7EF403-$7EF4FD** - Partially Repurposed
*Most of this block remains unused, but OOS utilizes a portion for the Dreams collectible system.*
| Address | Label | Description | Verified |
|----------|-----------|--------------------------------------------------------------------|----------|
| `$7EF410` | `Dreams` | Bitfield tracking collection of three Dreams (`.cpw`: Courage, Power, Wisdom). | ✓ |
> **💡 Usage Notes:**
> - **Scrolls** (`$7EF398`): One scroll per dungeon (7 total). Bitfield format: `.dgi zktm` where each letter represents a dungeon (m=Mushroom Grotto, t=Tail Palace, k=Kalyxo Castle, z=Zora Temple, i=Glacia Estate, g=Goron Mines, d=Dragon Ship).
> - **MagicBeanProg** (`$7EF39B`): Tracks multi-day growth cycle with bitfield `.dts fwpb` (b=bean planted, w=watered, p=pollinated, f=first day, s=second day, t=third day, d=done).
> - **Dreams** (`$7EF410`): Similar to vanilla Pendants, tracks three key collectibles. Bitfield: `.cpw` (c=Courage, p=Power, w=Wisdom).
### SRAM Address Contradictions (Source File Notes)
The following addresses appear with both active label definitions and `UNUSED_` markers in `Core/sram.asm`. **The label definitions take precedence** - these addresses are actively used:
- `$7EF3D4` - Both `MakuTreeQuest` and `UNUSED_7EF3D4` (✓ **In Use**)
- `$7EF3D6` - Both `OOSPROG` and `UNUSED_7EF3D6` (✓ **In Use**)
- `$7EF38A` - Both `FishingRod` and `UNUSED_7EF38A` (✓ **In Use**)
These contradictions appear to be legacy comments from the vanilla ALTTP disassembly that were not removed when OOS repurposed these addresses.
as a central reference for understanding the game's state.
## 1. WRAM (Work RAM) - `$7E0000`
This section details the layout of the game's volatile memory.
### Key Vanilla WRAM Variables
*This section contains a table listing critical vanilla WRAM addresses, their labels (from `Core/ram.asm` and `Core/symbols.asm`), and their purpose.*
| Address | Label | Description |
|----------|------------|---------------------------------------------------|
| `$7E0010` | `MODE` | The main game state/module index. |
| `$7E0011` | `SUBMODE` | The sub-state for the current game mode. |
| `$7E001A` | `FRAME` | A counter that increments each non-lagging frame. |
| `$7E001B` | `INDOORS` | A flag indicating if Link is indoors (0x01) or outdoors (0x00). |
| `$7E002F` | `DIR` | The direction Link is facing (0=U, 2=D, 4=L, 6=R). |
| `$7E005D` | `LINKDO` | Link's personal state machine ID (walking, swimming, etc.). |
| `$7E008A` | `OWSCR` | The current Overworld screen ID. |
| `$7E00A0` | `ROOM` | The current Underworld room ID. |
| `$7E02E0` | `BUNNY` | A flag indicating if Link is in his bunny form (0x01). |
| `$7E031F` | `IFRAMES` | Link's invincibility frame timer after taking damage. |
| `$7E0DD0` | `SprState` | An array storing the state for each of the 16 sprites. |
| `$7E0E20` | `SprType` | An array storing the ID for each of the 16 sprites. |
| `$7E0E50` | `SprHealth`| An array storing the health for each of the 16 sprites. |
### Custom WRAM Region - `$7E0730+`
*This section details the custom WRAM area defined in `Core/ram.asm` and `Core/symbols.asm`. It explains the purpose of each custom variable.*
| Address | Label | Description |
|----------|------------------------|--------------------------------------------------------------------------|
| `$7E0730` | `MenuScrollLevelV` | Vertical scroll position for the menu. |
| `$7E0731` | `MenuScrollLevelH` | Horizontal scroll position for the menu. |
| `$7E0732` | `MenuScrollHDirection` | The direction of horizontal scrolling in the menu. |
| `$7E0734` | `MenuItemValueSpoof` | Used to temporarily override the displayed value of a menu item. |
| `$7E0736` | `ShortSpoof` | A shorter version of the spoof value. |
| `$7E0737` | `MusicNoteValue` | The value of the current music note being played. |
| `$7E0739` | `GoldstarOrHookshot` | Differentiates between the vanilla Hookshot and the custom Goldstar item. |
| `$7E073A` | `Neck_Index` | Used for multi-part sprites, like a centipede body. |
| `$7E0745` | `FishingOrPortalRod` | Differentiates between the Fishing Rod and the Portal Rod. |
---
## 2. SRAM (Save RAM) - `$7EF000`
This section details the layout of the save file memory.
> **🔍 SRAM Verification Note**: All custom SRAM variables documented below have been cross-referenced with `Core/sram.asm`. Some addresses in the source file have contradictory `UNUSED_` markers alongside actual label definitions (e.g., `OOSPROG = $7EF3D6` vs `UNUSED_7EF3D6 = $7EF3D6`). **The actual usage takes precedence** - if a label is defined for an address, it is considered in-use regardless of `UNUSED_` markers. This appears to be a legacy comment artifact from the vanilla ALTTP disassembly.
### Key Vanilla SRAM Variables
*This section lists key vanilla save data locations, such as inventory, health, and progression flags, as defined in `Core/sram.asm`.*
| Address | Label | Description |
|----------|------------|-------------------------------------------|
| `$7EF340` | `Bow` | The player's current bow type (0x00-0x04). |
| `$7EF343` | `Bombs` | The number of bombs the player has. |
| `$7EF359` | `Sword` | The player's current sword type (0x00-0x04). |
| `$7EF35A` | `Shield` | The player's current shield type (0x00-0x03). |
| `$7EF360` | `Rupees` | The player's current rupee count. |
| `$7EF36C` | `MAXHP` | The player's maximum health (1 heart = 8 HP). |
| `$7EF36D` | `CURHP` | The player's current health. |
| `$7EF374` | `Pendants` | A bitfield for the collected pendants (Courage, Power, Wisdom). |
| `$7EF37A` | `Crystals` | A bitfield for the collected crystals from Dark World dungeons. |
| `$7EF3C5` | `GameState`| The main progression state of the game. |
### Custom SRAM Region
*This is a critical section. It provides a comprehensive breakdown of all custom variables added to the SRAM, explaining what each flag or value represents. This information is primarily found in `Core/sram.asm`.*
| Address | Label | Description |
|----------|-------------------|--------------------------------------------------------------------------|
| `$7EF3D6` | `OOSPROG` | A primary bitfield for major quest milestones unique to Oracle of Secrets. |
| `$7EF3C6` | `OOSPROG2` | A secondary bitfield for less critical progression flags. |
| `$7EF3D4` | `MakuTreeQuest` | A flag indicating if the Maku Tree has met Link. |
| `$7EF3C7` | `MapIcon` | Controls the position of the guiding 'X' on the world map. |
| `$7EF351` | `CustomRods` | A flag to differentiate between the Fishing Rod (1) and Portal Rod (2). |
| `$7EF38A` | `FishingRod` | Flag indicating if the player has the Fishing Rod. |
| `$7EF38B` | `Bananas` | The number of bananas collected for a side-quest. |
| `$7EF391` | `Seashells` | The number of secret seashells collected. |
| `$7EF398` | `Scrolls` | A bitfield tracking which of the lore scrolls have been found. |
| `$7EF39B` | `MagicBeanProg` | Tracks the multi-day growth cycle of the magic bean side-quest. |
| `$7EF39C` | `JournalState` | The current state of the player's journal. |
| `$7EF39D` | `SRAM_StormsActive`| A flag indicating if the Song of Storms effect is active. |
| `$7EF39E` | `StoryState` | State machine for Link's House intro sequence (0=Telepathy, 1=WakeUp, 2=End). |
| `$7EF410` | `Dreams` | A bitfield tracking the collection of the three "Dreams" (Courage, Power, Wisdom). |
| `$7EF347` | `ZoraMask` | Flag indicating if the player has obtained the Zora Mask. |
| `$7EF348` | `BunnyHood` | Flag indicating if the player has obtained the Bunny Hood. |
| `$7EF349` | `DekuMask` | Flag indicating if the player has obtained the Deku Mask. |
| `$7EF34D` | `RocsFeather` | Flag indicating if the player has obtained Roc's Feather. |
| `$7EF352` | `StoneMask` | Flag indicating if the player has obtained the Stone Mask. |
| `$7EF358` | `WolfMask` | Flag indicating if the player has obtained the Wolf Mask. |
## 3. Custom Code and Data Layout (ROM Banks)
This section details the allocation of custom code and data within the ROM banks, as defined by `org` directives in the project's assembly files. The order of `incsrc` directives in `Oracle_main.asm` is crucial for the final ROM layout.
| Bank (Hex) | Address Range (PC) | Purpose / Contents |
|------------|-----------------------|--------------------------------------------------------|
| $20 | `$208000` - `$20FFFF` | Expanded Music |
| $21-$27 | | ZScream Reserved |
| $28 | `$288000` - `$28FFFF` | ZSCustomOverworld data and code |
| $29-$2A | | ZScream Reserved |
| $2B | `$2B8000` - `$2BFFFF` | Items |
| $2C | `$2C8000` - `$2CFFFF` | Underworld/Dungeons |
| $2D | `$2D8000` - `$2DFFFF` | Menu |
| $2E | `$2E8000` - `$2EFFFF` | HUD |
| $2F | `$2F8000` - `$2FFFFF` | Expanded Message Bank |
| $30 | `$308000` - `$30FFFF` | Sprites |
| $31 | `$318000` - `$31FFFF` | Sprites |
| $32 | `$328000` - `$32FFFF` | Sprites |
| $33 | `$338000` - `$33FFFF` | Moosh Form Gfx and Palette |
| $34 | `$348000` - `$34FFFF` | Time System, Custom Overworld Overlays, Gfx |
| $35 | `$358000` - `$35FFFF` | Deku Link Gfx and Palette |
| $36 | `$368000` - `$36FFFF` | Zora Link Gfx and Palette |
| $37 | `$378000` - `$37FFFF` | Bunny Link Gfx and Palette |
| $38 | `$388000` - `$38FFFF` | Wolf Link Gfx and Palette |
| $39 | `$398000` - `$39FFFF` | Minish Link Gfx |
| $3A | `$3A8000` - `$3AFFFF` | Mask Routines, Custom Ancillae (Deku Bubble) |
| $3B | `$3B8000` - `$3BFFFF` | GBC Link Gfx |
| $3C | | Unused |
| $3D | | ZS Tile16 |
| $3E | | LW ZS Tile32 |
| $3F | | DW ZS Tile32 |
| $40 | `$408000` - `$40FFFF` | LW World Map |
| $41 | `$418000` - `$41FFFF` | DW World Map |
| Patches | Various | Targeted modifications within vanilla ROM addresses | `Core/patches.asm`, `Util/item_cheat.asm` |

104
Docs/Core/Ram.md Normal file
View File

@@ -0,0 +1,104 @@
# RAM Analysis: The Engine's State
This document provides a high-level analysis of how Work RAM (WRAM) and Save RAM (SRAM) are used to manage the game's state. For a raw list of addresses, see `Core/ram.asm` and `Core/sram.asm`.
## 1. The Core Game Loop: WRAM in Motion
The entire game is driven by a master state machine whose state is stored in a single WRAM variable:
- **`MODE` (`$7E0010`):** This is the game's primary state index. The main loop in `bank_00.asm` reads this value every frame and jumps to the corresponding module in the `Module_MainRouting` table.
- **`SUBMODE` (`$7E0011`):** Many modules have their own internal state machines. This variable holds the sub-state for the current `MODE`.
This `MODE`/`SUBMODE` pattern is the fundamental driver of the game's flow. For example:
- When Link opens the menu, the game sets `MODE` to `0x0E` (Interface), which gives control to the menu engine.
- When Link talks to a character, `Interface_PrepAndDisplayMessage` is called, which saves the current game state to `MODECACHE` (`$7E010C`) and then sets `MODE` to `0x0E` to display the text box. When the dialogue is finished, the previous state is restored from the cache.
- Transitioning between the overworld and underworld involves setting `MODE` to `0x08` (Overworld Load) or `0x06` (Underworld Load), respectively.
## 2. Defining the World: Location and Environment
The player's location and the properties of their environment are controlled by a handful of key WRAM variables.
- **`INDOORS` (`$7E001B`):** A simple but powerful flag (`0x01` for indoors, `0x00` for outdoors). This variable is checked by numerous systems to alter their behavior. For instance, the `ZSCustomOverworld` system reads this flag to determine whether to apply day/night palettes, and the audio engine uses it to select the appropriate music track.
- **`OWSCR` (`$7E008A`) and `ROOM` (`$7E00A0`):** These variables store the player's current location. `OWSCR` holds the Overworld screen ID, while `ROOM` holds the Underworld room ID.
The interaction between these variables is central to world traversal. When Link enters a cave on `OWSCR` 0x35, the following happens:
1. The game looks up the entrance data for that tile in `Overworld/entrances.asm`.
2. This data specifies the destination `ROOM` ID (e.g., 0x0104).
3. The `INDOORS` flag is set to `0x01`.
4. The main game `MODE` is set to `0x06` (Underworld Load).
5. The dungeon engine in `bank_01.asm` takes over. It reads the `ROOM` ID and uses it to look up the room's header in `ALTTP/rooms.asm`. This header contains pointers to all the data needed to draw the room, including its layout, objects, and sprites.
## 3. Room-Specific Behavior
Once a room is loaded, its specific behavior is governed by tags and flags.
- **`TAG1`/`TAG2` (`$7E00AE`/`$AF`):** These are "Room Effect" tags loaded from the room's header. They trigger special behaviors like kill rooms, shutter doors, or custom events defined in `Dungeons/custom_tag.asm`. For example, a kill room tag will cause the `Underworld_HandleRoomTags` routine to check if all sprites in the room (`$7E0E20+`) have been defeated.
- **`UWDEATH` (`$7FDF80`) and `OWDEATH` (`$7FEF80`):** These are large bitfields in SRAM that track the state of every overworld screen and underworld room. When a kill room is cleared or a key is taken from a chest, a bit is set in this array. This ensures that the state persists permanently in the save file, preventing enemies from respawning or chests from reappearing.
## 4. The Player and Entities
- **Link:** The player's state is managed by its own state machine in `bank_07.asm`, with the current state held in `LINKDO` (`$7E005D`). This is covered in detail in `Docs/Link.md`.
- **Sprites and Ancillae:** The WRAM regions from `$7E0D00` onwards are large arrays that hold the state of all active entities in the game (16 sprites, ~40 ancillae). These are defined as `structs` in `Core/structs.asm`. While there are dozens of variables for each sprite, the most important for general game logic are:
- `SprState` (`$7E0DD0,X`): The sprite's main state (e.g., `0x09` for active, `0x0B` for stunned).
- `SprType` (`$7E0E20,X`): The sprite's ID number.
- `SprX`/`SprY` (`$0D10,X`/`$0D00,X`): The sprite's coordinates.
The sprite engine in `bank_06.asm` iterates through these arrays each frame, executing the logic for each active sprite.
## 4.5. Custom WRAM Region (`$7E0730+`)
Oracle of Secrets adds a custom WRAM region starting at `$7E0730`, utilizing the MAP16OVERFLOW free RAM space. All custom variables documented here have been verified against `Core/ram.asm` and are actively used by the project's custom systems.
### Verified Custom WRAM Variables
| Address | Label | Description | Verified |
|------------|-------------------------|-----------------------------------------------------------|----------|
| `$7E0730` | `MenuScrollLevelV` | Vertical scroll position for the custom menu system | ✓ |
| `$7E0731` | `MenuScrollLevelH` | Horizontal scroll position for the custom menu system | ✓ |
| `$7E0732` | `MenuScrollHDirection` | Direction flag for horizontal menu scrolling (2 bytes) | ✓ |
| `$7E0734` | `MenuItemValueSpoof` | Temporary override for displayed menu item values (2 bytes)| ✓ |
| `$7E0736` | `ShortSpoof` | Shorter version of the spoof value (1 byte) | ✓ |
| `$7E0737` | `MusicNoteValue` | Current music note value for Ocarina system (2 bytes) | ✓ |
| `$7E0739` | `GoldstarOrHookshot` | Differentiates Hookshot (0) from Goldstar (1) mode | ✓ |
| `$7E073A` | `Neck_Index` | Index for multi-part sprite body tracking (e.g., bosses) | ✓ |
| `$7E073B` | `Neck1_OffsetX` | X-offset for first neck/body segment | ✓ |
| `$7E073C` | `Neck1_OffsetY` | Y-offset for first neck/body segment | ✓ |
| `$7E073D` | `Neck2_OffsetX` | X-offset for second neck/body segment | ✓ |
| `$7E073E` | `Neck2_OffsetY` | Y-offset for second neck/body segment | ✓ |
| `$7E073F` | `Neck3_OffsetX` | X-offset for third neck/body segment | ✓ |
| `$7E0740` | `Neck3_OffsetY` | Y-offset for third neck/body segment | ✓ |
| `$7E0741` | `Offspring1_Id` | Sprite ID of first child sprite (for boss mechanics) | ✓ |
| `$7E0742` | `Offspring2_Id` | Sprite ID of second child sprite (for boss mechanics) | ✓ |
| `$7E0743` | `Offspring3_Id` | Sprite ID of third child sprite (for boss mechanics) | ✓ |
| `$7E0744` | `Kydreeok_Id` | Sprite ID for Kydreeok boss entity | ✓ |
| `$7E0745` | `FishingOrPortalRod` | Differentiates Fishing Rod (1) from Portal Rod (2) | ✓ |
### Usage Notes
- **Menu System**: Variables `$7E0730-$7E0736` are exclusively used by the custom menu system (`Menu/menu.asm`) to manage smooth scrolling between the Items and Quest Status pages.
- **Item Differentiation**: `GoldstarOrHookshot` and `FishingOrPortalRod` are critical for shared inventory slots. These allow two distinct items to occupy a single menu slot, with the player able to switch between them using the L/R shoulder buttons.
- **Boss Mechanics**: The `Neck_*` and `Offspring_*` variables enable complex multi-part boss sprites (e.g., Kydreeok with multiple heads, Manhandla with independent parts). The parent sprite uses these to track and coordinate its child sprite components.
- **Memory Safety**: All variables in this region are placed within the MAP16OVERFLOW free RAM area, which is guaranteed to be unused by the vanilla game engine. This prevents conflicts with vanilla systems.
## 5. Long-Term Progression: SRAM and Custom Flags
SRAM (`$7EF000+`) stores the player's save file and is the key to managing long-term quest progression. Oracle of Secrets heavily expands the vanilla save format to support its new data-driven systems.
- **`OOSPROG` (`$7EF3D6`) and `OOSPROG2` (`$7EF3C6`):** These are the primary bitfields for tracking major and minor quest milestones. They are the heart of the game's custom progression.
- **Example Flow:**
1. The player talks to the `village_elder` NPC for the first time.
2. The NPC's code in `Sprites/NPCs/village_elder.asm` sets a specific bit in `OOSPROG` (e.g., `ORA.b #$10 : STA.l OOSPROG`).
3. Later, the world map code in `Overworld/world_map.asm` checks this bit (`LDA.l OOSPROG : AND.b #$10`). If it's set, a new icon is displayed on the map.
- **Other Custom SRAM:** The project adds many other custom variables to SRAM to track new systems, such as:
- **New Inventory:** `ZoraMask` (`$7EF347`), `RocsFeather` (`$7EF34D`), etc.
- **Side-Quests:** `MagicBeanProg` (`$7EF39B`) tracks the growth of a magic bean over time.
- **New Collectibles:** A block starting at `$7EF38B` tracks items like `Bananas` and `Seashells`.
This data-driven approach, centered on modifying and checking flags in SRAM, allows for complex, stateful quest design that persists across play sessions.

540
Docs/Core/StyleGuide.md Normal file
View File

@@ -0,0 +1,540 @@
# Oracle of Secrets: 65816 ASM Style Guide
**Version**: 1.0
**Purpose**: Reduce AI errors and ensure consistent code quality across the codebase.
---
## Quick Reference Card
### Label Naming
| Type | Pattern | Example |
|------|---------|---------|
| Sprite function | `Sprite_{Name}_{Type}` | `Sprite_Booki_Main`, `Sprite_Darknut_Draw` |
| Menu function | `Menu_{Purpose}` | `Menu_InitGraphics`, `Menu_DrawBackground` |
| Link/Player | `Link_{Action}` | `Link_ConsumeMagicBagItem`, `Link_HandleYItem` |
| Oracle namespace | `Oracle_{Function}` | `Oracle_CheckIfNight`, `Oracle_MainEntry` |
| Local labels | `.lowercase_with_underscores` | `.not_being_pushed`, `.nextTile` |
| Constants (macro) | `!UPPERCASE` | `!SPRID`, `!Health`, `!Damage` |
| Memory addresses | `CamelCase` | `SprAction`, `LinkState`, `OOSPROG` |
### Processor State Checklist
- [ ] Always use size suffixes (`.b`, `.w`, `.l`) for ambiguous operations
- [ ] Use `PHP`/`PLP` for functions called from unknown context
- [ ] `REP #$30` = 16-bit A and X/Y, `SEP #$30` = 8-bit
- [ ] `REP #$20` / `SEP #$20` = A only
- [ ] `REP #$10` / `SEP #$10` = X/Y only
### Call Convention Checklist
- [ ] `JSL`/`RTL` for cross-bank calls (3-byte return address)
- [ ] `JSR`/`RTS` for same-bank calls (2-byte return address)
- [ ] **NEVER MIX** - mismatch causes crashes
- [ ] External hooks use `JSL`, internal helpers use `JSR`
---
## 1. File Structure
### 1.1 File Header (Required for new files)
```asm
; =========================================================
; File: sprites/enemies/my_enemy.asm
; Purpose: [Brief description of what this file implements]
; Author: [Your name or handle]
; =========================================================
```
### 1.2 Section Organization
```asm
; =========================================================
; Sprite Properties
; =========================================================
!SPRID = $XX
!NbrTiles = 02
; ... (30 properties in standard order)
; =========================================================
; Entry Points
; =========================================================
Sprite_MyEnemy_Long:
{ ... }
Sprite_MyEnemy_Prep:
{ ... }
; =========================================================
; Main Logic
; =========================================================
Sprite_MyEnemy_Main:
{ ... }
; =========================================================
; Drawing
; =========================================================
Sprite_MyEnemy_Draw:
{ ... }
```
---
## 2. Naming Conventions
### 2.1 Labels
**PascalCase with underscores for hierarchy:**
```asm
; Good
Sprite_Booki_Main
Menu_InitGraphics
Link_ConsumeMagicBagItem
Oracle_CheckIfNight
; Bad
spriteBookiMain ; No underscores
sprite_booki_main ; All lowercase
SPRITE_BOOKI_MAIN ; All uppercase
```
### 2.2 Local Labels
**Lowercase with dot prefix:**
```asm
Sprite_Move:
{
LDA.w SprAction, X : BNE .already_moving
; Start movement
INC.w SprAction, X
.already_moving
RTS
}
```
### 2.3 Constants and Macros
```asm
; Macro parameters: !UPPERCASE
!SPRID = $D5
!Health = 08
!Damage = 02
; Debug flags: !UPPERCASE with LOG prefix
!DEBUG = 1
!LOG_MUSIC = 1
!LOG_SPRITES = 0
; Memory addresses: CamelCase (matching vanilla convention)
SprAction = $0D80
LinkState = $5D
OOSPROG = $7EF3D6
```
### 2.4 Sprite Property Standard Order
All sprites MUST define properties in this order:
```asm
!SPRID = Sprite_MyEnemyID
!NbrTiles = 02
!Harmless = 00
!Health = 08
!Damage = 02
!DeathAnimation = 00
!ImperviousArrow = 00
!ImperviousSword = 00
!Boss = 00
!Shadow = 01
!Palette = 00
!Hitbox = $00
!Persist = 00
!Statis = 00
!CollisionLayer = 00
!CanFall = 01
!DeflectProjectiles = 00
!WaterSprite = 00
!Blockable = 00
!Prize = 00
!Sound = $00
!Interaction = $00
!Subtype2 = 00
%Set_Sprite_Properties(Sprite_MyEnemy_Prep, Sprite_MyEnemy_Long)
```
---
## 3. Scoping and Indentation
### 3.1 Bracket Scoping
**ALL functions use `{ }` brackets** (NOT `subroutine`/`endsubroutine`):
```asm
Sprite_Booki_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw StalkPlayer
dw HideFromPlayer
dw ApproachPlayer
StalkPlayer:
{
%PlayAnimation(0,1,16)
JSR Sprite_Booki_Move
RTS
}
HideFromPlayer:
{
; Nested content indented 2 spaces
LDA.b #$00
STA.w SprAction, X
RTS
}
}
```
### 3.2 Indentation Rules
- **2-space indentation** for all nested content
- Local labels at same indentation as containing block
- Conditional code indented under branch:
```asm
JSL Sprite_CheckActive : BCC .inactive
; Active sprite code (indented)
JSR DoActiveStuff
.inactive
PLB
RTL
```
---
## 4. Processor State Management
### 4.1 Size Suffixes (REQUIRED)
Always use explicit size suffixes when processor state matters:
```asm
; Good - explicit sizes
LDA.w #$1234 ; 16-bit load
LDA.b #$12 ; 8-bit load
STA.l $7E0000 ; Long address
; Bad - ambiguous
LDA #$12 ; Is this 8-bit or 16-bit?
```
### 4.2 State Preservation
```asm
; Functions called from unknown context
SomePublicFunction:
{
PHP ; Save caller's state
SEP #$30 ; Set known state (8-bit A, X/Y)
; ... function body ...
PLP ; Restore caller's state
RTL
}
; Internal helpers can assume state from caller
.helper:
; Assume 8-bit A from caller
LDA.b #$00
RTS
```
### 4.3 Processor Mode Macros
Use these macros for clarity:
```asm
%m8() ; SEP #$20 - 8-bit accumulator
%m16() ; REP #$20 - 16-bit accumulator
%a8() ; SEP #$20 - 8-bit A (alias)
%a16() ; REP #$20 - 16-bit A (alias)
%index8() ; SEP #$10 - 8-bit X/Y
%index16() ; REP #$10 - 16-bit X/Y
```
---
## 5. Hook and Patch Patterns
### 5.1 Small Patches (pushpc/pullpc)
```asm
pushpc
org $1EF27D
ShopItem_Banana:
{
JSR $F4CE ; SpriteDraw_ShopItem
; Custom code here
RTS
}
assert pc() <= $1EF2AB ; Ensure we fit
pullpc
```
### 5.2 Hook Pattern
```asm
; In patches.asm or near related code
pushpc
org $02XXXX ; Vanilla address
JSL MyCustomHook ; 4-byte JSL
NOP ; Pad if needed
pullpc
; The hook implementation
MyCustomHook:
{
; Preserve any clobbered code
JSL OriginalRoutine
; Add custom logic
LDA.w CustomFlag : BEQ .skip
JSL DoCustomThing
.skip
RTL
}
```
### 5.3 Hook Documentation
```asm
; =========================================================
; Hook: $02XXXX - Link's Y-Button Handler
; Purpose: Add custom item handling for Magic Bag
; Vanilla Code Replaced: JSL $07F44C
; Side Effects: Clobbers A
; =========================================================
```
---
## 6. Comments and Documentation
### 6.1 Section Dividers
Use exactly 57 equal signs:
```asm
; =========================================================
; Section Name
; =========================================================
```
### 6.2 Bitfield Documentation
```asm
; Bitfield: hmwo oooo
; o - OAM slot count (bits 0-4)
; w - Wall-seeking behavior
; m - Master sword ceremony flag
; h - Harmless (no contact damage)
SprNbrOAM = $0E40
```
### 6.3 TODO Comments
```asm
; TODO: Add chase animation when player is detected
; TODO(scawful): Refactor this to use lookup table
```
### 6.4 Magic Number Documentation
```asm
LDA.b #$08 ; 8-frame animation delay
CMP.w #$0100 ; Check if past screen boundary (256px)
```
---
## 7. Memory and Data Structures
### 7.1 Struct Definitions
```asm
struct TimeState $7EE000
{
.Hours: skip 1
.Minutes: skip 1
.Speed: skip 1
.Padding: skip 13
.BlueVal: skip 2
.GreenVal: skip 2
.RedVal: skip 2
}
endstruct
```
### 7.2 Inline Data Tables
```asm
Sprite_Draw:
{
; ... draw code ...
RTS
; Data tables use dot notation
.start_index
db $00, $04, $08, $0C
.nbr_of_tiles
db 3, 3, 3, 3
.x_offsets
dw 4, -4, 4, -4
}
```
---
## 8. Error Prevention Checklist
### Before Submitting Code
- [ ] All labels use correct PascalCase_With_Underscores
- [ ] All local labels use .dot_notation
- [ ] Size suffixes on all ambiguous loads/stores
- [ ] JSL/RTL and JSR/RTS pairs matched correctly
- [ ] Hooks have `assert` statements to prevent overflow
- [ ] Magic numbers have inline comments explaining purpose
- [ ] Sprite properties in standard order
- [ ] Section dividers between major code blocks
### Common Crash Causes
1. **JSL/JSR mismatch** - Using RTL with JSR or RTS with JSL
2. **Bank crossing** - Forgetting to set DBR with PHK:PLB
3. **Processor state** - Assuming 8-bit when 16-bit or vice versa
4. **Hook overflow** - Patch exceeds available space
5. **Missing pullpc** - Stack imbalance from pushpc
---
## 9. Oracle of Secrets Specific Patterns
### 9.1 Sprite Entry Point Pattern
```asm
Sprite_MyEnemy_Long:
{
PHB : PHK : PLB ; Set data bank to current bank
JSR Sprite_MyEnemy_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .inactive
JSR Sprite_MyEnemy_Main
.inactive
PLB
RTL
}
```
### 9.2 State Machine Pattern
```asm
Sprite_MyEnemy_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw State_Idle
dw State_Chase
dw State_Attack
dw State_Retreat
State_Idle:
{
%PlayAnimation(0,1,16)
; Check for player proximity
JSL Sprite_CheckDamageToLink
BCC .stay_idle
INC.w SprAction, X ; Transition to Chase
.stay_idle
RTS
}
; ... more states ...
}
```
### 9.3 SRAM Flag Checking
```asm
; Check Oracle progression flag
LDA.l OOSPROG : AND.b #$01 : BEQ .not_complete
; Player has completed this milestone
.not_complete
; Bitfield reference for OOSPROG ($7EF3D6):
; .fmp h.i.
; i = Intro complete
; h = Hall of Secrets visited
; p = Pendant progress
; m = Master Sword acquired
; f = Fortress of Secrets
```
---
## 10. AI Agent Instructions
### 10.1 Before Writing Code
1. **Read existing patterns** - Search for similar implementations
2. **Check memory map** - Verify address usage won't conflict
3. **Identify hook points** - Use Hyrule Historian to find vanilla code
4. **Verify bank space** - Check MemoryMap.md for free space
### 10.2 When Modifying Code
1. **Always read the file first** - Never assume structure
2. **Match existing style** - Follow patterns in the same file
3. **Use explicit sizes** - Never rely on assumed processor state
4. **Add assert statements** - Prevent silent overflow errors
### 10.3 After Writing Code
1. **Run build** - Use `mcp__book-of-mudora__run_build()`
2. **Run lint** - Use `mcp__book-of-mudora__lint_asm()`
3. **Verify in emulator** - Test before marking complete
---
## Appendix: Common Macro Reference
### Animation and Drawing
- `%PlayAnimation(start, end, speed)` - Animate sprite frames
- `%DrawSprite(...)` - Draw sprite OAM
- `%SetFrame(n)` - Set current animation frame
### Sprite Control
- `%GotoAction(n)` - Change sprite state
- `%SetTimer*(n)` - Set various timers
- `%SetSpriteSpeed*(n)` - Set movement speed
- `%SetHarmless()` / `%SetImpervious()` - Damage flags
### Player
- `%PreventPlayerMovement()` / `%AllowPlayerMovement()`
- `%GetPlayerRupees()` - Return rupee count
- `%ShowUnconditionalMessage(id)` - Display dialogue
- `%ShowSolicitedMessage(id)` - Display on interaction
### Audio
- `%PlaySFX1(id)` / `%PlaySFX2(id)` - Sound effects
- `%PlayMusic(id)` - Change music
### Debugging
- `%print_debug(msg)` - Build-time debug output
- `%log_section(name, flag)` - Conditional section logging

View File

@@ -0,0 +1,472 @@
# Oracle of Secrets: System Architecture
**Purpose**: Document how major systems interact to help AI agents understand the codebase structure.
---
## 1. High-Level Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ Oracle_main.asm │
│ (Master include file - controls ROM layout and build order) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────┼─────────────────────────┐
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Core/ │ │ Sprites/ │ │ Overworld/ │
│ - link.asm │ │ - all_sprites│ │ - ZSCustomOW │
│ - sram.asm │ │ - Bosses/ │ │ - time_system│
│ - symbols.asm│ │ - NPCs/ │ │ - overlays │
│ - patches.asm│ │ - Enemies/ │ │ - lost_woods │
│ - message.asm│ │ - Objects/ │ └───────────────┘
└───────────────┘ └───────────────┘
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Items/ │ │ Menu/ │
│ - all_items │ │ - menu.asm │
│ - ocarina │ │ - menu_select│
│ - magic_bag │ │ - menu_journal│
└───────────────┘ └───────────────┘
```
---
## 2. Namespace Organization
### 2.1 Oracle Namespace
All custom Oracle of Secrets code lives inside the `Oracle` namespace:
```asm
namespace Oracle
{
; Core systems
incsrc "Core/link.asm"
incsrc "Core/sram.asm"
incsrc "Core/symbols.asm"
; Content
incsrc "Music/all_music.asm"
incsrc "Sprites/all_sprites.asm"
incsrc "Items/all_items.asm"
; Patches go last
incsrc "Core/patches.asm"
}
namespace off
; ZScream code is OUTSIDE the namespace
incsrc "Overworld/ZSCustomOverworld.asm"
```
### 2.2 Why This Matters
- Labels inside `namespace Oracle` become `Oracle.LabelName`
- ZScream uses its own conventions and must be outside
- Patches at end to ensure all labels are defined
---
## 3. Memory Map Overview
### 3.1 Bank Organization
| Bank Range | Purpose | Notes |
|------------|---------|-------|
| $00-$1F | Vanilla ALTTP + small patches | Limited free space |
| $20-$29 | ZScream Overworld Data | ~1.5MB reserved |
| $30 | Sprite prep/initialization | Oracle sprites start here |
| $31 | Main sprite logic | Enemy/NPC behavior |
| $32 | Boss logic | Complex sprite code |
| $33-$3F | Free space | Available for expansion |
### 3.2 RAM Regions
| Region | Purpose |
|--------|---------|
| $7E0000-$7E1FFF | Scratch RAM (volatile) |
| $7E2000-$7EFFFF | Game state RAM |
| $7EE000-$7EE0FF | TimeState struct |
| $7EF000-$7EFFFF | SRAM (saved data) |
| $7EF3C5-$7EF3D6 | Oracle progression flags |
| $7EF410 | Dreams bitfield |
### 3.3 Key SRAM Variables
```asm
; GameState ($7EF3C5) - Overall progression
; 0x00 = Very start
; 0x01 = Uncle reached
; 0x02 = Zelda rescued / Farore intro
; 0x03 = Agahnim defeated
; OOSPROG ($7EF3D6) - Oracle progression bitfield
; .fmp h.i.
; i = Intro complete, Maku Tree met
; h = Hall of Secrets visited
; p = Pendant quest progress
; m = Master Sword acquired
; f = Fortress of Secrets
; OOSPROG2 ($7EF3C6) - Secondary progression
; .fbh .zsu
; u = Uncle visited
; s = Priest visited in sanctuary
; z = Zelda brought to sanctuary
; h = Uncle left house
; b = Book of Mudora obtained
; f = Fortune teller flag
; Dreams ($7EF410) - Dream sequence tracking
; .dts fwpb
; (Individual dream completion flags)
```
---
## 4. System Interactions
### 4.1 ZScream Custom Overworld (ZSOW) Integration
ZSOW manages:
- Overworld map transitions
- Palette events
- Overlay data
- Custom collision
**Hook Points**:
```
OverworldHandleTransitions ($028000 area)
└── Oracle_CheckIfNight (time-based sprite loading)
└── LostWoods_PuzzleHandler (navigation puzzle)
└── SongOfStorms overlay effects
```
**Known Conflicts**:
- Lost Woods puzzle directly modifies transition logic
- Day/Night sprites must check `Oracle_CheckIfNight`
- Song of Storms overlays need coordination with ZSOW
### 4.2 Time System
**File**: `Overworld/time_system.asm`
**Structure**:
```asm
struct TimeState $7EE000
{
.Hours, .Minutes, .Speed
.BlueVal, .GreenVal, .RedVal
.TempColor, .SubColor
}
```
**Flow**:
```
RunClock (called each frame)
├── TimeSystem_CheckCanRun
│ └── Check game mode, indoors status
├── TimeSystem_IncrementTime
│ └── Update Hours/Minutes based on Speed
└── TimeSystem_UpdatePalettes
└── Apply color tinting based on time
```
**Integration Points**:
- `Oracle_CheckIfNight` - Called by ZSOW for sprite loading
- Palette system - Affects overworld colors
- NPC behavior - Some NPCs react to time
### 4.3 Sprite System
**Entry Points** (standard pattern):
```
Sprite_*_Long (JSL entry from sprite table)
├── PHB : PHK : PLB (set data bank)
├── Sprite_*_Draw (JSR - render)
├── Sprite_CheckActive (JSL - is active?)
│ └── Sprite_*_Main (JSR - if active)
├── PLB
└── RTL
```
**State Machine**:
```
SprAction (per-sprite state variable)
└── JumpTableLocal dispatches to state handlers
├── State_Idle
├── State_Chase
├── State_Attack
└── State_Retreat
```
**Key Variables** (indexed by X):
| Address | Name | Purpose |
|---------|------|---------|
| $0D00 | SprY | Y position (low byte) |
| $0D10 | SprX | X position (low byte) |
| $0D80 | SprAction | Current state |
| $0DA0 | SprHealth | Hit points remaining |
| $0E40 | SprNbrOAM | OAM slot count |
### 4.4 Menu System
**File**: `Menu/menu.asm`
**State Machine**:
```
MenuMode ($0200) - Current menu state
├── $00 = Not in menu
├── $01 = Opening animation
├── $02 = Item select
├── $0C = Magic Bag submenu
├── $0D = Ring Box submenu
└── $0E = Song select submenu
```
**Flow**:
```
Menu_Entry (from pause input)
├── Menu_InitGraphics
├── Menu_Upload* (VRAM transfers)
└── Menu_MainLoop
├── Handle input
├── Update selection
└── Menu_Draw*
```
### 4.5 Dialogue System
**Files**: `Core/message.asm`, `Core/messages.org`
**Message Format** (in messages.org):
```
** 20 - Maku Tree Part1
[W:02][S:03]Ah, [L]!
[2]Thank the Goddesses you are
[3]alright. I feared the worst.
[V]A dark shadow has befallen us.[K]
```
**Control Codes**:
| Code | Meaning |
|------|---------|
| `[2]`, `[3]` | Line 2, Line 3 |
| `[K]` | Wait for button press |
| `[V]` | Continue on same line |
| `[W:XX]` | Wait time |
| `[S:XX]` | Text speed |
| `[SFX:XX]` | Play sound effect |
| `[CH2I]` | 2-choice prompt |
| `[CH3]` | 3-choice prompt |
| `[L]` | Player name |
**Display Macros**:
```asm
%ShowUnconditionalMessage($20) ; Force display
%ShowSolicitedMessage($20) ; On interaction only
```
---
## 5. Hook Architecture
### 5.1 Hook Types
**Type 1: Inline Patch** (replace vanilla code)
```asm
pushpc
org $02XXXX ; Vanilla address
JSL MyHook ; Replace original instruction
NOP : NOP ; Pad to match original size
pullpc
```
**Type 2: Table Override** (jump table entry)
```asm
pushpc
org $07F000+($ID*2) ; Jump table for sprite $ID
dw Sprite_Custom_Prep
pullpc
```
**Type 3: Extended Logic** (call original + extend)
```asm
MyHook:
{
JSL OriginalRoutine ; Preserve original behavior
; Add custom logic
LDA.w CustomFlag
BEQ .skip
JSL CustomHandler
.skip
RTL
}
```
### 5.2 Hook Documentation Pattern
Every hook should document:
```asm
; =========================================================
; Hook: $XXBANK:ADDR
; Purpose: [What this hook adds/changes]
; Vanilla Code: [What original code did]
; Clobbered: [Registers modified]
; Dependencies: [Other systems affected]
; =========================================================
```
---
## 6. Build System
### 6.1 Build Order (Critical)
```
1. Core/symbols.asm - Memory declarations
2. Core/sram.asm - SRAM layout
3. Core/link.asm - Player modifications
4. Music/all_music.asm - SPC700 data
5. Sprites/all_sprites.asm - All sprite code
6. Items/all_items.asm - Item handlers
7. Menu/menu.asm - Menu system
8. Dungeons/ - Dungeon-specific code
9. Core/patches.asm - Vanilla patches (LAST)
10. Overworld/ZSCustomOverworld.asm - ZSOW (OUTSIDE namespace)
```
### 6.2 Build Commands
```bash
# Fast build
./run.sh
# MCP tools
mcp__book-of-mudora__run_build() # Build ROM
mcp__book-of-mudora__lint_asm() # Check style
mcp__book-of-mudora__analyze_patches() # Review patches
```
---
## 7. Debugging Workflow
### 7.1 Common Debug Points
| Symptom | Check First |
|---------|-------------|
| Crash on room enter | Sprite prep routine, invalid JSL target |
| Wrong graphics | SprGfx assignment, DMA timing |
| Frozen game | Infinite loop in Main, missing RTS/RTL |
| Black screen | Palette loading, HDMA setup |
| Corrupt saves | SRAM address conflict, missing bank setup |
### 7.2 Debug Tools
**Yaze MCP** (in-development):
```python
mcp__yaze_mcp__read_memory("7E0010", 2) # Read RAM
mcp__yaze_mcp__add_breakpoint("02XXXX") # Set breakpoint
mcp__yaze_mcp__get_game_state() # Dump current state
```
**Hyrule Historian**:
```python
mcp__hyrule-historian__lookup_address("02XXXX")
mcp__hyrule-historian__search_oracle_code("Sprite_Booki")
mcp__hyrule-historian__get_ram_info("SprAction")
```
### 7.3 Logging
```asm
; Build-time logging
%log_section("Sprites", !LOG_SPRITES)
; Enable in build:
!LOG_SPRITES = 1 ; Set in config
; Output appears in build log
```
---
## 8. Common Integration Patterns
### 8.1 Adding a New Sprite
1. Create file: `Sprites/Enemies/my_sprite.asm`
2. Define properties using standard order
3. Implement `_Long`, `_Prep`, `_Main`, `_Draw`
4. Add include to `Sprites/all_sprites.asm`
5. Add to sprite table with `org` directive
6. Build and test
### 8.2 Adding a Quest Flag
1. Find free bit in OOSPROG/OOSPROG2
2. Document in `Core/sram.asm`
3. Update this file's SRAM section
4. Use in code:
```asm
LDA.l OOSPROG : ORA.b #$XX : STA.l OOSPROG
```
### 8.3 Adding a New Hook
1. Find vanilla address with Hyrule Historian
2. Document what original code does
3. Create hook with `pushpc`/`pullpc`
4. Add `assert` to verify space
5. Test vanilla behavior still works
---
## 9. System Dependency Graph
```
┌─────────────┐
│ TimeSystem │
└──────┬──────┘
│ Oracle_CheckIfNight
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Lost Woods │────▶│ ZSOW │◀────│ Overlays │
│ Puzzle │ │ Transitions │ │ System │
└─────────────┘ └──────┬──────┘ └─────────────┘
┌────────────┴────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ Sprites │ │ Events │
│ (Loading) │ │ (Triggers) │
└──────┬──────┘ └──────┬──────┘
│ │
└───────────┬─────────────┘
┌─────────────┐
│ Player │
│ (Link) │
└─────────────┘
```
---
## 10. AI Agent Checklist
When working on Oracle of Secrets:
1. [ ] Check `oracle.org` for related tasks
2. [ ] Read existing code in affected files
3. [ ] Verify memory map for conflicts
4. [ ] Use Hyrule Historian for vanilla lookups
5. [ ] Follow StyleGuide.md conventions
6. [ ] Run build after changes
7. [ ] Document any new hooks
8. [ ] Update this file if adding new systems

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,610 @@
# Castle Ambush & Guard Capture System - Implementation Plan
**Status:** 🚧 Planning Phase
**Created:** October 3, 2025
**Target:** Future Update
**Related Files:** `Core/capture.asm`, `Sprites/Enemies/custom_guard.asm`, `Sprites/overlord_ref.asm`
---
## Overview
The **Castle Ambush System** will create a dynamic encounter where Link is detected by guards in the castle, captured, and warped to a dungeon. This combines:
1. **Probe Detection System** - Guards detect Link entering restricted areas
2. **Guard Capture Mechanics** - Guards surround and capture Link
3. **Warp System** - Link is transported to a dungeon entrance
4. **Overlord Management** - Multi-screen guard coordination
---
## Current State Analysis
### Existing Components
#### ✅ Core/capture.asm
**Status:** Implemented but untested
```asm
Oracle_CaptureAndWarp:
{
STA.w $010E ; Set the target entrance ID
LDA.b #$05 ; Game Mode 05: (hole/whirlpool transition)
STA.b $10 ; Set the game mode
STZ.b $2F ; Clear Link's action state
STZ.b $5D ; Clear Link's state
LDA.b #$02 : STA.b $71 ; Set transition flag
RTL
}
```
**Purpose:** Warps Link to a specific dungeon entrance (like WallMaster)
**Issues to Address:**
- [ ] Test entrance ID values (need to determine correct dungeon entrance)
- [ ] Verify game mode $05 works for this use case
- [ ] Add screen fade/transition effect
- [ ] Play capture sound effect
- [ ] Store pre-capture location for potential escape sequence
#### 🚧 Sprites/Enemies/custom_guard.asm
**Status:** Prototype with duplicate code
**Contains:**
1. `Oracle_CaptureAndWarp` (DUPLICATE - already in Core/capture.asm)
2. `Hooked_Guard_Main` - Modified guard behavior
**Issues:**
- [ ] Remove duplicate `Oracle_CaptureAndWarp` function
- [ ] Complete `Hooked_Guard_Main` implementation
- [ ] Test guard capture trigger conditions
- [ ] Integrate with vanilla guard sprites (ID $41, $42, $43)
#### 📚 Sprites/overlord_ref.asm
**Status:** Reference material (now in experimental/)
**Purpose:** Documents overlord path patterns for crumbling tiles
**Relevance:** Can be adapted for guard patrol paths
---
## System Architecture
### Phase 1: Detection (Probe System)
```
┌─────────────────────────────────────────────────┐
│ Link enters castle restricted area │
│ SRAM flag: $7EF??? = Castle infiltration active │
└──────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Guard spawns probe sprites every 32 frames │
│ Probe checks: │
│ - Link within 16px radius │
│ - Same floor layer ($0F20) │
│ - Not invisible/bunny │
└──────────────────┬──────────────────────────────┘
┌────────┴────────┐
│ Probe Hit? │
└────────┬────────┘
┌────────────┼────────────┐
│ YES │ NO
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Trigger │ │ Continue │
│ Alert State │ │ Patrol │
└──────┬───────┘ └──────────────┘
┌──────────────────────────────────────┐
│ Play alert sound ($1D) │
│ Set SprTimerD = $B0 (176 frames) │
│ Spawn reinforcement guards │
└──────────────────────────────────────┘
```
### Phase 2: Pursuit & Capture
```
┌─────────────────────────────────────────────────┐
│ Alert state active (SprTimerD > 0) │
└──────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Guards converge on Link's position │
│ - Use Guard_ChaseLinkOnOneAxis │
│ - Spawn additional guards from off-screen │
│ - Maximum 4 guards active │
└──────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Check capture conditions (every frame) │
│ - Link is surrounded (guards on 3+ sides) │
│ - Link is not moving (speed = 0) │
│ - Link has taken damage from guard │
└──────────────────┬──────────────────────────────┘
┌────────┴────────┐
│ Captured? │
└────────┬────────┘
┌────────────┼────────────┐
│ YES │ NO
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Initiate │ │ Continue │
│ Capture │ │ Chase │
└──────┬───────┘ └──────────────┘
┌──────────────────────────────────────┐
│ Phase 3: Warp Sequence │
└──────────────────────────────────────┘
```
### Phase 3: Warp Sequence
```
┌─────────────────────────────────────────────────┐
│ Freeze Link (disable input) │
│ Play capture animation │
│ - Link's sprite changes to "captured" pose │
│ - Guards move to surround positions │
│ - Screen shake effect (3 frames) │
└──────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Fade out screen ($0012 = $01) │
│ Wait 32 frames │
└──────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Call Oracle_CaptureAndWarp │
│ - A register = Dungeon entrance ID │
│ - Sets game mode to $05 (transition) │
│ - Clears Link state │
└──────────────────┬──────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ Link spawns in dungeon cell │
│ - Set SRAM flag: $7EF??? = Captured │
│ - Play jingle ($06) │
│ - Trigger escape sequence │
└──────────────────────────────────────────────────┘
```
---
## File Consolidation Plan
### Step 1: Organize Core Utilities
**Keep in `Core/capture.asm`:**
```asm
; Core warp functionality
Oracle_CaptureAndWarp:
{
; (existing implementation)
}
; NEW: Enhanced version with effects
Oracle_CaptureAndWarp_Enhanced:
{
PHP
; Store entrance ID
STA.w $010E
; Store capture flag in SRAM
LDA.b #$01
STA.l $7EF3D8 ; Custom flag: Has been captured
; Play capture sound
LDA.b #$1D ; Alert/Capture sound
STA.w $012E
; Fade out screen
LDA.b #$01
STA.w $0012 ; Request fade
; Set up timer for transition
LDA.b #$40 ; Wait 64 frames
STA.b $00
.wait_fade
LDA.b $00 : BNE .wait_fade
; Execute warp
LDA.w $010E ; Get entrance ID back
STA.w $010E
LDA.b #$05 ; Game Mode 05
STA.b $10
STZ.b $2F
STZ.b $5D
LDA.b #$02
STA.b $71
PLP
RTL
}
```
### Step 2: Create Unified Guard Sprite
**New file: `Sprites/Enemies/castle_guard.asm`**
This will replace `Sprites/Enemies/custom_guard.asm` with a complete implementation:
```asm
; =========================================================
; Castle Guard - Ambush & Capture Variant
; =========================================================
!SPRID = Sprite_CastleGuard ; Use new sprite ID or override vanilla
!NbrTiles = 02
!Health = 08
!Damage = 04
; ... other properties ...
%Set_Sprite_Properties(Sprite_CastleGuard_Prep, Sprite_CastleGuard_Long)
; States
!STATE_PATROL = 0
!STATE_ALERT = 1
!STATE_CHASE = 2
!STATE_CAPTURE = 3
Sprite_CastleGuard_Long:
{
PHB : PHK : PLB
JSR Sprite_CastleGuard_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .inactive
JSR Sprite_CastleGuard_Main
.inactive
PLB
RTL
}
Sprite_CastleGuard_Prep:
{
PHB : PHK : PLB
; Check if castle ambush is active
LDA.l $7EF3D7 : BEQ .no_ambush ; Custom flag: Castle ambush active
; Enable enhanced AI
LDA.b #$01 : STA.w $0E80, X ; Custom flag for this guard
.no_ambush
; Standard health based on sword level
LDA.l $7EF359 : TAY
LDA.w .health, Y : STA.w SprHealth, X
; Enable parrying
LDA.b #$80 : STA.w SprDefl, X
PLB
RTL
.health
db $04, $06, $08, $0A
}
Sprite_CastleGuard_Main:
{
; State machine
LDA.w SprAction, X
JSL JumpTableLocal
dw CastleGuard_Patrol
dw CastleGuard_Alert
dw CastleGuard_Chase
dw CastleGuard_Capture
}
CastleGuard_Patrol:
{
; Check if castle ambush should activate
LDA.l $7EF3D7 : BEQ .normal_patrol
; Check distance to Link
JSL GetDistance8bit_Long : CMP.b #$80 : BCS .no_probe
; Spawn probe for detection
LDA.w SprTimerA, X : BNE .no_probe
LDA.b #$20 : STA.w SprTimerA, X ; Spawn every 32 frames
JSL Sprite_SpawnProbeAlways_long
.no_probe
; Check if probe triggered alert
LDA.w SprTimerD, X : BEQ .normal_patrol
; Probe detected Link!
LDA.b #!STATE_ALERT : STA.w SprAction, X
; Play alert sound
LDA.b #$1D : STA.w $012E
; Spawn reinforcements
JSR CastleGuard_SpawnReinforcements
RTS
.normal_patrol
; Standard patrol behavior
JSR Guard_StandardPatrol
RTS
}
CastleGuard_Alert:
{
; Transition to chase after alert plays
LDA.w SprTimerD, X : CMP.b #$A0 : BCS .stay_alert
LDA.b #!STATE_CHASE : STA.w SprAction, X
.stay_alert
; Face Link
JSL Sprite_DirectionToFacePlayer : TYA : STA.w SprMiscC, X
; Draw with alert animation
LDA.b #$08 : STA.w SprGfx, X
RTS
}
CastleGuard_Chase:
{
; Move toward Link
LDA.b #$0C : JSL Sprite_ApplySpeedTowardsPlayer
JSL Guard_ChaseLinkOnOneAxis
JSL Sprite_Move
JSL Sprite_BounceFromTileCollision
; Check if Link is surrounded
JSR CastleGuard_CheckSurrounded : BCC .not_surrounded
; Capture Link!
LDA.b #!STATE_CAPTURE : STA.w SprAction, X
LDA.b #$60 : STA.w SprTimerC, X ; Capture animation duration
; Freeze Link
LDA.b #$17 : STA.b $5D ; Link state: captured
STZ.b $67 ; Stop Link's movement
RTS
.not_surrounded
; Continue chase
JSL Guard_ParrySwordAttacks
JSL Sprite_CheckDamageFromPlayer
RTS
}
CastleGuard_Capture:
{
; Animate capture sequence
LDA.w SprTimerC, X : BNE .animating
; Capture complete - warp Link
LDA.b #$42 ; Entrance ID for dungeon cell
JSL Oracle_CaptureAndWarp_Enhanced
RTS
.animating
; Guards surround Link
; ... capture animation logic ...
RTS
}
CastleGuard_CheckSurrounded:
{
; Count guards within range in each direction
; Return carry set if Link is surrounded
; (3+ guards within 32px, covering different quadrants)
; ... implementation ...
CLC ; Not surrounded
RTS
}
CastleGuard_SpawnReinforcements:
{
; Spawn additional castle guards off-screen
LDY.b #$00
LDX.b #$0F
.spawn_loop
LDA.w $0DD0, X : BEQ .found_slot
DEX : BPL .spawn_loop
RTS ; No free slots
.found_slot
; Spawn guard sprite
LDA.b #Sprite_CastleGuard : STA.w $0E20, X
LDA.b #$09 : STA.w $0DD0, X ; Active state
; Position off-screen based on Link's position
; ... positioning logic ...
; Set to chase state immediately
LDA.b #!STATE_CHASE : STA.w SprAction, X
INY
CPY.b #$03 : BCC .spawn_loop ; Spawn up to 3 reinforcements
RTS
}
; ... drawing routine ...
```
### Step 3: Integrate with Existing Systems
**Modify `Sprites/all_sprites.asm`:**
```asm
org $318000
%log_start("castle_guard", !LOG_SPRITES)
incsrc "Sprites/Enemies/castle_guard.asm"
%log_end("castle_guard", !LOG_SPRITES)
```
**Add SRAM Flags to `Core/sram.asm`:**
```asm
; Castle Ambush System
$7EF3D7 = CastleAmbushActive ; 01 = ambush scenario active
$7EF3D8 = HasBeenCaptured ; 01 = player has been captured before
$7EF3D9 = CaptureCount ; Number of times captured
```
**Add Constants to `Core/symbols.asm`:**
```asm
; Castle Ambush
CastleAmbushActive = $7EF3D7
HasBeenCaptured = $7EF3D8
CaptureCount = $7EF3D9
```
---
## Testing Plan
### Test Case 1: Detection
- [ ] Enter castle area with ambush flag set
- [ ] Walk near guard
- [ ] Verify probe spawns every 32 frames
- [ ] Walk into probe's path
- [ ] Verify alert sound plays
- [ ] Verify guard enters alert state
### Test Case 2: Chase
- [ ] Continue from Test Case 1
- [ ] Verify guard chases Link
- [ ] Verify reinforcements spawn
- [ ] Verify multiple guards coordinate
- [ ] Verify guards use parrying
### Test Case 3: Capture
- [ ] Let guards surround Link
- [ ] Verify capture check works
- [ ] Verify Link is frozen
- [ ] Verify capture animation plays
- [ ] Verify screen fades out
### Test Case 4: Warp
- [ ] Continue from Test Case 3
- [ ] Verify Link warps to dungeon
- [ ] Verify SRAM flag is set
- [ ] Verify Link spawns in correct room
- [ ] Verify capture count increments
### Test Case 5: Escape
- [ ] Escape from dungeon cell
- [ ] Return to castle
- [ ] Verify guards remember previous capture
- [ ] Verify harder difficulty on subsequent captures
---
## Implementation Phases
### Phase A: Core Functionality (Week 1)
- [ ] Clean up `Core/capture.asm`
- [ ] Add `Oracle_CaptureAndWarp_Enhanced`
- [ ] Test basic warp functionality
- [ ] Determine correct entrance ID for dungeon cell
### Phase B: Guard AI (Week 2)
- [ ] Create `Sprites/Enemies/castle_guard.asm`
- [ ] Implement probe detection
- [ ] Implement state machine
- [ ] Test patrol → alert → chase transitions
### Phase C: Capture Mechanics (Week 3)
- [ ] Implement surround check
- [ ] Implement capture animation
- [ ] Test capture trigger conditions
- [ ] Add sound effects
### Phase D: Integration (Week 4)
- [ ] Add SRAM flags
- [ ] Integrate with quest system
- [ ] Create dungeon escape sequence
- [ ] Test full cycle
### Phase E: Polish (Week 5)
- [ ] Add dialogue/cutscenes
- [ ] Add visual effects
- [ ] Balance difficulty
- [ ] Add achievements/tracking
---
## Entrance IDs Reference
Need to determine correct entrance for dungeon cell:
```asm
; Common dungeon entrances
$00 = Hyrule Castle (main entrance)
$04 = Hyrule Castle (throne room)
$0E = Hyrule Castle (dark passage)
$20 = Eastern Palace
$42 = Dark Palace
$?? = Custom dungeon cell (TBD)
```
**Action Required:** Find or create appropriate dungeon cell entrance
---
## Files to Create/Modify
### Create:
- [ ] `Sprites/Enemies/castle_guard.asm` - Main guard implementation
- [ ] `Docs/Features/CastleAmbush.md` - System documentation
- [ ] `Docs/Sprites/Enemies/CastleGuard.md` - Sprite documentation
### Modify:
- [ ] `Core/capture.asm` - Add enhanced version
- [ ] `Core/sram.asm` - Add SRAM flags
- [ ] `Core/symbols.asm` - Add constants
- [ ] `Sprites/all_sprites.asm` - Include castle_guard.asm
### Delete/Consolidate:
- [ ] `Sprites/Enemies/custom_guard.asm` - Consolidate into castle_guard.asm
- [ ] Remove duplicate `Oracle_CaptureAndWarp` from custom_guard.asm
---
## Questions to Resolve
1. **Entrance ID:** Which dungeon entrance should be used for the cell?
2. **Quest Integration:** When should castle ambush activate?
- After certain quest milestone?
- When Link enters specific castle area?
- Triggered by dialogue/cutscene?
3. **Difficulty Scaling:** Should capture difficulty increase after first capture?
4. **Escape Sequence:** How should the escape play out?
- Find key item?
- Stealth section?
- Fight way out?
5. **Sprite Slot:** New sprite ID or override vanilla guard ($41/$42/$43)?
---
## See Also
- `Docs/Sprites/ProbeSprites.md` - Probe detection system
- `Docs/Sprites/Enemies/Darknut.md` - Similar guard-type enemy
- `Docs/Guides/SpriteCreationGuide.md` - Sprite creation reference
- `Core/capture.asm` - Core warp functionality
- `Sprites/experimental/probe.asm` - Probe system reference

View File

@@ -0,0 +1,84 @@
# Custom Items System
This document details the functionality of new and modified items in Oracle of Secrets, based on analysis of the `Items/` directory.
## 1. Overview
The item roster has been significantly expanded with new mechanics, and many vanilla items have been reworked to provide new functionality. The system is managed through a combination of hooks into the main player state machine and custom routines for each item.
## 2. Vanilla Item Modifications
Several items from the original game have been altered.
### Hookshot / Goldstar
- **Files:** `goldstar.asm`
- **Functionality:** The Hookshot can be upgraded to the **Goldstar**, a powerful morning star weapon. The two items share an inventory slot.
- **Switching:** When the Goldstar is obtained, the player can switch between the Hookshot and Goldstar by pressing the L/R shoulder buttons while it is selected in the menu. The current mode is tracked by the `GoldstarOrHookshot` WRAM variable.
- **Goldstar Mechanics:** When active, the item functions as a short-range, powerful melee weapon with its own collision and damage properties, distinct from the Hookshot's grappling mechanic.
### Ice Rod
- **File:** `ice_rod.asm`
- **Functionality:** The Ice Rod's projectile now freezes water tiles it hits, creating temporary 16x16 ice platforms. This allows the player to cross water gaps.
- **Implementation:** The `LinkItem_IceRod` routine hooks into the ancilla tile collision logic. When the projectile hits a water tile, it dynamically modifies the tilemap properties in RAM and DMAs new ice graphics to VRAM.
### Bug-Catching Net -> Roc's Feather
- **File:** `jump_feather.asm`
- **Functionality:** The vanilla Bug-Catching Net has been completely replaced by **Roc's Feather**. This item allows Link to perform a short hop.
- **Implementation:** `LinkItem_JumpFeather` initiates the jump by setting Link's state to a recoil/ledge hop state and applying a burst of vertical velocity.
### Bottles
- **File:** `bottle_net.asm`
- **Functionality:** The Bug-Catching Net is no longer required to catch bees, fairies, etc. The Bottle item now has a dual function:
1. If the bottle is **empty**, using it initiates the `LinkItem_CatchBottle` routine, which performs a net-catching swing.
2. If the bottle is **full**, using it calls `LinkItem_Bottles`, which consumes the contents (e.g., drinks a potion, releases a fairy).
### Book of Mudora -> Book of Secrets
- **File:** `book_of_secrets.asm`
- **Functionality:** The Book of Mudora is now the **Book of Secrets**. While its vanilla function of translating Hylian text remains, it has a new secret-revealing capability.
- **Implementation:** The `Dungeon_RevealSecrets` routine checks if the L button is held while inside a building. If it is, it disables the `BG2` layer, which can be used to hide secret passages or objects behind walls that are part of that background layer.
## 3. New Active Items
### Ocarina
- **File:** `ocarina.asm`
- **Functionality:** A multi-song instrument. When selected, the player can cycle through learned songs using the L/R shoulder buttons. Pressing 'Y' plays the selected song, triggering its unique effect.
- **Songs & Effects:**
- **Song of Healing:** Heals certain NPCs or triggers quest events.
- **Song of Storms:** Toggles a rain overlay on the overworld, which can affect the environment (e.g., watering the Magic Bean).
- **Song of Soaring:** Warps the player to pre-defined locations (the vanilla flute's bird travel).
- **Song of Time:** Toggles the in-game time between day and night.
### Shared Slot: Portal Rod & Fishing Rod
- **Files:** `portal_rod.asm`, `fishing_rod.asm`
- **Functionality:** These two distinct items share a single inventory slot. If the player has the upgrade (`$7EF351 >= 2`), they can swap between the two by pressing L/R in the menu.
- **Portal Rod:** Fires a projectile that creates a portal sprite (blue or orange). The `Ancilla_HandlePortalCollision` logic detects when another projectile (like an arrow) hits a portal and teleports it to the other portal's location.
- **Fishing Rod:** Initiates a fishing minigame. `LinkItem_FishingRod` spawns a "floater" sprite, and the player can reel it in to catch fish or other items from a prize table.
## 4. New Passive Items
### Magic Rings
- **File:** `magic_rings.asm`
- **Functionality:** Passive items that grant buffs when equipped in one of the three ring slots in the Quest Status menu. The effects are applied by hooking into various game logic routines.
- **Implemented Rings:**
- **Power Ring:** Increases sword damage.
- **Armor Ring:** Reduces damage taken by half.
- **Heart Ring:** Slowly regenerates health over time.
- **Light Ring:** Allows the sword to shoot beams even when Link is not at full health (down to -2 hearts from max).
- **Blast Ring:** Increases the damage of bombs.
- **Steadfast Ring:** Prevents or reduces knockback from enemy hits.
## 5. Consumable Items (Magic Bag)
- **File:** `all_items.asm`
- **Functionality:** The Magic Bag is a sub-menu that holds new consumable items. The `Link_ConsumeMagicBagItem` routine is a jump table that executes the effect for the selected item.
- **Consumables:**
- **Banana:** Implemented. Restores a small amount of health (`#$10`).
- **Pineapple, Rock Meat, Seashells, Honeycombs, Deku Sticks:** Placeholder entries exist in the jump table, but their effects are not yet implemented (the routines just contain `RTS`).

View File

@@ -0,0 +1,145 @@
# Mask System
This document provides a detailed analysis of the Mask System in Oracle of Secrets, based on the code in the `Masks/` directory. The system allows Link to transform into various forms, each with unique graphics, palettes, and abilities.
## 1. System Architecture
The Mask System is built around a central WRAM variable and a set of core routines that handle transformations, graphics, and palettes.
- **`!CurrentMask` (`$02B2`):** A WRAM variable that stores the ID of the currently active mask. A value of `0x00` represents Link's normal human form.
- **`!LinkGraphics` (`$BC`):** A WRAM variable that holds the bank number for Link's current graphics sheet. The Mask System changes this value to load the appropriate sprite graphics for each form.
### Mask IDs
| ID | Mask / Form |
|------|---------------|
| `00` | Human (Default) |
| `01` | Deku Mask |
| `02` | Zora Mask |
| `03` | Wolf Mask |
| `04` | Bunny Hood |
| `05` | Minish Form |
| `06` | GBC Form |
| `07` | Moosh Form |
## 2. Core Routines & Hooks (`Masks/mask_routines.asm`)
A set of shared routines and hooks form the backbone of the system.
- **`Link_TransformMask`:** This is the primary function for changing forms. It is typically called when the player uses a mask item.
- **Trigger:** It requires a new R-button press (`CheckNewRButtonPress`) to prevent rapid toggling.
- **Logic:** It takes a mask ID in the A register. If the requested mask is already active, it reverts Link to his human form. Otherwise, it sets `!CurrentMask`, updates the graphics bank in `$BC` from a lookup table, and calls `Palette_ArmorAndGloves` to apply the new look.
- **Effect:** It spawns a "poof" of smoke (`AddTransformationCloud`) and plays a sound effect.
- **`Palette_ArmorAndGloves` (Hook):** This routine hooks the vanilla palette loading function (`$1BEDF9`). It checks `!CurrentMask` and jumps to the appropriate `Update...Palette` routine for the active form, ensuring the correct colors are loaded. If no mask is active, it proceeds with the vanilla logic for loading Link's tunic color.
- **`LinkItem_CheckForSwordSwing_Masks` (Hook):** This routine hooks the vanilla sword swing check (`$079CD9`). It prevents certain forms (Deku, Wolf, Minish, Moosh) from using the sword, while allowing others (Zora, GBC Link) to use it freely.
- **Reset Routines:**
- `ResetToLinkGraphics`: Reverts Link to his default graphics and `!CurrentMask = 0`.
- `ForceResetMask_GameOver` / `ForceResetMask_SaveAndQuit`: Hooks into the game over and save/quit routines to ensure Link's form is reset before the game saves or restarts.
## 3. Transformation Masks
These masks grant Link new forms with significant new abilities.
### Deku Mask
- **File:** `Masks/deku_mask.asm`
- **Transformation:** Replaces the Quake Medallion. Pressing 'Y' with the item selected transforms Link.
- **Abilities:**
- **Spin Attack:** Pressing 'Y' performs a spinning attack.
- **Deku Bubble:** If not on a Deku Flower, the spin attack also shoots a bubble projectile (`Ancilla0E_MagicBubble`).
- **Hover:** If standing on a Deku Flower (tile property check sets WRAM `$71`), the spin attack launches Link into the air, allowing him to hover for a short time.
- **Bomb Drop:** While hovering, pressing 'Y' drops a bomb.
- **Cancel Hover:** Pressing 'B' or letting the timer expire cancels the hover.
- **Key Flags & Variables:**
- `!CurrentMask`: `0x01`
- `DekuFloating` (`$70`): Flag set when Link is hovering.
- `DekuHover` (`$71`): Flag set when Link is standing on a Deku Flower, enabling the hover ability.
- **Code Interactions:**
- Hooks `LinkItem_Quake` (`$07A64B`).
- Repurposes Link's "Using Quake Medallion" state (`$5D = 0x0A`) for the hover ability.
- Hooks `LinkOAM_DrawShield` (`$0DA780`) to prevent the shield from being drawn.
### Zora Mask
- **File:** `Masks/zora_mask.asm`
- **Transformation:** Replaces the Bombos Medallion. Pressing 'Y' with the item selected transforms Link.
- **Abilities:**
- **Diving:** Allows Link to dive in deep water by pressing 'Y'. Pressing 'Y' again resurfaces.
- **Overworld Diving:** When diving in the overworld, Link becomes invincible, moves faster, and is hidden beneath a ripple effect.
- **Dungeon Diving:** When diving in a dungeon, Link moves to the lower layer (`$EE=0`), allowing him to swim under floors and obstacles.
- **Sword:** The Zora form can use the sword.
- **Key Flags & Variables:**
- `!CurrentMask`: `0x02`
- `!ZoraDiving` (`$0AAB`): Flag set when Link is currently underwater.
- **Code Interactions:**
- Hooks `LinkItem_Bombos` (`$07A569`).
- Hooks the end of `LinkState_Swimming` (`$079781`) to handle the dive input.
- Hooks the end of `LinkState_Default` (`$0782D2`) to handle resurfacing in dungeons.
- Hooks `Link_HopInOrOutOfWater_Vertical` (`$07C307`) to reset the dive state when using water stairs.
### Wolf Mask
- **File:** `Masks/wolf_mask.asm`
- **Transformation:** Shares an item slot with the Flute. When selected, it replaces the Shovel. Pressing 'Y' transforms Link.
- **Abilities:**
- **Dig:** When transformed, pressing 'Y' executes the vanilla `LinkItem_Shovel` routine, allowing Wolf Link to dig for items without needing the shovel.
- **Key Flags & Variables:**
- `!CurrentMask`: `0x03`
- **Code Interactions:**
- Hooks the `LinkItem_Shovel` vector (`$07A313`) to a new `LinkItem_ShovelAndFlute` routine that dispatches between the Flute and Wolf Mask logic based on the selected item (`$0202`).
### Minish Form
- **File:** `Masks/minish_form.asm`
- **Transformation:** Context-sensitive. When standing on a special portal tile (`ID 64`), pressing 'R' transforms Link into Minish form. Pressing 'R' on the portal again reverts him.
- **Abilities:**
- **Access Minish Areas:** Allows Link to pass through special small openings (`Tile ID 65`).
- **Restricted Actions:** Cannot lift objects.
- **Key Flags & Variables:**
- `!CurrentMask`: `0x05`
- **Code Interactions:**
- Hooks the overworld (`$07DAF2`) and underworld (`$07D8A0`) tile collision tables to add handlers for the portal and passage tiles.
- Hooks the lift check (`$079C32`) to disable lifting while in Minish form.
### Moosh Form
- **File:** `Masks/moosh.asm`
- **Transformation:** The trigger for transforming into Moosh is not defined within the mask's own file, but is handled by `Link_TransformMoosh`.
- **Abilities:**
- **Hover Dash:** Attempting to use the Pegasus Boots (dash) while in Moosh form will instead trigger a short hover, similar to the Deku Mask's ability.
- **Key Flags & Variables:**
- `!CurrentMask`: `0x07`
- **Code Interactions:**
- Hooks the dash initiation logic (`$079093`) to intercept the dash and call `PrepareQuakeSpell`, which sets Link's state to `0x0A` (hover).
- Shares the hover/recoil animation logic with the Deku Mask.
## 4. Passive & Cosmetic Forms
### Bunny Hood
- **File:** `Masks/bunny_hood.asm`
- **Transformation:** Replaces the Ether Medallion. Pressing 'Y' activates the Bunny Hood state. This is a state change, not a visual transformation.
- **Abilities:**
- **Increased Speed:** While the Bunny Hood is the active mask (`!CurrentMask = 0x04`), Link's movement speed is increased across various actions (walking, carrying, etc.). The specific speed values are defined in `BunnySpeedTable`.
- **Key Flags & Variables:**
- `!CurrentMask`: `0x04`
- **Code Interactions:**
- Hooks `LinkItem_Ether` (`$07A494`) to trigger the state change.
- Hooks the velocity calculation in the player engine (`$07E330`) to load custom speed values from a table.
### GBC Form
- **File:** `Masks/gbc_form.asm`
- **Transformation:** An automatic, cosmetic transformation that occurs whenever Link is in the Dark World.
- **Abilities:**
- Changes Link's graphics to a Game Boy Color-inspired sprite.
- Applies a unique, limited-color palette. The palette correctly reflects Link's current tunic (Green, Blue, or Red).
- This form can still use the sword.
- **Key Flags & Variables:**
- `!CurrentMask`: `0x06`
- `$0FFF`: The vanilla Dark World flag.
- **Code Interactions:**
- Hooks numerous overworld, underworld, and transition routines to consistently apply the effect when in the Dark World and remove it when in the Light World.

View File

@@ -0,0 +1,77 @@
# Custom Menu & HUD System
This document provides a detailed analysis of the custom menu and Heads-Up Display (HUD) systems in Oracle of Secrets, based on the code in the `Menu/` directory.
## 1. Overview
The project features a completely custom menu and HUD, replacing the vanilla systems. The menu is a robust, multi-screen system, while the HUD provides a clean, modern interface for in-game stats.
- **Menu System**: A two-page design that separates selectable items from quest status and equipment. It is accessible by pressing the Start button.
- **HUD System**: A persistent on-screen display for health, magic, rupees, and the currently equipped item.
## 2. Menu System Architecture
The entire menu operates as a large state machine, with the main entry point being `Menu_Entry` in `Menu/menu.asm`. The flow is controlled by the value in WRAM `$0200`.
### 2.1. Main State Machine
The `Menu_Entry` routine uses a jump table (`.vectors`) to execute different subroutines based on the state in `$0200`. This modular approach allows for a clean separation of tasks like initialization, drawing, input handling, and screen transitions.
**Key States in `$0200`:**
| State ID | Label | Purpose |
|----------|----------------------------|-------------------------------------------------------------------------|
| `0x00` | `Menu_InitGraphics` | Initializes the menu, clears player state, and prepares for drawing. |
| `0x01` | `Menu_UploadRight` | Draws the entire right-hand screen (Quest Status). |
| `0x02` | `Menu_UploadLeft` | Draws the entire left-hand screen (Item Selection). |
| `0x04` | `Menu_ItemScreen` | The main interactive state for the Item screen. Handles cursor movement. |
| `0x05` | `Menu_ScrollTo` | Handles the smooth scrolling animation when moving from Items to Quest. |
| `0x06` | `Menu_StatsScreen` | The main interactive state for the Quest Status screen. |
| `0x0A` | `Menu_Exit` | Exits the menu, restores the game state, and updates the equipped item. |
| `0x0C` | `Menu_MagicBag` | A sub-menu for viewing collectible items. |
| `0x0D` | `Menu_SongMenu` | A sub-menu for selecting Ocarina songs. |
| `0x0E` | `Menu_Journal` | A sub-menu for reading the player's journal. |
| `0x09` | `Menu_RingBox` | A sub-menu for managing magic rings. |
### 2.2. Item Selection Screen (Left Page)
This is the primary interactive screen where the player selects their Y-button item.
- **Drawing (`DrawYItems`):** This routine is responsible for rendering all 24 item slots. It reads the SRAM address for each slot from `Menu_AddressIndex` (`menu_select_item.asm`), checks if the player owns the item, and then calls `DrawMenuItem`.
- **`DrawMenuItem`:** This generic function is the core of the drawing system. It takes an item's SRAM value (e.g., Sword level 0-4) and uses it to look up the correct 16x16 tile data from a large graphics table in `Menu/menu_gfx_table.asm`. This makes the menu highly data-driven.
- **Selection (`menu_select_item.asm`):** Cursor movement is handled by `Menu_FindNextItem`, `Menu_FindPrevItem`, etc. These routines intelligently skip over empty slots, ensuring the cursor always lands on a valid item. The currently selected slot index is stored in `$0202`.
### 2.3. Quest Status Screen (Right Page)
This screen is a static display of the player's overall progress.
- **Drawing:** It is rendered by a series of functions in `menu_draw.asm`, including:
- `Menu_DrawQuestItems`: Draws equipped sword, shield, tunic, etc.
- `Menu_DrawPendantIcons` & `Menu_DrawTriforceIcons`: Reads SRAM flags to draw collected pendants and crystals.
- `Menu_DrawCharacterName`: Reads the player's name from SRAM and renders it.
- `DrawLocationName`: Reads the current overworld area (`$008A`) or underworld room (`$00A0`) and looks up the corresponding name from the tables in `menu_map_names.asm`.
## 3. HUD System Architecture
The HUD is a separate system that hooks the vanilla game's NMI rendering routines. Its main entry point is `HUD_Update` in `Menu/menu_hud.asm`.
- **Functionality:** The `HUD_Update` routine runs every frame during gameplay. It reads player stats directly from SRAM and WRAM and draws them to the VRAM buffer for the top of the screen.
- **Key Drawing Logic:**
- **Hearts:** `HUD_UpdateHearts` is a loop that draws empty hearts based on `MAXHP` (`$7EF36C`) and then overlays full/partial hearts based on `CURHP` (`$7EF36D`).
- **Magic Meter:** It reads `MagicPower` (`$7EF36E`) and uses the `MagicTilemap` lookup table to find the correct tiles to display the green bar.
- **Counters:** It uses a `HexToDecimal` routine to convert the values for Rupees, Bombs, and Arrows into drawable digits.
- **Equipped Item:** `HUD_UpdateItemBox` reads the currently equipped item index (`$0202`), finds its graphics data in the `HudItems` table, and draws the icon in the top-left box.
## 4. Data-Driven Design & Areas for Improvement
The entire menu and HUD are heavily data-driven, which is a major strength.
- **Graphics:** All item icons for both the menu and HUD are defined in data tables in `menu_gfx_table.asm` and `menu_hud.asm`, not hardcoded.
- **Item Layout:** The position and SRAM address of every item in the menu are defined in the `Menu_ItemCursorPositions` and `Menu_AddressIndex` tables, allowing the layout to be easily changed.
- **Text:** Item names, location names, and other text are all stored in data tables in `menu_text.asm` and `menu_map_names.asm`.
This analysis confirms the suggestions in the placeholder `Menu.md` file:
1. **Refactor Redundant Code:** The input handling logic for the Magic Bag, Song Menu, and Ring Box is nearly identical and is a prime candidate for being refactored into a single, reusable subroutine.
2. **Use `table` for Jump Tables:** The main `Menu_Entry` jump table is created with manual `dw` directives and would be cleaner and safer if generated with asar's `table` directive.
3. **Replace Hardcoded Values:** Hardcoded state values (e.g., `LDA.b #$0C : STA.w $0200`) should be replaced with named constants (`!MENU_STATE_MAGIC_BAG = $0C`) for readability and maintainability.

View File

@@ -0,0 +1,137 @@
# Music Creation Guide
This document details the process for creating and integrating custom music into Oracle of Secrets. The project uses the native Super Nintendo Packet Chip (N-SPC) music format, abstracted through a powerful set of `asar` macros.
## 1. N-SPC Music Format Primer
Music in the N-SPC engine is structured around eight independent channels. Each channel is a stream of bytes that are read sequentially. The stream consists of two types of data:
- **Commands:** Special bytes (from `$E0` to `$FF`) that control aspects of the sound, such as setting the instrument, changing volume, or calling a subroutine.
- **Notes:** A note consists of a duration byte followed by one or more tone bytes. The duration byte (e.g., `$48` for a quarter note) determines how long the following tone(s) will play.
## 2. The Macro System (`Core/music_macros.asm`)
To make composing more intuitive, the project uses a comprehensive library of macros that wrap the raw N-SPC commands into readable names. All music files must include `Core/music_macros.asm`.
### Key Concepts
- **Note Durations:** Constants are defined for standard note lengths (e.g., `!4th`, `!8th`, `!16th`).
- **Note Tones:** Constants are defined for all notes across several octaves (e.g., `C4`, `G4s` for G#4, `A5`).
- **Special Notes:** `Tie` (`$C8`) continues the previous note for the new duration, and `Rest` (`$C9`) signifies silence.
### Core Macros
- **`%SetInstrument(id)`:** Sets the instrument for the current channel (e.g., `%SetInstrument($09)` for Strings). Helper macros like `%Strings()`, `%Piano()`, etc., exist for common instruments.
- **`%SetTempo(value)`:** Sets the overall playback speed of the song.
- **`%SetMasterVolume(value)` / `%SetChannelVolume(value)`:** Sets the volume for the entire song or just the current channel.
- **`%CallSubroutine(address, repeats)`:** The most important macro for structuring songs. It jumps to a labeled subroutine, plays it `repeats+1` times, and then returns. **This is the primary method for looping musical phrases.**
- **`%VibratoOn(delay, rate, depth)`:** Adds a vibrato effect.
- **`%TremoloOn(delay, rate, depth)`:** Adds a tremolo (volume fluctuation) effect.
- **`%SetPan(value)`:** Sets the stereo position (left/right) of the channel.
- **`%EchoVBits(switch, left, right)`:** Enables and configures echo for the channel.
## 3. Song File Structure
Every song `.asm` file follows a standard structure.
#### 1. Header
The file begins with a header that defines metadata for the song engine.
```asm
MyNewSong:
!ARAMAddr = $D86A ; Base address in ARAM for this song
dw !ARAMAddr+$0A ; Pointer to the Intro section
dw !ARAMAddr+$1A ; Pointer to the Main (looping) section
dw $00FF ; Default fade-in
dw !ARAMAddr+$02 ; Start of the looping section data
dw $0000
```
#### 2. Channel Pointers
Next is a table of pointers to each of the eight channel data blocks. The `!ARAMC` constant is used to make these pointers relative to the song's ARAM address.
```asm
.Channels
!ARAMC = !ARAMAddr-MyNewSong
dw .Channel0+!ARAMC
dw .Channel1+!ARAMC
; ...up to 8 channels, use dw $0000 for unused channels
```
#### 3. Channel Data
Each channel is a block of code containing commands and notes.
```asm
.Channel0
%SetMasterVolume($DA)
%SetTempo(62)
%SetInstrument($02) ; Tympani
%SetDurationN(!4th, $7F)
%CallSubroutine(.sub1+!ARAMC, 23) ; Call subroutine .sub1 24 times
db End ; $00, signifies end of channel data
```
#### 4. Subroutines
The bulk of a song is made of small, labeled subroutines containing musical phrases. These are placed after the channel data.
```asm
.sub1
db !4th, B1, B1, !8th, Tie, C2, !4th, F3s
db End ; Subroutines must also end with $00
```
## 4. How to Add a New Song
1. **Create the File:** Create a new `.asm` file in the `Music/` directory.
2. **Copy Template:** Copy the contents of an existing song (e.g., `stone_tower_temple_v2.asm`) into your new file to use as a template.
3. **Set Header:** Change the main label (e.g., `MyNewSong:`) and set the `!ARAMAddr`. This address must be unique and not conflict with other songs.
4. **Compose:** Write your music in the channel and subroutine blocks using the note constants and macros.
5. **Integrate the Song:**
- Open `Music/all_music.asm` and add an `incsrc` for your new file.
- To replace a vanilla song, find its label in the ROM map and use `org` to place your new song at that address. For example, to replace the Lost Woods theme:
```asm
org $1AADDE ; Original address of Lost Woods theme
incsrc "Music/MyNewSong.asm"
```
- To add a new song to the expanded Dark World bank, open `Music/expanded.asm` and add a new entry to the `SongBank_OverworldExpanded_Main` table.
## 5. Proposals for Improved Organization
The current system is functional but can be made more readable and maintainable.
1. **Standardize Subroutine Naming:** The current convention of `.sub1`, `.sub101`, etc., is ambiguous. A clearer naming scheme would greatly improve readability.
- **Proposal:** Name subroutines based on their musical function, like `.MelodyVerseA`, `.BasslineIntro`, `.PercussionFill1`. This makes the main channel blocks easier to read as a high-level song structure.
2. **Create a Common Patterns Library:** Many songs use similar rhythmic or melodic patterns (e.g., a standard 4/4 drum beat, an arpeggiated chord).
- **Proposal:** Create a `Music/common_patterns.asm` file. This file could contain a library of generic, reusable subroutines for things like drum patterns, basslines, or common arpeggios. Songs could then `incsrc` this library and call these patterns, reducing code duplication and speeding up composition.
3. **Develop Advanced Composition Macros:** The existing helper macros are basic. More advanced macros could abstract away the manual process of defining and calling subroutines.
- **Proposal:**
- `%DefineMeasure(Name, Notes...)`: A macro that takes a name and a list of notes and automatically creates a correctly formatted subroutine block.
- `%PlayMeasure(Name, Repeats)`: A macro that automatically calculates the relative address (`+!ARAMC`) and calls `%CallSubroutine`.
- **Example Workflow with Proposed Macros:**
```asm
; --- Subroutine Definitions ---
%DefineMeasure(VerseMelody, !8th, C4, D4, E4, F4, G4, A4, B4, C5)
%DefineMeasure(VerseBass, !4th, C2, G2, A2, F2)
; --- Channel Data ---
.Channel0
; ... setup ...
%PlayMeasure(VerseMelody, 4) ; Plays the melody 4 times
db End
.Channel1
; ... setup ...
%PlayMeasure(VerseBass, 4) ; Plays the bassline 4 times
db End
```
This approach would make the main channel data blocks read like a high-level song arrangement, significantly improving clarity.
4. **Improve In-File Documentation:**
- **Proposal:** Encourage the use of comments to label major song sections directly within the channel data (e.g., `; --- VERSE 1 ---`, `; --- CHORUS ---`). This provides crucial signposting when navigating complex song files.

848
Docs/GEMINI.md Normal file
View File

@@ -0,0 +1,848 @@
# Gemini Development Guidelines for Oracle of Secrets
This document outlines the established coding conventions, architectural patterns, and best practices observed in the Oracle of Secrets project. Adhering to these guidelines will ensure consistency and maintainability.
## 1. SNES 65816 Processor Basics
### 1.1. Architecture Overview
The 65816 is an 8/16-bit microprocessor used in the Super Nintendo Entertainment System (SNES). It operates in two modes: emulation mode (6502-compatible, 8-bit) and native mode (65816, 16-bit). The SNES typically runs in native mode.
### 1.2. Key Registers
- **A (Accumulator):** The primary register for data manipulation. Its size (8-bit or 16-bit) is controlled by the M flag in the Processor Status Register.
- **X, Y (Index Registers):** Used for addressing memory. Their size (8-bit or 16-bit) is controlled by the X flag in the Processor Status Register.
- **S (Stack Pointer):** Points to the current top of the stack.
- **D (Direct Page Register):** Used for direct page addressing, allowing faster access to the first 256 bytes of each bank.
- **DB (Data Bank Register):** Specifies the current 64KB data bank for memory accesses.
- **PB (Program Bank Register):** Specifies the current 64KB program bank for instruction fetches.
- **P (Processor Status Register):** A crucial 8-bit register containing various flags:
- **N (Negative):** Set if the result of an operation is negative.
- **V (Overflow):** Set if an arithmetic overflow occurs.
- **M (Memory/Accumulator Select):** Controls the size of the A register (0=16-bit, 1=8-bit).
- **X (Index Register Select):** Controls the size of the X and Y registers (0=16-bit, 1=8-bit).
- **D (Decimal Mode):** Enables BCD arithmetic (rarely used in SNES development).
- **I (IRQ Disable):** Disables interrupt requests.
- **Z (Zero):** Set if the result of an operation is zero.
- **C (Carry):** Used for arithmetic operations and bit shifts.
### 1.3. Processor Status Register (P) Manipulation
- **`SEP #$20` (or `SEP #$30`):** Sets the M flag (and X flag if $30 is used) to 1, switching A (and X/Y) to 8-bit mode.
- **`REP #$20` (or `REP #$30`):** Resets the M flag (and X flag if $30 is used) to 0, switching A (and X/Y) to 16-bit mode.
- **Importance:** Mismatched M/X flags between calling and called routines are a common cause of crashes (BRKs) or unexpected behavior. Always ensure the P register is in the expected state for a given routine, or explicitly set it.
**Practical Example:**
```asm
; INCORRECT: Size mismatch causes corruption
REP #$20 ; A = 16-bit
LDA.w #$1234 ; A = $1234
SEP #$20 ; A = 8-bit now
LDA.w #$1234 ; ERROR: Assembler generates LDA #$34, $12 becomes opcode!
; CORRECT: Match processor state to operation
REP #$20 ; A = 16-bit
LDA.w #$1234 ; A = $1234
SEP #$20 ; A = 8-bit
LDA.b #$12 ; Load only 8-bit value
```
**Best Practice:**
```asm
MyFunction:
PHP ; Save caller's processor state
SEP #$30 ; Set to known 8-bit state
; ... your code here ...
PLP ; Restore caller's processor state
RTL
```
See `Docs/General/Troubleshooting.md` Section 3 for comprehensive processor state troubleshooting.
### 1.4. Memory Mapping
- The SNES has a 24-bit address space, allowing access to up to 16MB of ROM/RAM.
- **Banks:** Memory is organized into 256 banks of 64KB each.
- **Direct Page (Bank 00):** The first 256 bytes of bank 00 (`$0000-$00FF`) are special and can be accessed quickly using direct page addressing (when D=0).
- **WRAM (Work RAM):** Located in banks $7E-$7F. This is where most game variables and temporary data are stored.
- **SRAM (Save RAM):** Typically located in banks $70-$7D, used for saving game progress.
## 2. Asar Best Practices
### 2.1. `pushpc`/`pullpc` and `org`
- **Guideline:** While `pushpc`/`pullpc` is good for isolating small, targeted patches, extreme care must be taken. Patches that use `org` to place new code into freespace (e.g., `org $2B8000`) have a dependency on their location within the file. Moving these `org` blocks can break the ROM by changing the memory layout.
- **Rationale:** The order of `incsrc` and `org` directives determines the final ROM layout. Moving a freespace `org` block to a central `patches.asm` file changes this order and will likely cause errors. Simple, single-line patches that modify existing vanilla code can often be moved, but larger blocks of new code should remain in their contextually relevant files.
### 2.2. Scoping and Style
- **Guideline:** The established code style uses labels followed by `{}` brackets to define scope for new blocks of logic. This convention must be followed. The `subroutine`/`endsubroutine` keywords are explicitly *not* to be used in this project.
- **Rationale:** The `subroutine`/`endsubroutine` keywords are not used in this project. Maintaining a consistent style is crucial for readability.
### 2.3. Data Organization
- **Guideline:** For complex, related data (like sprite state or system configurations), use `struct`. For jump tables or data arrays, use `table`.
- **Rationale:** These directives make data structures explicit and readable. They replace confusing pointer arithmetic and manual offset calculations with clear, named accessors, which is less error-prone.
### 2.4. Define Constants for Magic Numbers
- **Guideline:** Avoid hardcoding numerical values. Use `!` or `define()` to create named constants for RAM/SRAM addresses, item IDs, sprite states, tile IDs, etc.
- **Rationale:** This makes the code self-documenting and significantly easier to maintain and debug.
### 2.5. Opcode Size Suffixes (.b, .w, .l)
`asar` can often infer operand sizes, but relying on this can lead to bugs when the processor state (M and X flags) is not what you expect. To write robust, readable, and safe code, you should use explicit size suffixes.
- **`.b` (byte):** Forces an 8-bit operation. Use this when you are certain you are working with a single byte.
- Example: `LDA.b $7E0010` will correctly load a single byte into the accumulator, regardless of the M flag's state.
- **`.w` (word):** Forces a 16-bit operation. Use this when working with two bytes (a word).
- Example: `LDA.w $7E0022` will load a 16-bit value. This is essential for correctness if the M flag is 1 (8-bit mode).
- **`.l` (long):** Forces a 24-bit operation, typically for addresses in `JML` or `JSL`.
- Example: `JSL.l SomeRoutineInAnotherBank`
**Golden Rule:** A mismatch between the M/X flags and the intended operation size is a primary cause of crashes. When in doubt, wrap your code in `REP`/`SEP` to explicitly set the processor state, and use size suffixes to make your intent clear to both the assembler and future developers.
## 3. Project-Specific Conventions
### 3.1. File & Directory Structure
- The project is well-organized by functionality (`Core`, `Items`, `Sprites`, `Overworld`, etc.). New code should be placed in the appropriate directory.
- Central include files (e.g., `all_items.asm`, `all_sprites.asm`) are used to aggregate modules. This is a good pattern to continue.
### 3.2. Patch Management
- **Guideline:** Hooks and patches should be placed logically near the code they relate to, not centralized in `Core/patches.asm`. This improves code organization, maintainability, and context preservation.
- **Rationale:** When a hook modifies vanilla behavior to add custom functionality, placing the hook in the same file as the custom implementation keeps related code together. This makes it easier to understand the complete feature, debug issues, and maintain the codebase.
- **Exception:** Only truly generic, cross-cutting patches that don't belong to any specific feature should be considered for `Core/patches.asm`.
### 3.3. Debugging
- The `!DEBUG` flag and `%print_debug()` macro in `Util/macros.asm` should be used for all build-time logging. This allows for easy enabling/disabling of diagnostic messages.
### 3.4. Referencing Vanilla Code (`usdasm`)
- When hooking or modifying vanilla code, it is essential to understand the original context. The `usdasm` disassembly is the primary reference for this.
- To find the original code for a patch at a given address (e.g., `$07A3DB`), you can search for the SNES address in the `usdasm` files (e.g., `#_07A3DB:`).
- **Vanilla labels are not included by default.** The `usdasm` project is a reference, not part of the build. If you need to call a vanilla routine, you must find its implementation in the disassembly and explicitly copy or recreate it within the `Oracle of Secrets` source, giving it a new label (e.g., inside the `Oracle` namespace).
- **Disassembly files are for reference only.** Never modify any files within the `usdasm` directory. All changes must be made within the `Oracle of Secrets` project files.
### 3.5. Namespacing
- **Guideline:** The majority of the *Oracle of Secrets* codebase is organized within an `Oracle` namespace, as defined in `Oracle_main.asm`. However, some modules, notably `ZSCustomOverworld.asm`, are included *outside* of this namespace.
- **Interaction:** To call a function that is inside the `Oracle` namespace from a file that is outside of it (like `ZSCustomOverworld.asm`), you must prefix the function's label with `Oracle_`. For example, to call the `CheckIfNight16Bit` function (defined inside the namespace), you must use `JSL Oracle_CheckIfNight16Bit`.
- **Rationale:** The build process correctly resolves these `Oracle_` prefixed labels to their namespaced counterparts (e.g., `Oracle.CheckIfNight16Bit`). Do not add the `Oracle_` prefix to the original function definition; it is only used by the calling code outside the namespace.
**Practical Example - Oracle to ZScream:**
```asm
// In ZScream file (no namespace):
LoadOverworldSprites_Interupt:
{
; ZScream code here
RTL
}
// Export to Oracle namespace:
namespace Oracle
{
Oracle_LoadOverworldSprites_Interupt = LoadOverworldSprites_Interupt
}
// Now Oracle code can call it:
namespace Oracle
{
MyFunction:
JSL Oracle_LoadOverworldSprites_Interupt ; Use prefix!
RTL
}
```
**Practical Example - ZScream to Oracle (Bridge Pattern):**
```asm
// Oracle implementation:
namespace Oracle
{
CheckIfNight:
LDA.l $7EE000 ; Check time system
; ... logic ...
RTL
}
// Bridge function (no namespace):
ZSO_CheckIfNight:
{
JSL Oracle_CheckIfNight ; Can call INTO Oracle
RTL
}
// Export bridge:
namespace Oracle
{
Oracle_ZSO_CheckIfNight = ZSO_CheckIfNight
}
// ZScream hook can use it:
org $09C4C7
LoadOverworldSprites_Hook:
JSL Oracle_ZSO_CheckIfNight ; Bridge function
```
For comprehensive namespace troubleshooting and advanced patterns, see:
- `Docs/World/Overworld/ZSCustomOverworldAdvanced.md` Section 5 (Cross-Namespace Integration)
- `Docs/General/Troubleshooting.md` Section 5 (Cross-Namespace Calling)
- `Docs/General/DevelopmentGuidelines.md` Section 2.4 (Namespace Architecture)
### 3.6. Safe Hooking and Code Injection
When modifying vanilla game logic, it is critical to never edit the disassembly files in `ALTTP/` or `usdasm/` directly. Instead, use the following safe hooking method to inject custom code.
- **1. Identify a Target and Free Space:**
- Locate the exact address in the vanilla code you want to modify (the "hook point").
- Identify a free bank or region in the ROM to place your new, expanded code.
- **2. Choose the Appropriate File for Your Hook:**
- **Feature-Specific Hooks:** Place hooks in the same file as the custom implementation they enable. For example, if you're adding a new item feature in `Items/magic_ring.asm`, place the vanilla hook in that same file.
- **Module-Specific Hooks:** For hooks that modify core game systems (sprite engine, player engine, etc.), place them in the relevant module file within the `Core/` directory.
- **Generic Patches:** Only place truly generic, cross-cutting modifications in `Core/patches.asm` (e.g., fixes to vanilla bugs, performance optimizations).
- **Rationale:** Co-locating hooks with their implementations improves code organization, makes features self-contained, and provides better context for future maintenance.
- **3. Write the Hook:**
- Use `pushpc` and `pullpc` to isolate your patch.
- Use `org` to navigate to the target address in the vanilla code.
- At the target address, overwrite the original instruction(s) with a `JSL` (or `JMP`) to your new custom routine in free space.
- **Crucially, ensure your `JSL`/`JMP` instruction and any necessary `NOP`s perfectly replace the original instruction(s) byte-for-byte.** A `JSL` is 4 bytes. If you overwrite an instruction that is only 2 bytes, you must add `NOP` instructions to fill the remaining 2 bytes to avoid corrupting the subsequent instruction.
- **4. Implement the Custom Routine:**
- In a `freedata` block (or using `org` with a free space address), write your new routine in the same file as the hook.
- **Preserve Overwritten Code:** The first thing your new routine must do is execute the exact vanilla instruction(s) that you overwrote with your `JSL`. This is essential to maintain the original game's behavior.
- After preserving the original logic, add your new custom code.
- End your routine with an `RTL` (to return from a `JSL`) or `RTS` (to return from a `JSR`).
- **Example (Feature-Specific Hook):**
```asm
; In Items/magic_ring.asm
; 1. Place the new, expanded logic in a free bank.
org $348000
MagicRing_CustomEffect:
{
; 2. First, execute the original instruction(s) that were overwritten.
LDA.b #$01 ; Example: Original instruction was LDA #$01 (2 bytes)
STA.w $0DD0,X ; Example: Original instruction was STA $0DD0,X (3 bytes)
; 3. Now, add your new custom logic for the magic ring.
LDA.l MagicRing ; Check if player has magic ring
BEQ .no_ring
LDA.b #$FF ; Apply ring's special effect
STA.w $1234,X
.no_ring
; 4. Return to the vanilla code.
RTL
}
; 5. Hook placement: In the same file, near the feature implementation
pushpc
org $05C227 ; Target address in vanilla sprite damage routine
JSL MagicRing_CustomEffect ; JSL is 4 bytes.
NOP ; Fill the 5th byte since we overwrote two instructions (2+3=5 bytes)
pullpc
```
- **Example (Core System Hook):**
```asm
; In Core/sprite_engine_hooks.asm (or similar)
org $348100
CustomSprite_DeathHandler:
{
; Preserve original death logic
LDA.w SprHealth, X
BNE .not_dead
; Original vanilla death code here
JSL Sprite_SpawnDeathAnimation
.not_dead
; Add custom death effects for Oracle sprites
LDA.w SprType, X
CMP.b #CustomSpriteID : BNE .skip_custom
JSR CustomSprite_SpecialDeath
.skip_custom
RTL
}
; Hook in same file
pushpc
org $068450
JSL CustomSprite_DeathHandler
pullpc
```
## 4. Build Process and ROM Management
- **Clean ROM**: The clean, unmodified "The Legend of Zelda: A Link to the Past" ROM should be placed at `Roms/oos169.sfc`. This path is included in `.gitignore`, so the ROM file will not be committed to the repository.
- **Build Script**: A `build.sh` script is provided to automate the build process. For detailed usage, see `Docs/General/AsarUsage.md`.
- **Workflow**: The build script creates a fresh copy of the clean ROM and applies the `Oracle_main.asm` patch to it using `asar`.
- **Important**: Never apply patches directly to `Roms/oos169.sfc`. Always use the build script to create a new, patched ROM. This ensures the clean ROM remains untouched for future builds.
## 5. Debugging Tips for BRKs and Crashes
When encountering unexpected crashes (often indicated by a `BRK` instruction in emulators), especially after modifying code, consider the following:
**For comprehensive debugging guidance with step-by-step procedures, see `Docs/General/Troubleshooting.md`.**
### 5.1. Most Common Causes
- **Processor Status Register (P) Mismatch:** This is a very common cause. If a routine expects 8-bit accumulator/index registers (M=1, X=1) but is called when they are 16-bit (M=0, X=0), or vice-versa, memory accesses and arithmetic operations will be incorrect, leading to crashes.
**Example:**
```asm
; BAD: Size mismatch
REP #$20 ; A = 16-bit
JSL Function ; Function expects 8-bit!
; Inside Function:
SEP #$20 ; Sets 8-bit mode
LDA.b #$FF
STA.w $1234 ; Only stores $FF, not $00FF!
RTL ; ← Doesn't restore caller's 16-bit mode
; GOOD: Preserve state
Function:
PHP ; Save caller's state
SEP #$20 ; Set to 8-bit
LDA.b #$FF
STA.w $1234
PLP ; Restore caller's state
RTL
```
- **Stack Corruption:** JSL/JSR push the return address onto the stack. If a called routine pushes too much data onto the stack without popping it, or if the stack pointer (`S`) is corrupted, the return address can be overwritten, leading to a crash when `RTL`/`RTS` is executed.
- **`JSR`/`RTS` vs `JSL`/`RTL` Mismatch:** This is a critical and common error.
- `JSR` (Jump to Subroutine) pushes a 2-byte return address. It **must** be paired with `RTS` (Return from Subroutine), which pulls 2 bytes.
- `JSL` (Jump to Subroutine Long) pushes a 3-byte return address (including the bank). It **must** be paired with `RTL` (Return from Subroutine Long), which pulls 3 bytes.
**Example:**
```asm
; BAD: Mismatched call/return
MainFunction:
JSL SubFunction ; Pushes 3 bytes ($02 $C4 $09)
SubFunction:
; ... code ...
RTS ; ← ERROR: Only pops 2 bytes! Stack corrupted!
; GOOD: Matched call/return
MainFunction:
JSL SubFunction ; Pushes 3 bytes
SubFunction:
; ... code ...
RTL ; ← Correct: Pops 3 bytes
```
### 5.2. Debugging Tools
- **Mesen-S (Recommended):** The most powerful SNES debugger:
- Set breakpoints with conditions: `A == #$42`
- Memory watchpoints: `[W]$7E0730` (break on write)
- Stack viewer to trace call history
- Event viewer for NMI/IRQ timing
- Break on BRK automatically
**Quick Mesen-S Workflow:**
1. Enable "Break on BRK" in Debugger settings
2. When crash occurs, check Stack viewer
3. Read return addresses to trace call history
4. Set breakpoint before suspected crash location
5. Step through code examining registers
- **Breadcrumb Tracking:**
```asm
; Add markers to narrow down crash location
LDA.b #$01 : STA.l $7F5000 ; Breadcrumb 1
JSL SuspiciousFunction
LDA.b #$02 : STA.l $7F5000 ; Breadcrumb 2
JSL AnotherFunction
LDA.b #$03 : STA.l $7F5000 ; Breadcrumb 3
; After crash, check $7F5000 in memory viewer
; If value is $02, crash occurred in AnotherFunction
```
### 5.3. Common Error Patterns
**Pattern 1: Jumping to $000000 (BRK)**
- Cause: Corrupted jump address or return address
- Debug: Check stack contents, verify JSL/JSR is called before RTL/RTS
**Pattern 2: Infinite Loop / Freeze**
- Cause: Forgot to increment module/submodule, infinite recursion
- Debug: Check that `$10` (module) or `$11` (submodule) is incremented
**Pattern 3: Wrong Graphics / Corrupted Screen**
- Cause: DMA during active display, wrong VRAM address
- Debug: Ensure graphics updates only during NMI or Force Blank
### 5.4. Cross-Reference Documentation
For specific debugging scenarios:
- **BRK Crashes:** `Docs/General/Troubleshooting.md` Section 2
- **Stack Corruption:** `Docs/General/Troubleshooting.md` Section 3
- **Processor State Issues:** `Docs/General/Troubleshooting.md` Section 4
- **Namespace Problems:** `Docs/General/Troubleshooting.md` Section 5
- **Memory Conflicts:** `Docs/General/Troubleshooting.md` Section 6
- **Graphics Issues:** `Docs/General/Troubleshooting.md` Section 7
- **ZScream-Specific:** `Docs/General/Troubleshooting.md` Section 8
### 5.5. Recent Debugging Insights
During recent development and bug-fixing tasks, several critical patterns and debugging insights have emerged:
- **Processor Status Mismatch (M/X flags) and BRK (`$00` opcode):**
- A common cause of crashes is calling a routine expecting a different processor mode (e.g., 16-bit Accumulator) than the current CPU state (e.g., 8-bit Accumulator).
- Specifically, if an `AND.w #$00FF` instruction is encountered while the A-register is in 8-bit mode (`SEP #$20` active), the assembler may generate `29 FF 00`. The CPU will execute `29 FF` (AND immediate byte), and then interpret the trailing `00` as a `BRK` instruction, leading to a crash.
- **Resolution:** Always explicitly set the processor state (`REP #$30` for 16-bit, `SEP #$30` for 8-bit) at the entry of routines that depend on a specific mode, and consider `PHP`/`PLP` for state preservation if the routine needs to temporarily change modes or be called from various contexts.
- **Input Polling Registers for Continuous Actions:**
- For features requiring continuous input (e.g., holding a button to navigate in a menu or turn pages), use the joypad register that tracks **all pressed buttons** (`$F2` / `JOY1B_ALL`).
- Avoid using registers that only signal **new button presses** (`$F6` / `JOY1B_NEW`), as these will only trigger an action for a single frame, making continuous interaction impossible.
- **Resolution:** Pair `$F2` checks with a delay timer (`$0207` in our context) to prevent rapid-fire actions.
- **VRAM Update Flags (`$0116`, `$15`, `$17`) for Menu Graphics:**
- The variable `$0116` acts as a crucial trigger for the vanilla NMI handler (`NMI_DoUpdates`) to perform VRAM updates. **Bit 0 of `$0116` must be set (`$01`, or part of `$21`, `$23`, `$25`, etc.)** for standard tilemap and OAM buffer uploads to occur. Values like `$22` (where bit 0 is clear) may be ignored by the vanilla NMI handler for general VRAM updates.
- The `$15` flag (often referred to as the Palette/Refresh flag) should often be set alongside `$17` (NMI module selection) to ensure consistent and complete VRAM refreshes, especially for full-screen updates.
- **Resolution:** When a menu state needs to update its tilemap or OAM visually, ensure `$0116` has bit 0 set, and consider setting `$15` for comprehensive refreshes.
- **Data Table Mismatches (Logical vs. Visual Indexing):**
- In UI-heavy features (like inventory or submenus), a misalignment between a table defining the *logical order* of items (e.g., `Menu_AddressIndex`) and a table defining their *visual positions* (e.g., `Menu_ItemCursorPositions`) can lead to subtle bugs.
- **Symptom:** A cursor might appear in the wrong place, or attempting to clear a cursor from one item might inadvertently clear another, resulting in visual artifacts.
- **Resolution:** Rigorously align item indices across all related data tables, ensuring a 1:1 mapping between logical item order and visual screen coordinates.
- **Custom NMI Handlers and Vanilla System Integration:**
- Be aware that extensive custom systems (like ZScream's overworld graphics streaming) may replace or heavily modify vanilla NMI routines. These custom handlers might be designed to process their *own* DMA requests and could potentially ignore standard vanilla flags (`$0116`, `$15`) or input registers.
- **Symptom:** Standard game elements (like menus) may fail to update graphics or respond to input if the custom NMI handler does not explicitly integrate or defer to the vanilla update logic.
- **Resolution:** If a custom NMI handler is in place, it must either pass control to the vanilla NMI handler when appropriate (e.g., when the game is in a menu state), or manually replicate the necessary vanilla update logic (e.g., checking `$0116` and initiating DMA for menu buffers).
## 6. Verification Policy
- **Bugs and Features:** Never mark a bug fix or feature implementation as `DONE` until it has been thoroughly tested and verified in an emulator. This ensures stability and prevents regressions.
## 7. Memory and Symbol Analysis
This section details the layout and purpose of critical memory regions (WRAM and SRAM) and the symbol definition files that give them context.
**For comprehensive memory documentation, see:**
- `Docs/Core/MemoryMap.md` - Complete WRAM/SRAM map with verified custom variables
- `Docs/Core/Ram.md` - High-level overview of memory usage
- `Docs/General/Troubleshooting.md` Section 6 - Memory conflict resolution
### 7.1. WRAM (Work RAM) Analysis
Work RAM (WRAM) holds the active, volatile state of the entire game. The following are some of the most critical variables for understanding real-time game logic.
* **Direct Page & Scrap (`$7E0000` - `$7E000F`):** A highly volatile scratchpad for temporary, single-frame calculations.
* **Main Game State (`$7E0010` - `$7E001F`):**
* `MODE` (`$7E0010`): The primary game state variable (Overworld, Underworld, Menu, etc.). This dictates which main module is executed each frame.
* `INDOORS` (`$7E001B`): A flag (`0x01` for indoors, `0x00` for outdoors) controlling environmental factors.
* **Link's State (`$7E0020`+):** A large block containing the player's immediate state.
* `POSX`/`POSY`/`POSZ`: Link's 16-bit absolute coordinates.
* `LINKDO` (`$7E005D`): Link's personal state machine variable (walking, swimming, lifting, etc.), used by the player engine in Bank $07.
* `IFRAMES` (`$7E031F`): Invincibility frame timer after taking damage.
* **Area & Room State (`$7E008A` - `$7E00AF`):**
* `OWSCR` (`$7E008A`): The current Overworld screen ID.
* `ROOM` (`$7E00A0`): The current Underworld room ID.
* **Sprite and Ancilla Data (`$7E0D00+`):** `Core/symbols.asm` maps the data structures for all sprites and ancillae (projectiles, effects). Key variables include `SprState` (state machine), `SprType` (ID), `SprHealth`, and coordinates. This is fundamental to all NPC and enemy logic.
* **Oracle of Secrets Custom WRAM (`$7E0730+`):** A custom region for new features. Notable variables include `GoldstarOrHookshot` and `FishingOrPortalRod`, used to manage the state of new custom items.
### 7.2. SRAM (Save RAM) Analysis
SRAM stores the player's save file, including long-term progression and inventory. `Core/sram.asm` reveals significant customization for Oracle of Secrets.
#### Vanilla ALTTP Save Data:
* **Inventory:** `Bow` (`$7EF340`), `Bombs` (`$7EF343`), `Sword` (`$7EF359`), `Shield` (`$7EF35A`).
* **Player Status:** `Rupees` (`$7EF360`), `MAXHP` (`$7EF36C`), `CURHP` (`$7EF36D`).
* **Progression:** `Pendants` (`$7EF374`), `Crystals` (`$7EF37A`), `GAMESTATE` (`$7EF3C5`).
#### Oracle of Secrets (OOS) Custom SRAM Data:
This highlights the major new features of the hack.
* **New Items & Masks:** `ZoraMask` (`$7EF347`), `BunnyHood` (`$7EF348`), `DekuMask` (`$7EF349`), `RocsFeather` (`$7EF34D`), etc. These introduce major new player abilities.
* **New Progression System:**
* `OOSPROG` (`$7EF3D6`): A primary bitfield for major quest milestones unique to OOS.
* `Dreams` (`$7EF410`): A new collectible concept.
* **New Collectibles & Side-Quests:** A block from `$7EF38B` holds new items like `Bananas`, `Seashells`, and `Honeycomb`. `MagicBeanProg` (`$7EF39B`) tracks a new multi-day side-quest.
### 7.3. Symbols and Functions (`Core/symbols.asm`)
This file acts as a central header, defining constants and labels for memory addresses and functions to make the assembly code readable and maintainable.
* **Function Pointers:** It provides labels for critical functions across different ROM banks (e.g., `Sprite_CheckDamageToPlayer`, `EnableForceBlank`), allowing for modular code.
* **Memory Maps:** It contains the definitive memory maps for WRAM structures, most notably for sprites and ancillae.
* **Readability:** Its primary purpose is to replace "magic numbers" (raw addresses) with human-readable labels, which is essential for a project of this scale.
## 8. Documentation Reference
This section provides a comprehensive overview of all available documentation files, organized by category. These documents serve as key references for understanding the project's architecture, gameplay systems, and development practices.
### 8.1. Core System Documentation
* **`Docs/Core/MemoryMap.md`:** Complete WRAM and SRAM memory map with verified custom variables. Documents all custom memory regions from `$7E0730+` (WRAM) and OOS-specific SRAM including `$7EF3D6` (OOSPROG), `$7EF38A+` (collectibles block), and `$7EF410` (Dreams). Essential reference for any code that accesses RAM.
* **`Docs/Core/Ram.md`:** High-level overview of WRAM and SRAM usage patterns with descriptions of custom variable purposes.
* **`Docs/Core/Link.md`:** Documentation of Link's state machine, player mechanics, and control handlers.
* **`Docs/Core/SystemInteractions.md`:** Documentation of core game systems and their interactions.
### 8.2. Development Guidelines
* **`Docs/General/DevelopmentGuidelines.md`:** Comprehensive development guidelines covering architecture patterns, memory management, assembly best practices, coding standards, and debugging strategies. Expands on concepts in this GEMINI.md file with detailed examples and rationale. **Required reading for all contributors.**
* **`Docs/General/Troubleshooting.md`:** Comprehensive troubleshooting guide with step-by-step debugging procedures. Covers:
- BRK crashes and stack traces
- Stack corruption patterns (JSL/JSR vs RTL/RTS mismatches)
- Processor status register issues (M/X flag problems)
- Cross-namespace calling problems
- Memory conflicts and bank collisions
- Graphics/DMA timing issues
- ZScream-specific problems
- Debugging with Mesen-S and BSNES-Plus
* **`Docs/General/AsarUsage.md`:** Best practices for using the Asar assembler and the build script system. Covers `org` vs `pushpc`/`pullpc`, ROM management, and build workflow.
### 8.3. Creation Guides
* **`Docs/Guides/SpriteCreationGuide.md`:** Comprehensive 878-line tutorial for creating custom sprites from scratch. Covers:
- File setup and bank organization
- Sprite properties (23 configurable flags with memory addresses)
- Initialization with 60+ WRAM variables documented
- Main logic with 30+ macros and 20+ core functions
- OAM drawing system with tile format specifications
- Testing procedures and debugging strategies
- Common issues and solutions
- 10 advanced patterns:
1. State Machines (Booki example)
2. Multi-part Sprites (Kydreeok boss)
3. Guard/Defense Mechanics (Darknut)
4. Shared Logic (Goriya variations)
5. Complex Boss Mechanics
6. NPC Interactions (Followers)
7. Interactive Objects (Minecart)
8. Environmental Awareness
9. Advanced AI Patterns
10. Cross-System Integration
- Real examples from Booki, Darknut, Kydreeok, Goriya, Followers, and Minecart sprites
* **`Docs/Guides/QuestFlow.md`:** Detailed guide to main story and side-quest progression, including trigger conditions, progression flags, and quest dependencies.
### 8.4. World System Documentation
* **`Docs/World/Overworld/ZSCustomOverworld.md`:** Basic guide to the ZScream custom overworld system and its data tables.
* **`Docs/World/Overworld/ZSCustomOverworldAdvanced.md`:** Advanced technical documentation for ZScream integration. **Critical for modifying overworld behavior.** Covers:
- Internal hook architecture (38+ vanilla routine replacements)
- Hook execution order during screen transitions
- Memory management and state tracking (Bank $28 data pool)
- Graphics loading pipeline with frame-by-frame analysis
- Sprite loading system deep dive
- Cross-namespace integration patterns (Oracle ↔ ZScream)
- Performance considerations and optimization strategies
- Adding custom features to the overworld system
- Debugging ZScream-specific issues
* **`Docs/World/Overworld/TimeSystem.md`:** Documentation of the day/night cycle system implementation.
* **`Docs/World/Dungeons/Dungeons.md`:** Breakdown of dungeon systems, layouts, enemy placements, and puzzle mechanics.
### 8.5. Feature System Documentation
* **`Docs/Features/Menu/Menu.md`:** Analysis of the custom menu and HUD systems, including two-page menu layout and item drawing routines.
* **`Docs/Features/Items/Items.md`:** Guide to custom and modified items, including implementation details for Goldstar, Portal Rod, Ocarina, and others.
* **`Docs/Features/Music/Music.md`:** Guide to custom music tracks, sound effects, and adding new audio.
* **`Docs/Features/Masks/Masks.md`:** Comprehensive overview of the Mask System, including transformation mechanics and each mask's abilities.
### 8.6. Sprite Documentation
Each sprite category has both an overview document and individual files for specific sprites:
* **`Docs/Sprites/NPCs.md`:** Overview of NPC sprite system with links to individual NPC documentation in `Docs/Sprites/NPCs/` (BeanVendor, DekuScrub, EonOwl, Farore, Followers, Impa, Korok, Maple, MaskSalesman, Tingle, Vasu, ZoraPrincess, etc.)
* **`Docs/Sprites/Bosses.md`:** Overview of boss sprite system with links to individual boss documentation in `Docs/Sprites/Bosses/` (DarkLink, Kydreeok, Manhandla, etc.)
* **`Docs/Sprites/Enemies/`:** Individual documentation for enemy sprites (AntiKirby, Booki, BusinessScrub, Darknut, Goriya, Keese, Leever, Octorok, PolsVoice, Wolfos, etc.)
* **`Docs/Sprites/Objects.md`:** Overview of interactive object sprites with documentation in `Docs/Sprites/Objects/` (Collectible, DekuLeaf, IceBlock, Minecart, Pedestal, PortalSprite, etc.)
* **`Docs/Sprites/Overlords.md`:** Analysis of the Overlord sprite system used for environmental effects and multi-screen management.
## 9. Disassembly Analysis and Search Guide
This section provides a high-level analysis of key banks in the Link to the Past disassembly. Use this guide to quickly locate relevant code and understand the overall structure of the game.
### 9.1. Bank $00: Game Core & Main Loop
**File:** `ALTTP/bank_00.asm`
**Address Range:** `$008000` - `$00FFFF`
**Summary:** The heart of the game engine. Contains the main game loop, interrupt handlers, and the primary game state machine.
#### Key Structures & Routines:
* **`Reset:` (#_008000)**: Game entry point on boot.
* **`MainGameLoop:` (#_008034)**: Central loop, calls `Module_MainRouting`.
* **`Module_MainRouting:` (#_0080B5)**: Primary state machine dispatcher. Reads `MODE` (`$7E0010`) and uses `pool Module_MainRouting` to jump to the correct game module.
* **`Interrupt_NMI:` (#_0080C9)**: Runs every frame. Handles input (`NMI_ReadJoypads`), graphics DMA (`NMI_DoUpdates`), and sprite preparation (`NMI_PrepareSprites`).
#### Search Heuristics:
* **Game Module Logic (Overworld, Underworld, Menus):** Search `bank_00.asm` for the `pool Module_MainRouting` jump table. The labels (e.g., `Module09_Overworld`) are the entry points for each game state, determined by WRAM `$7E0010` (`MODE`).
* **Per-Frame Logic:** Search `bank_00.asm` for `Interrupt_NMI:`. Key routines called from here are `NMI_ReadJoypads` (input) and `NMI_DoUpdates` (graphics DMA).
* **Initialization Logic:** Start at the `Reset:` label in `bank_00.asm` and trace `JSR`/`JSL` calls to routines like `InitializeMemoryAndSRAM`.
### 9.2. Bank $01: Dungeon Engine
**File:** `ALTTP/bank_01.asm`
**Address Range:** `$018000` - `$01FFFF`
**Summary:** Responsible for loading, drawing, and managing all aspects of interior rooms (dungeons, houses, caves).
#### Key Structures & Routines:
* **`Underworld_LoadRoom:` (#_01873A)**: Main entry point for loading a dungeon room.
* **`DrawObjects` Tables:** A set of tables at the top of the bank defining object graphics and drawing routines.
* **`RoomDraw_DrawAllObjects:` (#_0188E4)**: Iterates through a room's object list.
* **`RoomDraw_RoomObject:` (#_01893C)**: Main dispatcher for drawing a single object based on its ID.
#### Search Heuristics:
* **Room Construction Logic:** In `bank_01.asm`, start at `Underworld_LoadRoom` and trace the call sequence: `Underworld_LoadHeader` -> `RoomDraw_DrawFloors` -> `RoomDraw_DrawAllObjects`.
* **Specific Dungeon Object Code:** To find an object's drawing code, search the `.type1_subtype_..._routine` tables at the start of `bank_01.asm` for the object's ID. The corresponding label is the drawing routine. To find its tile data, search the `.type1_subtype_..._data_offset` tables.
### 9.3. Bank $02: Overworld & Transitions
**File:** `ALTTP/bank_02.asm`
**Address Range:** `$028000` - `$02FFFF`
**Summary:** Manages loading the overworld, transitioning between areas, and handling special game sequences.
#### Key Structures & Routines:
* **`Module06_UnderworldLoad:` (#_02821E)**: Primary module for transitioning into and loading an underworld room.
* **`Module08_OverworldLoad:` (#_0283BF)**: Primary module for loading the overworld.
* **`Module07_Underworld:` (#_0287A2)**: Main logic loop for when the player is in the underworld. Dispatches to submodules based on WRAM `$11`.
#### Search Heuristics:
* **Overworld Loading:** Start at `Module08_OverworldLoad` in `bank_02.asm`. Logic checks WRAM `$8A` (overworld area number) to determine behavior.
* **Underworld Gameplay:** Start at `Module07_Underworld` in `bank_02.asm`. Examine the `.submodules` jump table to see the different states, determined by WRAM `$11`.
* **Transition Logic:** Search for code that sets the game `MODE` (`$10`) to `$08` (Overworld Load) or `$06` (Underworld Load) to find the start of a transition.
### 9.4. Bank $03: Tile32 Overworld Layout Data
### 9.5. Bank $04: Tile32 Overworld Layout Data, Dungeon Room Headers
### 9.6. Bank $07: Core Player (Link) Engine
**File:** `ALTTP/bank_07.asm`
**Address Range:** `$078000` - `$07FFFF`
**Summary:** Contains Link's core state machine, governing movement, physics, item usage, and interactions.
#### Key Structures & Routines:
* **`Link_Main:` (#_078000)**: Top-level entry point for all player logic.
* **`Link_ControlHandler:` (#_07807F)**: The heart of the player engine. A state machine dispatcher that reads `LINKDO` (`$7E005D`) and jumps via the `pool Link_ControlHandler` table.
* **`LinkState_Default` (#_078109):** The most common state, handling walking and dispatching to action sub-handlers like `Link_HandleYItem`.
#### Search Heuristics:
* **Player Action Logic (walking, swimming):** In `bank_07.asm`, search for `pool Link_ControlHandler`. The state ID is from WRAM `$7E005D` (`LINKDO`). Find the label for the desired state (e.g., `LinkState_Default`) to locate its main routine.
* **Player Physics/Collision:** Within a player state routine, search for calls to `JSL Link_HandleVelocity` (physics) and `JSR Link_HandleCardinalCollision` (collision).
* **Y-Button Item Logic:** In `LinkState_Default`, search for the call to `JSR Link_HandleYItem`.
* **Player Damage Logic:** Search for writes to WRAM `$7E0373` (`HURTME`).
### 9.7. Bank $05: Specialized Sprite & Object Engine
**File:** `ALTTP/bank_05.asm`
**Address Range:** `$058000` - `$05FFFF`
**Summary:** Code for unique, complex, and scripted sprites that do not fit the standard enemy AI model (e.g., cutscene sprites, minigame hosts, complex traps).
#### Search Heuristics:
* **Unique/Non-Enemy Sprites:** When looking for a unique sprite (minigame, cutscene object, complex trap), check `bank_05.asm` first.
* **Finding Sprite Logic:** Search for the sprite's name (e.g., "MasterSword") or its hexadecimal ID (e.g., `Sprite_62`) to find its main routine.
### 9.8. Bank $06: Main Sprite Engine & Helpers
**File:** `ALTTP/bank_06.asm`
**Address Range:** `$068000` - `$06FFFF`
**Summary:** Contains the main sprite processing engine and a vast library of shared helper subroutines used by sprites game-wide.
#### Key Structures & Routines:
* **`Sprite_Main:` (#_068328)**: The master sprite loop that iterates through all 16 sprite slots.
* **`Sprite_ExecuteSingle:` (#_0684E2)**: The state machine dispatcher for an individual sprite, reading `SprState` (`$7E0DD0,X`).
* **`SpriteModule_Initialize:` (#_06864D)**: Master initialization routine. Contains a massive jump table pointing to a specific `SpritePrep_...` routine for nearly every sprite type.
* **`Sprite_SpawnSecret` (`#_068264`):** Determines the "secret" item that appears under a liftable bush or rock.
#### Search Heuristics:
* **Sprite Initialization (HP, damage, etc.):** In `bank_06.asm`, go to `SpriteModule_Initialize`. Find the sprite's ID in the large jump table to get the label for its `SpritePrep_...` routine.
* **Sprite Core AI:** In `bank_06.asm`, go to `SpriteModule_Active`. Find the sprite's ID in its jump table to find the entry point to its main AI logic (which may be in another bank).
* **Bush/Rock Item Drops:** Locate the `Sprite_SpawnSecret` routine and examine the `.ID` table at `#_0681F4` to see the prize mappings.
### 9.9. Bank $08: Ancilla Engine
**File:** `ALTTP/bank_08.asm`
**Address Range:** `$088000` - `$08FFFF`
**Summary:** The engine for "Ancillae" (projectiles, particle effects, etc.). Contains the execution logic for entities like arrows, bombs, and magic spells.
#### Search Heuristics:
* **Projectile/Effect Logic:** In `bank_08.asm`, find the main jump table in `Ancilla_ExecuteOne` (at `#_08837F`). Look up the ancilla's ID in this table to find the label for its logic routine (e.g., `Ancilla07_Bomb`).
* **Projectile Properties (speed, graphics):** Go to the ancilla's main logic routine (e.g., `Ancilla09_Arrow`) and look for writes to its WRAM properties (e.g., `$0C2C` for X-speed).
### 9.10. Bank $09: Ancilla Spawning & Item Logic
**File:** `ALTTP/bank_09.asm`
**Address Range:** `$098000` - `$09FFFF`
**Summary:** Contains the ancilla *creation* engine (a library of `AncillaAdd_...` functions) and the critical logic for giving items to the player.
#### Search Heuristics:
* **Projectile/Effect Creation:** To find where a projectile is created, search the codebase for `JSL` calls to its corresponding `AncillaAdd_...` function in this bank (e.g., `JSL AncillaAdd_Bomb`).
* **Item "Get" Properties:** To change the properties of an item the player receives, find the `AncillaAdd_ItemReceipt` routine and examine the large data tables starting at `#_098404`.
### 9.11. Bank $0A: World Map & Flute Menu Engine
**File:** `ALTTP/bank_0A.asm`
**Address Range:** `$0A8000` - `$0AFFFF`
**Summary:** Controls all full-screen map interfaces (pause menu map, flute destination map).
#### Search Heuristics:
* **Flute Warp Destinations:** In `bank_0A.asm`, find the `FluteMenu_LoadTransport` routine. The table within it maps the 8 flute spots to screen indexes.
* **Map Icon Locations:** Search for the `WorldMapIcon_posx_...` and `WorldMapIcon_posy_...` tables to adjust icon coordinates.
### 9.12. Bank $0B: Overworld Environment & State Helpers
**File:** `ALTTP/bank_0B.asm`
**Address Range:** `$0B8000` - `$0BFFFF`
**Summary:** Miscellaneous helper functions related to the overworld environment and player state.
#### Search Heuristics:
* **Overworld Area Palette:** To change the background color of an overworld area, modify the color values loaded in `Overworld_SetFixedColAndScroll`. The logic checks WRAM `$8A` to decide which color to use.
* **Wall Master Capture:** To change what happens when captured, find the `WallMaster_SendPlayerToLastEntrance` routine.
### 9.13. Bank $0C: Intro & Credits Sequence
**File:** `ALTTP/bank_0C.asm`
**Address Range:** `$0C8000` - `$0CFFFF`
**Summary:** Handles the game's intro and end-game credits sequences.
#### Search Heuristics:
* **Intro/Credits Scene Logic:** Start at the `Module00_Intro` or `Module1A_Credits` jump tables. The sub-mode in WRAM `$11` determines which part of the sequence is running. Follow the jump table to the routine for the scene you want to change.
### 9.14. Bank $0D: Link Animation & OAM Data
**File:** `ALTTP/bank_0D.asm`
**Address Range:** `$0D8000` - `$0DFFFF`
**Summary:** A massive graphical database defining every frame of Link's animation. It is not executable code.
#### Search Heuristics:
* **Link's Animation Sequence:** To modify an animation, find the action in `LinkOAM_AnimationSteps`. The values are indices into the `LinkOAM_PoseData` table, which defines the body parts for each frame.
* **Link's Item Positioning:** To change how Link holds an item, find the animation frame index in `LinkOAM_AnimationSteps` and use it to find the corresponding entries in the `LinkOAM_SwordOffsetX/Y` or `LinkOAM_ShieldOffsetX/Y` tables.
### 9.15. Bank $0E: Tile Properties & Credits Engine
**File:** `ALTTP/bank_0E.asm`
**Address Range:** `$0E8000` - `$0EFFFF`
**Summary:** Contains fundamental game assets (font, tile properties) and the credits engine.
#### Search Heuristics:
* **Tile Behavior (e.g., making a wall walkable):** Identify the tile's graphical ID and find its entry in the `OverworldTileTypes` or `UnderworldTileTypes` tables. Change its byte value to match a tile with the desired properties.
* **Custom Tile Physics (e.g., ice):** Search for the `Underworld_LoadCustomTileTypes` function to see how alternate tile property sets are loaded for specific dungeons.
### 9.16. Bank $0F: Miscellaneous Game Logic & Helpers
**File:** `ALTTP/bank_0F.asm`
**Address Range:** `$0F8000` - `$0FFFFF`
**Summary:** A collection of important miscellaneous subroutines, including player death and dialogue box initiation.
#### Search Heuristics:
* **Player Death Sequence:** The entry points are `PrepareToDie` and `Link_SpinAndDie`.
* **Dialogue Box Trigger:** Search for `JSL Interface_PrepAndDisplayMessage`. The code immediately preceding it sets up the message ID to be displayed.
### 9.17. Bank $10-$18: Graphics Sheets for Link, Dungeon, Overworld, Sprites
### 9.18. Bank $19: Sound Data
### 9.19. Bank $1A: Miscellaneous Sprites & Cutscenes
**File:** `ALTTP/bank_1A.asm`
**Address Range:** `$1A8000` - `$1AFFFF`
**Summary:** Logic for a variety of unique sprites, NPCs, and cutscene events that are too specific for the main sprite engine.
#### Search Heuristics:
* **Pyramid of Power Opening:** Search for `BatCrash` or `CreatePyramidHole`.
* **Waterfall of Wishing Splash:** Search for `SpawnHammerWaterSplash`.
* **Secret Item Substitution:** To understand how items under rocks are sometimes replaced by enemies, analyze `Overworld_SubstituteAlternateSecret`.
### 9.20. Bank $1B: Overworld Interaction & Palettes
**File:** `ALTTP/bank_1B.asm`
**Address Range:** `$1B8000` - `$1BFFFF`
**Summary:** The heart of the overworld interaction system. Manages all entrances, pits, and item-based tile interactions (digging, bombing). Also contains a very large store of palette data.
#### Search Heuristics:
* **Overworld Entrances:** To change where a door leads, find its entry in the `Overworld_Entrance...` tables at the top of the bank.
* **Hidden Item Locations:** To change the item under a specific bush, find the correct `OverworldData_HiddenItems_Screen_XX` table and modify the entry for that bush's coordinates.
* **Sprite/Armor Colors:** To change a color, find the correct palette in the `PaletteData` section and modify the desired color values.
### 9.21. Bank $1C: Text Data
### 9.22. Bank $1D & $1E: Advanced Sprite & Boss AI
**Files:** `ALTTP/bank_1D.asm`, `ALTTP/bank_1E.asm`
**Summary:** These banks contain the specific, complex AI for most of the game's major bosses and late-game enemies (Ganon, Moldorm, Trinexx, Helmasaur King, Kholdstare, Agahnim, etc.).
#### Search Heuristics:
* **Boss/Enemy AI:** To modify a specific boss or advanced enemy, search for its `Sprite_...` routine in these two banks (e.g., `Sprite_92_HelmasaurKing` in bank $1E).
* **Sprite Dispatch Table:** The jump table at `SpriteModule_Active_Bank1E` in `bank_1E.asm` provides a comprehensive list of all sprites managed by that bank and is a good starting point for investigation.
### 9.23. Bank $1F: Dungeon Room Data
## 10. ZScream Expanded ROM Map
> **Last Updated:** 02/28/2025
> **Note:** All addresses are in PC format unless otherwise stated.
> **Note:** Some features are supported in yaze (yet another zelda3 editor) but not all.
ZScream reserves:
- All space up to **1.5MB** (`0x150000`)
- An additional **3 banks** at the end of the 2.0MB range (`0x1E8000` to `0x1FFFFF`)
### Bank Allocation Overview
| Address Range | Size | Purpose/Contents |
|---------------------- |---------- |----------------------------------------------------|
| `0x100000 - 0x107FFF` | 1 Bank | *(Unused?)* |
| `0x108000 - 0x10FFFF` | 1 Bank | Title screen data, Dungeon map data |
| `0x110000 - 0x117FFF` | 1 Bank | Default room header location |
| | | (Old dungeon object data expansion, now moved) |
| `0x118000 - 0x11FFFF` | 1 Bank | (Old dungeon object data expansion, now moved) |
| `0x120000 - 0x127FFF` | 1 Bank | Expanded overlay data |
| `0x128000 - 0x12FFFF` | 1 Bank | Custom collision data |
| `0x130000 - 0x137FFF` | 1 Bank | Overworld map data overflow |
| `0x138000 - 0x13FFFF` | 1 Bank | Expanded dungeon object data |
| `0x140000 - 0x147FFF` | 1 Bank | Custom overworld data |
| `0x148000 - 0x14FFFF` | 1 Bank | Expanded dungeon object data |
| `0x1E0000 - 0x1E7FFF` | 1 Bank | Custom ASM patches |
| `0x1E8000 - 0x1EFFFF` | 1 Bank | Expanded Tile16 space |
| `0x1F0000 - 0x1FFFFF` | 2 Banks | Expanded Tile32 space |
## 11. Oracle of Secrets Specific Guidelines for Gemini
To ensure accurate and consistent modifications within the Oracle of Secrets project, adhere to the following guidelines regarding memory management and code placement:
- **Understand `incsrc` Order:** The order of `incsrc` directives in `Oracle_main.asm` is paramount. It dictates the final ROM layout, especially for code and data placed using `org`. Always consult `Oracle_main.asm` and `Docs/Core/MemoryMap.md` to understand the current memory allocation before introducing new `org` directives.
- **`org` for New Features:** When implementing new features that require significant code or data, use `org $XXXXXX` to place them in an appropriate free bank. Refer to the detailed memory map in `Docs/Core/MemoryMap.md` and the `Oracle_main.asm` comment for available and designated banks. If a new bank is needed, ensure it does not conflict with existing allocations or ZScream reserved space.
- **`pushpc`/`pullpc` for Patches:** For small, targeted modifications to vanilla code or data (e.g., changing a few bytes, hooking a jump), use `pushpc`/`pullpc`. These directives are ideal for non-intrusive patches that don't require a dedicated bank. Examine `Core/patches.asm` and `Util/item_cheat.asm` for examples of this usage.
- **Consult Memory Map:** Before any code modification involving `org` or `pushpc`/`pullpc`, always cross-reference with `Docs/Core/MemoryMap.md` and the `Oracle_main.asm` comment to prevent memory conflicts and ensure proper placement.
- **Prioritize Existing Conventions:** Mimic the existing style and structure of the codebase. If a new feature is similar to an existing one, follow its implementation pattern, including how it manages memory.
- **Avoid Arbitrary `org`:** Never use `org` without a clear understanding of the target address and its implications for the overall ROM layout. Unplanned `org` directives can lead to crashes or unexpected behavior.

51
Docs/General/AsarUsage.md Normal file
View File

@@ -0,0 +1,51 @@
# Asar Usage and ROM Management
This document outlines the best practices for using Asar and managing ROM files within the Oracle of Secrets project.
## Safety First: Preserve the Clean ROM
The most important rule is to **never modify the clean ROM directly**. The clean ROM for this project is expected to be `Roms/oos169.sfc`. All patches must be applied to a *copy* of this file. This ensures that you always have a pristine base to work from and prevents irreversible changes to the original game file.
The `Roms/` directory is ignored by git, so you don't have to worry about accidentally committing large ROM files.
## The Build Script
A `build.sh` script is provided to automate the build process and enforce safe ROM management.
### Usage
To build the ROM, run the script from the project root. You can optionally provide a version number.
**Build with a version number:**
```sh
./build.sh 1.0
```
This will create a patched ROM named `Roms/oos-v1.0.sfc`.
**Build without a version number:**
```sh
./build.sh
```
This will create a patched ROM named `Roms/oos-patched.sfc`.
### What it Does
1. **Copies the ROM**: It creates a copy of `Roms/oos169.sfc`.
2. **Applies the Patch**: It runs `asar` to apply the main patch file (`Oracle_main.asm`) to the newly created ROM copy.
3. **Output**: The final, patched ROM is placed in the `Roms/` directory.
## Manual Build Process (Not Recommended)
If you need to run the build process manually, follow these steps:
1. **Create a copy of the clean ROM**:
```sh
cp Roms/oos169.sfc Roms/my_patched_rom.sfc
```
2. **Run Asar**:
```sh
asar Oracle_main.asm Roms/my_patched_rom.sfc
```
Using the `build.sh` script is highly recommended to avoid mistakes.

View File

@@ -0,0 +1,332 @@
# Oracle of Secrets Development Guidelines
## 1. Introduction
This document outlines the established coding conventions, architectural patterns, and best practices for the Oracle of Secrets project. Adhering to these guidelines is crucial for maintaining code quality, consistency, and long-term maintainability.
The Oracle of Secrets is a large-scale ROM hack of "The Legend of Zelda: A Link to the Past" for the Super Nintendo. It is built using the `asar` assembler and features a highly modular and data-driven architecture. The project's core philosophy is to replace hardcoded vanilla logic with flexible, data-driven systems, allowing for easier expansion and modification.
## 2. Core Architecture
### 2.1. Game State Management
The game's main loop and state management are handled in `bank_00.asm`. The `Module_MainRouting` routine acts as the primary state machine, using a jump table to execute the logic for the current game state (e.g., Overworld, Underworld, Menu).
### 2.2. Memory Management
The project makes extensive use of both WRAM and SRAM to store custom data and game state.
* **WRAM (Work RAM):** Located at `$7E0000`, WRAM holds the game's volatile state. A custom region starting at `$7E0730` is reserved for new features, including the Time System, Mask System, and custom item states.
* **SRAM (Save RAM):** Located at `$7EF000`, SRAM stores the player's save data. The save format has been significantly expanded to accommodate new items, progress flags (`OOSPROG`), and new item data.
### 2.3. Assembly Best Practices (`asar`)
To ensure modern and maintainable assembly code, the project adheres to the following `asar` best practices:
* **`org` for New Code and Data:** Use `org $XXXXXX` to place larger blocks of new code or data into designated free space banks. The `incsrc` order in `Oracle_main.asm` is critical when using `org`, as it directly determines the final ROM layout. Moving `org` blocks or changing the `incsrc` order can lead to memory conflicts or incorrect addressing.
* **`pushpc`/`pullpc` for Targeted Patches:** Use `pushpc`/`pullpc` for small, targeted modifications to existing vanilla code or data. This directive temporarily changes the program counter, allowing a small patch to be inserted at a specific address without affecting the surrounding `org` context. Files like `Core/patches.asm` and `Util/item_cheat.asm` extensively use `pushpc`/`pullpc` for this purpose.
* **Scoping:** Use labels followed by `{}` to define local scopes for new logic blocks. This is the established convention, and `subroutine`/`endsubroutine` are not used.
* **Data Structures:** Use `struct` for complex, related data (e.g., sprite state) and `table` for jump tables or data arrays. This improves readability and reduces errors from manual offset calculations.
* **Constants:** Use `!` or `define()` to create named constants for RAM/SRAM addresses, item IDs, sprite states, and other "magic numbers." This makes the code self-documenting and easier to maintain.
### 2.4. Namespace Architecture
Oracle of Secrets uses a mixed namespace architecture to organize code and manage symbol visibility:
* **`Oracle` Namespace:** Most custom code is placed within the `namespace Oracle { }` block. This includes Items, Menu, Masks, Time System, and most custom features.
* **ZScream (No Namespace):** The `ZSCustomOverworld` system operates outside any namespace, as it needs to hook directly into vanilla bank addresses.
* **Cross-Namespace Calling:** When Oracle code needs to call ZScream functions, or vice versa, proper exports must be defined:
```asm
// In ZScream file:
LoadOverworldSprites_Interupt:
{
; ... ZScream code ...
RTL
}
// Export to Oracle namespace:
namespace Oracle
{
Oracle_LoadOverworldSprites_Interupt = LoadOverworldSprites_Interupt
}
```
* **Bridge Functions:** When ZScream needs to call Oracle code, use a bridge function pattern:
```asm
// Oracle implementation:
namespace Oracle
{
CheckIfNight:
; Main implementation
RTL
}
// Bridge function (no namespace):
ZSO_CheckIfNight:
{
JSL Oracle_CheckIfNight ; Can call INTO Oracle
RTL
}
// Export bridge:
namespace Oracle
{
Oracle_ZSO_CheckIfNight = ZSO_CheckIfNight
}
```
**Important:** Always use the `Oracle_` prefix when calling exported functions from within the Oracle namespace. See `Docs/World/Overworld/ZSCustomOverworldAdvanced.md` Section 5 for detailed examples.
### 2.5. Processor State Management
The 65816 processor has critical flags (M and X) that control register sizes:
* **M Flag (bit 5):** Controls Accumulator size (0=16-bit, 1=8-bit)
* **X Flag (bit 4):** Controls Index register size (0=16-bit, 1=8-bit)
**Best Practices:**
1. **Initialize at function entry:**
```asm
MyFunction:
PHP ; Save caller's state
SEP #$30 ; Set to known 8-bit state
; ... your code ...
PLP ; Restore caller's state
RTL
```
2. **Be explicit about sizes:**
```asm
REP #$20 ; A = 16-bit
LDA.w #$1234 ; Load 16-bit value
SEP #$20 ; A = 8-bit
LDA.b #$12 ; Load 8-bit value only
```
3. **Document function requirements:**
```asm
; Function: CalculateOffset
; Inputs: A=16-bit (offset), X=8-bit (index)
; Outputs: A=16-bit (result)
; Status: Returns with P unchanged (uses PHP/PLP)
```
4. **Cross-namespace calls:** Be especially careful when calling between Oracle and ZScream code, as they may use different processor states.
See `Docs/General/Troubleshooting.md` Section 3 for common processor state issues and solutions.
## 3. Key Custom Systems
Oracle of Secrets introduces several major custom systems that form the foundation of the hack.
### 3.1. `ZSCustomOverworld`
`ZSCustomOverworld` is a data-driven system for managing the overworld. It is configured via a series of tables located at `org $288000` in `Overworld/ZSCustomOverworld.asm`. This system controls:
* **Palettes:** Day/night and seasonal palette transitions.
* **Graphics:** Custom graphics sets for different areas.
* **Overlays:** Data-driven overlays for weather effects and other visual enhancements.
* **Layouts:** Custom tile arrangements and area layouts.
### 3.2. Time System
The Time System, implemented in `Overworld/time_system.asm`, provides a full day/night cycle. It interacts closely with `ZSCustomOverworld` to manage palette changes and other time-of-day effects.
### 3.3. Mask System
The Mask System, located in the `Masks/` directory, allows Link to transform into different forms with unique abilities. The core transformation logic is handled by the `Link_TransformMask` routine. Each mask has its own file (e.g., `deku_mask.asm`, `zora_mask.asm`) that defines its specific behavior and abilities. The system uses custom WRAM to store the current mask and its state.
### 3.4. Custom Menu & HUD
The `Menu/` directory contains a completely new menu and HUD system. This includes:
* A two-page menu for items and quest status.
* A custom item layout and drawing routines.
* A detailed quest status screen with new icons and text.
* A custom HUD with a new magic meter and layout.
### 3.5. Custom Items
The `Items/` directory contains the implementation for all new items, such as the Goldstar, Portal Rod, and Ocarina songs. These items often have complex interactions with the player state machine and other game systems.
### 3.6. Sprite Engine
While the main sprite engine from the original game is still used (`bank_06.asm`), Oracle of Secrets introduces a custom sprite system for managing complex sprite behaviors and interactions. The `Sprites/` directory contains the code for all new custom sprites, including NPCs, enemies, and bosses.
`Core/sprite_functions.asm`, `Core/sprite_macros.asm`, `Core/sprite_new_table.asm` and `Sprites/all_sprites.asm` contain the overriden sprite logic and includes for all custom sprites.
## 4. Coding Standards & Style
### 4.1. File and Directory Structure
The project is organized into a modular directory structure. New code should be placed in the appropriate directory based on its functionality (e.g., new items in `Items/`, new sprites in `Sprites/`).
### 4.2. Naming Conventions
* **Labels:** Use descriptive, CamelCase names for labels (e.g., `LinkState_Swimming`, `Menu_DrawItemName`).
* **Variables:** Use uppercase for constants (`!CONSTANT_NAME`) and CamelCase for RAM/SRAM variables (`VariableName`).
* **Macros:** Use CamelCase for macro names (`%MacroName()`).
### 4.3. Commenting
Comments should be used to explain the *why* behind a piece of code, not the *what*. For complex logic, a brief explanation of the algorithm or purpose is helpful. Avoid excessive or obvious comments.
### 4.4. Macros
Macros are used extensively to simplify common tasks and improve code readability. When creating new macros, follow the existing style and ensure they are well-documented.
## 5. Debugging
### 5.1. Common Issues
* **`BRK` Instructions:** A `BRK` instruction indicates a crash. This is often caused by:
* Jumping to invalid memory (uninitialized ROM, data instead of code)
* P-register mismatch (e.g., calling a 16-bit routine when in 8-bit mode)
* Stack corruption (unbalanced push/pop, JSR/JSL vs RTS/RTL mismatch)
* Return without matching call (RTL executed without previous JSL)
* **P-Register Mismatches:** Always ensure the M and X flags of the processor status register are in the correct state before calling a routine. Use `SEP` and `REP` to switch between 8-bit and 16-bit modes as needed.
* **Stack Corruption:** Ensure push/pop operations are balanced and that JSR is paired with RTS (2 bytes) while JSL is paired with RTL (3 bytes). Never mix them.
* **Namespace Visibility:** If you get "label not found" errors, verify:
* Label is exported with `Oracle_` prefix
* File is included in correct build order in `Oracle_main.asm`
* Namespace block is properly closed
* **Memory Conflicts:** If you get "overwrote some code" warnings:
* Check for overlapping `org` directives
* Use `assert pc() <= $ADDRESS` to protect boundaries
* Review ROM map in Section 6 for available space
**For comprehensive troubleshooting guidance, see `Docs/General/Troubleshooting.md` which covers:**
- BRK crash debugging with emulator tools
- Stack corruption patterns
- Processor status register issues
- Cross-namespace calling problems
- Memory conflicts and bank collisions
- Graphics/DMA timing issues
- ZScream-specific problems
### 5.2. Debugging Tools
* **Mesen-S (Recommended):** The most powerful SNES debugger with:
* Execution breakpoints with conditions
* Memory watchpoints (read/write/execute)
* Stack viewer
* Event viewer (NMI/IRQ timing)
* Live memory updates
* **BSNES-Plus:** Cycle-accurate emulator with:
* Memory editor with search
* Tilemap and VRAM viewers
* Debugger with disassembly
* **`!DEBUG` Flag:** The `!DEBUG` flag in `Util/macros.asm` can be used to enable or disable build-time logging.
* **`%print_debug()` Macro:** This macro can be used to print debug messages and register values during assembly. It is an invaluable tool for tracing code execution and identifying issues.
* **Breadcrumb Tracking:** Add markers to narrow down crash locations:
```asm
LDA.b #$01 : STA.l $7F5000 ; Breadcrumb 1
JSL SuspiciousFunction
LDA.b #$02 : STA.l $7F5000 ; Breadcrumb 2
; After crash, check $7F5000 to see which breadcrumb was reached
```
* **Vanilla Disassembly:** The ALTTP disassembly in `ALTTP/` is the primary reference for the original game's code. Use it to understand the context of vanilla routines that are being hooked or modified.
### 5.3. Debugging Checklist
When encountering an issue:
1. ✅ Check error message carefully - Asar errors are usually precise
2. ✅ Verify namespace - Is label prefixed correctly?
3. ✅ Check stack balance - Equal push/pop counts?
4. ✅ Verify processor state - REP/SEP correct for operation?
5. ✅ Check memory bounds - Assertions in place?
6. ✅ Test in Mesen-S first - Best debugger for SNES
7. ✅ Use breadcrumbs - Narrow down crash location
8. ✅ Check build order - Files included in correct order?
9. ✅ Review recent changes - Compare with known working version
10. ✅ Read vanilla code - Understand what you're hooking
## 6. ROM Map & Memory Layout
Oracle of Secrets utilizes the ZScream expanded ROM map, providing significant additional space for new code and data. The allocation of custom code and data within these banks is managed through `org` directives in the assembly files. The `incsrc` order in `Oracle_main.asm` is crucial, as it dictates the final placement of these blocks in the ROM.
Here is a detailed overview of the custom ROM bank allocations:
| Bank (Hex) | Address Range (PC) | Purpose / Contents | Defining File(s) |
|------------|--------------------|--------------------------------------------------------|---------------------------------------|
| $20 | `$208000` - `$20FFFF` | Expanded Music | `Music/all_music.asm` |
| $21-$27 | | ZScream Reserved | |
| $28 | `$288000` - `$28FFFF` | ZSCustomOverworld data and code | `Overworld/ZSCustomOverworld.asm` |
| $29-$2A | | ZScream Reserved | |
| $2B | `$2B8000` - `$2BFFFF` | Items | `Items/all_items.asm` |
| $2C | `$2C8000` - `$2CFFFF` | Underworld/Dungeons | `Dungeons/dungeons.asm` |
| $2D | `$2D8000` - `$2DFFFF` | Menu | `Menu/menu.asm` |
| $2E | `$2E8000` - `$2EFFFF` | HUD | `Menu/menu.asm` |
| $2F | `$2F8000` - `$2FFFFF` | Expanded Message Bank | `Core/message.asm` |
| $30 | `$308000` - `$30FFFF` | Sprites | `Sprites/all_sprites.asm` |
| $31 | `$318000` - `$31FFFF` | Sprites | `Sprites/all_sprites.asm` |
| $32 | `$328000` - `$32FFFF` | Sprites | `Sprites/all_sprites.asm` |
| $33 | `$338000` - `$33FFFF` | Moosh Form Gfx and Palette | `Masks/all_masks.asm` |
| $34 | `$348000` - `$34FFFF` | Time System, Custom Overworld Overlays, Gfx | `Masks/all_masks.asm` |
| $35 | `$358000` - `$35FFFF` | Deku Link Gfx and Palette | `Masks/all_masks.asm` |
| $36 | `$368000` - `$36FFFF` | Zora Link Gfx and Palette | `Masks/all_masks.asm` |
| $37 | `$378000` - `$37FFFF` | Bunny Link Gfx and Palette | `Masks/all_masks.asm` |
| $38 | `$388000` - `$38FFFF` | Wolf Link Gfx and Palette | `Masks/all_masks.asm` |
| $39 | `$398000` - `$39FFFF` | Minish Link Gfx | `Masks/all_masks.asm` |
| $3A | `$3A8000` - `$3AFFFF` | Mask Routines, Custom Ancillae (Deku Bubble) | `Masks/all_masks.asm` |
| $3B | `$3B8000` - `$3BFFFF` | GBC Link Gfx | `Masks/all_masks.asm` |
| $3C | | Unused | |
| $3D | | ZS Tile16 | |
| $3E | | LW ZS Tile32 | |
| $3F | | DW ZS Tile32 | |
| $40 | `$408000` - `$40FFFF` | LW World Map | `Overworld/overworld.asm` |
| $41 | `$418000` - `$41FFFF` | DW World Map | `Overworld/overworld.asm` |
| Patches | Various | Targeted modifications within vanilla ROM addresses | `Core/patches.asm`, `Util/item_cheat.asm` |
For a more detailed breakdown of the ROM map, refer to the `ZS ROM MAP.txt` file in the `Core/` directory, and `Docs/Core/MemoryMap.md` for a comprehensive overview of all custom memory regions.
---
## 7. Documentation
The following documents have been generated by analyzing the codebase and project files. They serve as key references for understanding the project's architecture and gameplay systems.
* **`Docs/Core/MemoryMap.md`:** A comprehensive map of all custom WRAM and SRAM variables, including repurposed vanilla blocks. See [MemoryMap.md](../Core/MemoryMap.md) for details.
* **`Docs/Core/Ram.md`:** High-level overview of WRAM and SRAM usage with verified custom variables. See [Ram.md](../Core/Ram.md) for details.
* **`Docs/World/Overworld/ZSCustomOverworldAdvanced.md`:** Advanced technical guide for ZScream integration, including hook architecture, sprite loading system, cross-namespace integration, and performance considerations. See [ZSCustomOverworldAdvanced.md](../World/Overworld/ZSCustomOverworldAdvanced.md) for details.
* **`Docs/General/Troubleshooting.md`:** Comprehensive troubleshooting guide covering BRK crashes, stack corruption, processor state issues, namespace problems, memory conflicts, and graphics issues. See [Troubleshooting.md](Troubleshooting.md) for details.
* **`Docs/QuestFlow.md`:** A detailed guide to the main story and side-quest progression, including trigger conditions and progression flags. See [QuestFlow.md](../QuestFlow.md) for details.
* **`Docs/SpriteCreationGuide.md`:** A step-by-step tutorial for creating a new custom sprite using the project's frameworks and conventions. See [SpriteCreationGuide.md](SpriteCreationGuide.md) for details.
* **`Docs/Menu.md`:** A detailed analysis of the custom menu and HUD systems. See [Menu.md](Menu.md) for details.
* **`Docs/Items.md`:** A detailed guide to the custom and modified items in the game. See [Items.md](Items.md) for details.
* **`Docs/Music.md`:** A guide to the custom music tracks and sound effects, including how to add new audio. See [Music.md](Music.md) for details.
* **`Docs/Masks.md`:** A comprehensive overview of the Mask System, including each mask's abilities and implementation details. See [Masks.md](Masks.md) for details.
* **`Docs/Dungeons.md`:** A breakdown of all dungeons, including layouts, enemy placements, and puzzle solutions. See [Dungeons.md](Dungeons.md) for details.
* **`Docs/Overworld.md`:** An analysis of the overworld systems, including `ZSCustomOverworld`, the time system, and other custom features. See [Overworld.md](Overworld.md) for details.
* **`Docs/NPCs.md`:** An analysis of the various NPC sprites. See [NPCs.md](NPCs.md) for details.
* **`Docs/Bosses.md`:** An analysis of the custom boss sprites. See [Bosses.md](Bosses.md) for details.
* **`Docs/Objects.md`:** An analysis of interactive object sprites. See [Objects.md](Objects.md) for details.
* **`Docs/Overlord.md`:** An analysis of the Overlord sprite system. See [Overlord.md](Overlord.md) for details.

File diff suppressed because it is too large Load Diff

177
Docs/Guides/QuestFlow.md Normal file
View File

@@ -0,0 +1,177 @@
# Quest & Event Flow
This document outlines the progression of the main story and major side-quests. It details the flags and conditions that control the game's narrative flow, making it easier to understand how events are triggered.
## 1. Main Quest Progression
*This section provides a step-by-step guide to the main story, detailing the sequence of events, required items, and the flags that are set at each milestone.*
### Chapter 0: A Hero is Born
1. **Trigger:** The game begins.
2. **Events:**
* The Farore intro sequence plays, explaining the backstory of the Triforce and the sealing of the Sacred Realm.
* Link is shipwrecked and awakens in the Eon Abyss.
* The Kydrog intro sequence plays, showing the game's antagonist.
* Link is transported to the Temporal Pyramid.
3. **Player Actions:**
* Navigate the Temporal Pyramid to find the **Moon Pearl**.
* Exit the pyramid to arrive in the Forest of Dreams.
* Obtain the **Lv1 Sword and Shield**.
4. **Progression Flags:**
| Flag | Address | Value/Bit | Notes |
|------------|----------|----------------|-------------------------------------|
| `GameState`| `$7EF3C5`| `0x02` | Set after the Farore intro. |
| `OosProg2` | `$7EF3C6`| `bit $04` set | Set after the Kydrog intro. |
### Chapter 1: The Maku Tree Awakens
1. **Trigger:** Player talks to the Maku Tree for the first time (`Sprites/NPCs/maku_tree.asm`).
2. **Events:** The Maku Tree speaks to Link, explaining the plight of the land.
3. **Reward:** A Heart Container is given to the player (`Link_ReceiveItem` with Y=`$3E`).
4. **Progression Flags & Consequences:**
| Flag | Address | Value/Bit | Consequence |
|-----------------|----------|---------------|--------------------------------------------------------------------------|
| `MakuTreeQuest` | `$7EF3D4`| `0x01` | The Maku Tree will now use a different dialogue branch on subsequent talks. |
| `MapIcon` | `$7EF3C7`| `0x01` | A red 'X' appears on the map over the Mushroom Grotto. |
| `OOSPROG` | `$7EF3D6`| `bit $02` set | A major story flag indicating the quest has officially begun. |
### Chapter 2: The Mushroom Grotto (D1)
1. **Trigger:** Player enters the Mushroom Grotto, west of Wayward Village.
2. **Events:** Player navigates the dungeon, facing the Vampire Bat miniboss and the Mothra boss.
3. **Reward:** **Bow**.
### Chapter 3: The Tail Palace (D2)
1. **Trigger:** Player enters Tail Palace.
2. **Events:** Player defeats the boss, a vanilla-style Big Moldorm.
3. **Reward:** **Roc's Feather**.
4. **World State Changes:** After completion, Deku NPCs will appear in the overworld area near Tail Palace.
### Chapter 4: The Path to the Castle
1. **Trigger:** This is a multi-part quest chain required to access Kalyxo Castle.
2. **Player Actions:**
* **Ocarina:** Complete the "Lost Ranch Girl" side-quest to obtain the Ocarina.
* **Song of Healing:** Learn the Song of Healing from the Happy Mask Salesman.
* **Running Boots:** Play the Song of Healing for the sick child in Wayward Village to receive the Running Boots.
* **Book of Secrets:** Use the Running Boots to get the Book of Secrets from the village library.
3. **Consequence:** The Book of Secrets is required to open the gates to Kalyxo Castle.
### Chapter 5: Kalyxo Castle (D3)
1. **Trigger:** Player enters Kalyxo Castle.
2. **Required Items:** Book of Secrets.
3. **Events:** Player defeats the Armos Knights boss.
4. **Reward:** **Meadow Blade (Lv2 Sword)**.
### Chapter 6: The Shrine of Wisdom (S1)
1. **Trigger:** Player enters the Shrine of Wisdom.
2. **Events:** Player must navigate a swampy overworld area.
3. **Reward:** **Zora Flippers**.
### Chapter 7: Zora Temple (D4)
1. **Trigger:** Player enters the Zora Temple.
2. **Required Items:** Zora Flippers.
3. **Events:** Player defeats an advanced variant of the Arrghus boss.
4. **Reward:** **Hookshot**, **Zora Mask** (via side-quest within the dungeon).
### Chapter 8: Glacia Estate (D5)
1. **Trigger:** Player enters Glacia Estate.
2. **Required Items:** **Goldstar** (from the "Old Man Mountain Quest").
3. **Events:** Player navigates ice puzzles and defeats the Twinrova boss.
4. **Reward:** **Fire Rod**.
### Chapter 9: The Shrine of Power (S2)
1. **Trigger:** Player enters the Shrine of Power.
2. **Reward:** **Power Glove**.
### Chapter 10: Goron Mines (D6)
1. **Trigger:** Player enters the Goron Mines.
2. **Required Items:** **Power Glove**, Completion of the "Goron Mines Quest".
3. **Events:** Player defeats the Lanmolas and the King Dodongo (Helmasaur variant) boss.
4. **Reward:** **Hammer**.
### Chapter 11: Dragon Ship (D7)
1. **Trigger:** Player enters the Dragon Ship.
2. **Reward:** **Somaria Rod**.
### Chapter 12: The Shrine of Courage (S3)
1. **Trigger:** Player enters the Shrine of Courage.
2. **Events:** Player defeats the boss Vaati (Vitreous variant).
3. **Reward:** **Mirror Shield**.
### Chapter 13: Fortress of Secrets (D8)
1. **Trigger:** Player enters the Fortress of Secrets.
2. **Events:** Player defeats Dark Link.
3. **Reward:** **Portal Rod**.
### Chapter 14: The Eon Core (Endgame)
1. **Trigger:** Player enters the final dungeon.
2. **Events:** Player faces the final bosses: Kydreeok and Ganon.
3. **Reward:** **The Triforce**.
---
## 2. Major Side-Quests
### The Lost Ranch Girl (Ocarina Quest)
1. **Mushroom:** Get a Mushroom from the old woman's house in the Mushroom Grotto area.
2. **Magic Powder:** Trade the Mushroom to the Potion Shop owner. Leave the area and return later to receive the Magic Powder.
3. **Ocarina:** Use the Magic Powder on the sleeping Cucco in the Ranch House. This wakes it up and it gives you the Ocarina.
### The Mask Salesman
1. **Trigger:** Player must have the Ocarina.
2. **Action:** Talk to the Happy Mask Salesman.
3. **Reward:** He teaches Link the **Song of Healing**.
### The Zora Mask
1. **Trigger:** Player talks to the Zora Princess in the Zora Temple. She gives message `$0C5`.
2. **Action:** Player must play the Song of Healing.
3. **Reward:** The princess gives the player the **Zora Mask**.
4. **Flag:** `ZoraMask` (`$7EF347`) is set in SRAM.
### The Wolf Mask
1. **Trigger:** A Wolfos sprite appears outside Kalyxo Castle at night.
2. **Action:** Player must defeat the Wolfos and then play the Song of Healing.
3. **Reward:** **Wolf Mask**.
### Old Man Mountain Quest
1. **Trigger:** Player takes the warp portal at the northwest point of Mount Snowpeak.
2. **Action:** Enter the Lava Lands cave to find an Old Man sprite. Escort him to a rock formation and use the Magic Mirror.
3. **Reward:** **Goldstar** (upgrade for the Hookshot).
### Goron Mines Quest
1. **Trigger:** Player needs to open the Goron Mines.
2. **Required Item:** Power Glove.
3. **Action:**
* Collect five pieces of Goron Rock Meat from Lupo Mountain.
* Give the five pieces to the Kalyxian Goron NPC in the desert.
4. **Consequence:** The Goron NPC opens the entrance to the Goron Mines.
### The Magic Bean
1. **Purchase:** Player buys the Magic Bean from the Bean Vendor for 100 rupees. Requires an empty bottle.
2. **Planting:** Player takes the bean to the fertile soil patch on the ranch. `MagicBeanProg` (`$7EF39B`) has bit `$01` set.
3. **Watering:** Player plays the Song of Storms. `MagicBeanProg` has bit `$04` set.
4. **Pollination:** Player must release a Good Bee from a bottle near the bean sprout. `MagicBeanProg` has bit `$02` set.
5. **Growth:** After 3 in-game day/night cycles, the beanstalk grows into a large flower.
6. **Reward:** The player can ride the flower to a Heart Container. `MagicBeanProg` has bit `$40` set upon completion.

View File

@@ -0,0 +1,877 @@
# Sprite Creation Guide
This guide provides a step-by-step walkthrough for creating a new custom sprite in Oracle of Secrets using the project's modern sprite system.
## 1. File Setup
1. **Create the Sprite File:** Create a new `.asm` file for your sprite in the appropriate subdirectory of `Sprites/`:
* `Sprites/Enemies/` - For enemy sprites
* `Sprites/Bosses/` - For boss sprites
* `Sprites/NPCs/` - For non-playable character sprites
* `Sprites/Objects/` - For interactive objects and items
2. **Include the File:** Open `Sprites/all_sprites.asm` and add an `incsrc` directive to include your new file. The file must be placed in the correct bank section:
* **Bank 30** (`$308000`) - First bank, includes `sprite_new_table.asm` and some core sprites
* **Bank 31** (`$318000`) - Second bank, includes `sprite_functions.asm` and more sprites
* **Bank 32** (`$328000`) - Third bank for additional sprites
* **Bank 2C** (Dungeon bank) - For sprites that are part of dungeon-specific content
Example:
```asm
; In Sprites/all_sprites.asm
org $318000 ; Bank 31
%log_start("my_new_enemy", !LOG_SPRITES)
incsrc "Sprites/Enemies/MyNewEnemy.asm"
%log_end("my_new_enemy", !LOG_SPRITES)
```
3. **Assign a Sprite ID:** Choose an unused sprite ID for your sprite. You can either:
* Use a completely new ID (e.g., `$A0` through `$FF` range)
* Override a vanilla sprite ID (for replacing existing sprites)
* Share an ID with another sprite and use `SprSubtype` to differentiate behaviors
## 2. Sprite Properties
At the top of your new sprite file, define its core properties using the provided template. These `!` constants are used by the `%Set_Sprite_Properties` macro to automatically configure the sprite's behavior and integrate it into the game.
```asm
; =========================================================
; Sprite Properties
; =========================================================
!SPRID = $XX ; CHOOSE AN UNUSED SPRITE ID or use a constant like Sprite_MyNewEnemy
!NbrTiles = 02 ; Number of 8x8 tiles used in the largest frame
!Harmless = 00 ; 00 = Harmful, 01 = Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = 10 ; Number of Health the sprite has
!Damage = 04 ; Damage dealt to Link on contact (08 = whole heart, 04 = half heart)
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attacked, 01 = all attacks clink harmlessly
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 00 ; Unused in this template (can be 0 to 7)
!Hitbox = 08 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = sprite continues to live offscreen
!Statis = 00 ; 00 = sprite is alive? (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layers for collision
!CanFall = 00 ; 01 = sprite can fall in holes, 00 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk in shallow water
!Blockable = 00 ; 01 = can be blocked by Link's shield
!Prize = 01 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is a statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
; This macro MUST be called after the properties
%Set_Sprite_Properties(Sprite_MyNewEnemy_Prep, Sprite_MyNewEnemy_Long)
```
### Property Details
**Memory Mapping:** The `%Set_Sprite_Properties` macro writes these properties to specific ROM addresses:
* `$0DB080+!SPRID` - OAM/Harmless/HVelocity/NbrTiles
* `$0DB173+!SPRID` - Sprite HP
* `$0DB266+!SPRID` - Sprite Damage
* `$0DB359+!SPRID` - Death Animation/Impervious/Shadow/Palette flags
* `$0DB44C+!SPRID` - Collision Layer/Statis/Persist/Hitbox
* `$0DB53F+!SPRID` - DeflectArrow/Boss/CanFall flags
* `$0DB632+!SPRID` - Interaction/WaterSprite/Blockable/Sound/Prize
* `$0DB725+!SPRID` - Statue/DeflectProjectiles/Impervious flags
The macro also sets up the jump table entries at:
* `$069283+(!SPRID*2)` - Vanilla Sprite Main Pointer
* `$06865B+(!SPRID*2)` - Vanilla Sprite Prep Pointer
* `NewSprRoutinesLong+(!SPRID*3)` - New Long Sprite Pointer
* `NewSprPrepRoutinesLong+(!SPRID*3)` - New Long Sprite Prep Pointer
### Design Considerations
* **Multi-purpose Sprite IDs:** A single `!SPRID` can be used for multiple distinct behaviors (e.g., Keese, Fire Keese, Ice Keese, Vampire Bat all share sprite IDs) through the use of `SprSubtype`. This is a powerful technique for reusing sprite slots and creating variations of enemies.
* **Damage Handling for Bosses:** For boss sprites, `!Damage = 00` is common if damage is applied through other means, such as spawned projectiles or direct contact logic within the main routine.
* **Dynamic Health:** Many sprites set health dynamically in their `_Prep` routine based on game progression (e.g., Booki sets health based on Link's sword level, Darknut based on sword upgrades).
* **Custom Boss Logic:** Setting `!Boss = 00` for a boss sprite indicates that custom boss logic is being used, rather than relying on vanilla boss flags.
* **Shared Sprite IDs:** Multiple distinct NPCs or objects can share a single `!SPRID` by using `SprSubtype` for differentiation (e.g., `Sprite_Mermaid = $F0` is used for Mermaid, Maple, and Librarian with different subtypes).
## 3. Main Structure (`_Long` routine)
This is the main entry point for your sprite, called by the game engine every frame. Its primary job is to call the drawing and logic routines.
```asm
Sprite_MyNewEnemy_Long:
{
PHB : PHK : PLB ; Set up bank registers (Push Bank, Push K, Pull Bank)
JSR Sprite_MyNewEnemy_Draw
JSL Sprite_DrawShadow ; Optional: Draw a shadow (use appropriate shadow function)
JSL Sprite_CheckActive : BCC .SpriteIsNotActive ; Only run logic if active
JSR Sprite_MyNewEnemy_Main
.SpriteIsNotActive
PLB ; Restore bank register
RTL ; Return from long routine
}
```
### Important Notes
* **Bank Register Management:** Always use `PHB : PHK : PLB` at the start and `PLB` before `RTL` to ensure proper bank context.
* **Sprite_CheckActive:** This critical function checks if the sprite should execute logic based on its state, freeze status, and pause flags. Returns carry set if active.
* **Drawing Order:** Drawing is typically done before the main logic, though the order can vary based on sprite needs.
* **Conditional Drawing:** Shadow drawing might be conditional based on the sprite's current action or state (e.g., Thunder Ghost only draws shadow when grounded).
## 4. Initialization (`_Prep` routine)
This routine runs *once* when the sprite is first spawned. Use it to set initial values for timers, its action state, and any other properties. For dynamic difficulty scaling, you can adjust properties based on game progression here.
```asm
Sprite_MyNewEnemy_Prep:
{
PHB : PHK : PLB
; Set dynamic health based on sword level (optional)
LDA.l Sword : DEC A : TAY
LDA.w .health, Y : STA.w SprHealth, X
%GotoAction(0) ; Set the initial state to the first one in the jump table
%SetTimerA(120) ; Set a general-purpose timer to 120 frames (2 seconds)
PLB
RTL
; Optional: Dynamic health table
.health
db $04, $08, $10, $18 ; Health values for sword levels 1-4
}
```
### Available Sprite RAM Variables
The following WRAM addresses are available for sprite-specific data (all indexed by X):
**Position & Movement:**
* `SprY, SprX` ($0D00, $0D10) - 8-bit position coordinates (low byte)
* `SprYH, SprXH` ($0D20, $0D30) - High bytes of position
* `SprYSpeed, SprXSpeed` ($0D40, $0D50) - Movement velocities
* `SprYRound, SprXRound` ($0D60, $0D70) - Sub-pixel precision
* `SprCachedX, SprCachedY` ($0FD8, $0FDA) - Cached coordinates
**Animation & Graphics:**
* `SprAction` ($0D80) - Current state in state machine
* `SprFrame` ($0D90) - Current animation frame index
* `SprGfx` ($0DC0) - Graphics offset for drawing
* `SprFlash` ($0B89) - Flash color for damage indication
**Timers:**
* `SprTimerA-F` ($0DF0, $0E00, $0E10, $0EE0, $0F10, $0F80) - Six general-purpose timers
* Note: `SprTimerF` decreases by 2 each frame (used for gravity)
**Miscellaneous Data:**
* `SprMiscA-G` ($0DA0, $0DB0, $0DE0, $0E90, $0EB0, $0EC0, $0ED0) - Seven general-purpose variables
* `SprCustom` ($1CC0) - Additional custom data storage
**State & Properties:**
* `SprState` ($0DD0) - Sprite state (0x00=dead, 0x08=spawning, 0x09=active, etc.)
* `SprType` ($0E20) - Sprite ID
* `SprSubtype` ($0E30) - Sprite subtype for variations
* `SprHealth` ($0E50) - Current health
* `SprNbrOAM` ($0E40) - Number of OAM slots + flags
* `SprFloor` ($0F20) - Layer (0=top, 1=bottom)
* `SprHeight` ($0F80) - Z-position for altitude/jumping
### Common Initialization Patterns
```asm
; Set sprite to be impervious initially (e.g., for a boss with phases)
LDA.b #$80 : STA.w SprDefl, X
; Configure tile collision behavior
LDA.b #%01100000 : STA.w SprTileDie, X
; Set bump damage type
LDA.b #$09 : STA.w SprBump, X
; Initialize custom variables
STZ.w SprMiscA, X
STZ.w SprMiscB, X
```
## 5. Main Logic & State Machine (`_Main` routine)
This is the heart of your sprite. Use the `%SpriteJumpTable` macro to create a state machine. The sprite's current state is stored in `SprAction, X`.
```asm
Sprite_MyNewEnemy_Main:
{
%SpriteJumpTable(State_Idle, State_Attacking, State_Hurt)
State_Idle:
{
%PlayAnimation(0, 1, 15) ; Animate between frames 0 and 1 every 15 game frames
; Check distance to player. If less than 80 pixels, switch to attacking state.
JSL GetDistance8bit_Long : CMP.b #$50 : BCS .player_is_far
%GotoAction(1) ; Switch to State_Attacking
.player_is_far
RTS
}
State_Attacking:
{
%PlayAnimation(2, 3, 8)
%MoveTowardPlayer(12) ; Move toward the player with speed 12
%DoDamageToPlayerSameLayerOnContact()
; Check if the player has hit the sprite
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
%GotoAction(2) ; Switch to State_Hurt
.no_damage
RTS
}
State_Hurt:
{
; Sprite was hit, flash and get knocked back
JSL Sprite_DamageFlash_Long
JSL Sprite_CheckIfRecoiling
; Return to attacking after recoil
LDA.w SprRecoil, X : BNE .still_recoiling
%GotoAction(1)
.still_recoiling
RTS
}
}
```
### Available Macros
**State Management:**
* `%GotoAction(action)` - Set `SprAction` to switch states
* `%SpriteJumpTable(state1, state2, ...)` - Create state machine jump table
* `%JumpTable(index, state1, state2, ...)` - Jump table with custom index
**Animation:**
* `%PlayAnimation(start, end, speed)` - Animate frames (uses `SprTimerB`)
* `%PlayAnimBackwards(start, end, speed)` - Animate in reverse
* `%StartOnFrame(frame)` - Ensure animation starts at a minimum frame
* `%SetFrame(frame)` - Directly set animation frame
**Movement:**
* `%MoveTowardPlayer(speed)` - Apply speed toward player and move
* `%SetSpriteSpeedX(speed)` - Set horizontal velocity
* `%SetSpriteSpeedY(speed)` - Set vertical velocity
**Timers:**
* `%SetTimerA-F(length)` - Set timer values
**Player Interaction:**
* `%DoDamageToPlayerSameLayerOnContact()` - Damage on contact (same layer only)
* `%PlayerCantPassThrough()` - Prevent Link from passing through sprite
* `%ShowSolicitedMessage(id)` - Show message when player presses A
* `%ShowMessageOnContact(id)` - Show message on contact
* `%ShowUnconditionalMessage(id)` - Show message immediately
**Sprite Properties:**
* `%SetHarmless(value)` - 0=harmful, 1=harmless
* `%SetImpervious(value)` - Toggle invulnerability
* `%SetRoomFlag(value)` - Set room completion flag
**Audio:**
* `%PlaySFX1(id)`, `%PlaySFX2(id)` - Play sound effect
* `%PlayMusic(id)` - Change background music
* `%ErrorBeep()` - Play error sound
**Utility:**
* `%ProbCheck(mask, label)` - Random check, branch if result is non-zero
* `%ProbCheck2(mask, label)` - Random check, branch if result is zero
* `%SetupDistanceFromSprite()` - Setup distance calculation
### Common Functions
**Movement & Physics:**
* `Sprite_Move` / `Sprite_MoveLong` - Apply velocity to position
* `Sprite_MoveHoriz` / `Sprite_MoveVert` - Move in one axis
* `Sprite_BounceFromTileCollision` - Bounce off walls
* `Sprite_CheckTileCollision` - Check for tile collision
* `Sprite_ApplySpeedTowardsPlayer` - Calculate speed toward player
* `Sprite_FloatTowardPlayer` - Float toward player with altitude
* `Sprite_FloatAwayFromPlayer` - Float away from player
* `Sprite_InvertSpeed_X` / `Sprite_InvertSpeed_Y` - Reverse velocity
**Combat:**
* `Sprite_CheckDamageFromPlayer` - Check if player attacked sprite
* `Sprite_CheckDamageToPlayer` - Check if sprite damaged player
* `Sprite_DamageFlash_Long` - Flash sprite when damaged
* `Sprite_CheckIfRecoiling` - Handle knockback after being hit
* `Guard_ParrySwordAttacks` - Parry sword attacks (like Darknut)
**Spawning:**
* `Sprite_SpawnDynamically` - Spawn a new sprite
* `Sprite_SpawnProbeAlways_long` - Spawn probe projectile
* `Sprite_SpawnSparkleGarnish` - Spawn sparkle effect
**Distance & Direction:**
* `GetDistance8bit_Long` - Get 8-bit distance to player
* `Sprite_DirectionToFacePlayer` - Get direction to face player
* `Sprite_IsToRightOfPlayer` - Check if sprite is to right of player
**Randomness:**
* `GetRandomInt` - Get random 8-bit value
### Code Style Guidelines
* **Named Constants:** Always use named constants for magic numbers:
```asm
GoriyaMovementSpeed = 10
LDA.b #GoriyaMovementSpeed : STA.w SprXSpeed, X
```
* **Processor Status Flags:** Explicitly manage 8-bit/16-bit mode with `REP #$20` (16-bit) and `SEP #$20` (8-bit), especially during OAM calculations
* **State Machine Pattern:** Use `SprAction` with `%SpriteJumpTable` for clear state management
* **Timer Usage:** Use dedicated timers for different purposes (e.g., `SprTimerA` for state changes, `SprTimerB` for animation, `SprTimerC` for cooldowns)
## 6. Drawing (`_Draw` routine)
This routine renders your sprite's graphics. The project provides the `%DrawSprite()` macro for standard drawing, which reads from data tables you define.
### Standard Drawing with %DrawSprite()
```asm
Sprite_MyNewEnemy_Draw:
{
JSL Sprite_PrepOamCoord ; Prepare OAM coordinates
JSL Sprite_OAM_AllocateDeferToPlayer ; Allocate OAM slots
%DrawSprite()
; --- OAM Data Tables ---
.start_index ; Starting index in the tables for each animation frame
db $00, $02, $04, $06
.nbr_of_tiles ; Number of tiles to draw for each frame (actual count minus 1)
db 1, 1, 1, 1
.x_offsets ; X-position offset for each tile (16-bit values)
dw -8, 8, -8, 8, -8, 8, -8, 8
.y_offsets ; Y-position offset for each tile (16-bit values)
dw -8, -8, -8, -8, -8, -8, -8, -8
.chr ; The character (tile) number from the graphics sheet
db $C0, $C2, $C4, $C6, $C8, $CA, $CC, $CE
.properties ; OAM properties (palette, priority, flips)
db $3B, $7B, $3B, $7B, $3B, $7B, $3B, $7B
.sizes ; Size of each tile (e.g., $02 for 16x16)
db $02, $02, $02, $02, $02, $02, $02, $02
}
```
### OAM Property Byte Format
The `.properties` byte contains flags for each tile:
* Bits 0-2: Palette (0-7)
* Bit 3: Priority (0=front, 1=behind BG)
* Bit 4: Unused
* Bit 5: Horizontal flip
* Bit 6: Vertical flip
* Bit 7: Unused
Example values:
* `$39` = Palette 1, no flip, front priority
* `$79` = Palette 1, horizontal flip, front priority
* `$B9` = Palette 1, vertical flip, front priority
### Custom Drawing Logic
For complex drawing needs (multi-part sprites, dynamic flipping, etc.), implement custom drawing:
```asm
Sprite_MyNewEnemy_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprGfx, X : CLC : ADC.w SprFrame, X : TAY ; Get animation frame
LDA.w .start_index, Y : STA $06 ; Get starting index
LDA.w SprFlash, X : STA $08 ; Store flash value
LDA.w SprMiscC, X : STA $09 ; Store direction for flipping
PHX
LDX .nbr_of_tiles, Y ; Load number of tiles minus 1
LDY.b #$00 ; OAM buffer index
.nextTile
PHX ; Save tile index
TXA : CLC : ADC $06 : PHA ; Calculate absolute tile index
ASL A : TAX ; Multiply by 2 for 16-bit offsets
REP #$20 ; 16-bit accumulator
LDA $00 : CLC : ADC .x_offsets, X : STA ($90), Y ; Write X position
AND.w #$0100 : STA $0E ; Store X high bit
INY
LDA $02 : CLC : ADC .y_offsets, X : STA ($90), Y ; Write Y position
CLC : ADC #$0010 : CMP.w #$0100 ; Check if on screen
SEP #$20 ; Back to 8-bit
BCC .on_screen_y
LDA.b #$F0 : STA ($90), Y : STA $0E ; Move offscreen
.on_screen_y
PLX ; Restore absolute tile index
INY
LDA .chr, X : STA ($90), Y ; Write character
INY
; Apply horizontal flip based on direction
LDA.b $09 : BEQ .no_flip
LDA.b #$79 : JMP .write_prop
.no_flip
LDA .properties, X
.write_prop
ORA $08 : STA ($90), Y ; Write properties with flash
PHY
TYA : LSR #2 : TAY
LDA .sizes, X : ORA $0F : STA ($92), Y ; Write size
PLY : INY
PLX : DEX : BPL .nextTile
PLX
RTS
; Data tables follow...
}
```
### Important Drawing Notes
* **16-bit Calculations:** Always use `REP #$20` before 16-bit position calculations and `SEP #$20` afterward
* **OAM Allocation:** Different allocation functions for different scenarios:
* `Sprite_OAM_AllocateDeferToPlayer` - Standard allocation
* `OAM_AllocateFromRegionE` - For large sprites (bosses)
* `Sprite_OAM_AllocateDeferToPlayerLong` - Long version
* **Shadow Drawing:** Call `Sprite_DrawShadow` in the `_Long` routine, not in `_Draw`
* **Multi-Layer Drawing:** For objects like minecarts that Link can be "inside", draw in multiple parts from different OAM regions to create depth
* **Conditional Drawing:** Some sprites (like followers or bosses) dispatch to different draw routines based on `SprSubtype` or current state
## 7. Final Integration
The `%Set_Sprite_Properties()` macro you added in Step 2 handles the final integration automatically. It:
1. Writes your sprite properties to the appropriate ROM addresses
2. Sets up pointers in the vanilla sprite jump tables
3. Adds your `_Prep` and `_Long` routines to the new sprite table in `Core/sprite_new_table.asm`
Your sprite is now ready to be placed in the game world using your level editor!
## 8. Testing Your Sprite
1. **Build the ROM:** Run your build script (`build.sh` or `build.bat`)
2. **Place in Editor:** Use your level editor to place the sprite in a room
3. **Test Behavior:** Load the room and verify:
* Sprite spawns correctly
* Animation plays as expected
* Movement works properly
* Collision detection functions
* Damage and health mechanics work
* State transitions occur correctly
## 9. Common Issues and Solutions
### Sprite Doesn't Appear
* Check that the sprite ID is not already in use
* Verify the `incsrc` directive is in the correct bank
* Ensure `%Set_Sprite_Properties` is called after property definitions
* Check that the sprite is being placed in a compatible room type
### Graphics are Corrupted
* Verify 16-bit mode (`REP #$20`) is used for OAM calculations
* Check that `.start_index`, `.nbr_of_tiles`, and data tables are correctly sized
* Ensure `.sizes` table uses correct values ($00=8x8, $02=16x16)
* Verify character numbers (`.chr`) match your graphics sheet
### Sprite Behaves Incorrectly
* Check that timers are being set and checked correctly
* Verify state transitions in the jump table
* Ensure `Sprite_CheckActive` is called before main logic
* Check that collision functions are being called in the right order
### Performance Issues
* Reduce `!NbrTiles` if using too many tiles
* Optimize drawing routine (avoid redundant calculations)
* Use simpler collision detection where possible
* Consider using `!Persist = 00` for non-critical sprites
## 10. Advanced Sprite Design Patterns
## 10. Advanced Sprite Design Patterns
### 10.1. Multi-Part Sprites and Child Sprites
For complex bosses or entities, break them down into a main parent sprite and multiple child sprites. Examples include Kydreeok (body + heads), Darknut (knight + probes), Goriya (enemy + boomerang), and Helmet Chuchu (body + detachable helmet).
**Parent Sprite Responsibilities:**
* Spawns and manages child sprites using `Sprite_SpawnDynamically`
* Stores child sprite IDs in global variables or `SprMisc` slots
* Monitors child sprite states to determine phases or defeat conditions
* Handles overall movement, phase transitions, and global effects
**Child Sprite Responsibilities:**
* Handles independent logic, movement, and attacks
* May be positioned relative to parent sprite
* Uses `SprSubtype` to differentiate between multiple instances
**Example: Kydreeok Boss**
```asm
; In Kydreeok body sprite
SpawnLeftHead:
{
LDA #$CF ; Kydreeok Head sprite ID
JSL Sprite_SpawnDynamically : BMI .return
TYA : STA.w Offspring1_Id ; Store child ID globally
LDA.b #$00 : STA.w SprSubtype, Y ; Subtype 0 = left head
; Position relative to parent
REP #$20
LDA.w SprCachedX : SEC : SBC.w #$0010
SEP #$20
STA.w SprX, Y : XBA : STA.w SprXH, Y
; ... more initialization
.return
RTS
}
; Check if all heads are defeated
Sprite_Kydreeok_CheckIfDead:
{
LDA.w Offspring1_Id : TAY
LDA.w SprState, Y : BNE .not_dead ; Check if left head alive
LDA.w Offspring2_Id : TAY
LDA.w SprState, Y : BNE .not_dead ; Check if right head alive
; All heads defeated - trigger death sequence
.not_dead
RTS
}
```
**Shared Sprite IDs for Variations:**
A single sprite ID can represent different enemy types using `SprSubtype`:
* Keese sprite ID shared by: Regular Keese, Fire Keese, Ice Keese, Vampire Bat
* Mermaid sprite ID ($F0) shared by: Mermaid, Maple, Librarian (all using different subtypes)
* This efficiently reuses sprite slots and base logic
### 10.2. Quest Integration and Dynamic Progression
Boss fights and NPC interactions can be deeply integrated with quest progression using SRAM flags, dynamic health management, and multi-phase battles.
**Phase Transitions:**
Trigger new phases based on health thresholds, timers, or child sprite states:
```asm
; Check health threshold for phase change
LDA.w SprHealth, X : CMP.b #$10 : BCS .phase_one
LDA.w SprMiscD, X : CMP.b #$02 : BEQ .already_phase_two
LDA.b #$02 : STA.w SprMiscD, X ; Switch to phase 2
JSR LoadPhase2Graphics
JSR SpawnPhase2Adds
.already_phase_two
.phase_one
```
**Health Management:**
* **Direct Health:** Use `SprHealth` for straightforward health tracking
* **Indirect Health:** Base defeat on child sprite states (e.g., Kydreeok defeated when all heads are killed)
* **Phase-Based Health:** Refill health between phases for extended boss fights
* **Dynamic Scaling:** Adjust health based on Link's sword level or progression
**Quest Integration Examples:**
* **Wolfos:** After being subdued, plays Song of Healing animation and grants Wolf Mask
* **Bug Net Kid:** Dialogue changes based on whether Link has the Bug Net
* **Maple:** Spawns items and interacts with Link differently based on quest flags
* **Mask Salesman:** Complex shop system with inventory checks and rupee deduction
* **Zora Princess:** Quest rewards and dialogue conditional on SRAM flags
**SRAM Flag Usage:**
```asm
; Check if quest item has been obtained
LDA.l $7EF3XX : CMP.b #$XX : BNE .not_obtained
; Quest item obtained - change behavior
%ShowUnconditionalMessage(MessageID)
JMP .quest_complete
.not_obtained
```
### 10.4. Code Reusability and Best Practices
**Shared Logic Functions:**
Create reusable functions for common behaviors across multiple sprites:
```asm
; Shared by Goriya and Darknut
Goriya_HandleTileCollision:
{
JSL Sprite_CheckTileCollision
LDA.w SprCollision, X : BEQ .no_collision
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X
STA.w SprMiscE, X
%SetTimerC(60)
.no_collision
RTS
}
```
**Named Constants:**
Always use named constants instead of magic numbers:
```asm
; Good
GoriyaMovementSpeed = 10
MinecartSpeed = 20
DoubleSpeed = 30
LDA.b #GoriyaMovementSpeed : STA.w SprXSpeed, X
; Bad
LDA.b #10 : STA.w SprXSpeed, X ; What does 10 mean?
```
**Processor Status Management:**
Explicitly manage 8-bit/16-bit modes:
```asm
REP #$20 ; 16-bit accumulator
LDA $00 : CLC : ADC .x_offsets, X : STA ($90), Y
SEP #$20 ; Back to 8-bit
```
**State Machine Pattern:**
Use `SprAction` with jump tables for clear state management:
```asm
Sprite_Enemy_Main:
{
%SpriteJumpTable(State_Idle, State_Chase, State_Attack, State_Retreat)
State_Idle: { /* ... */ RTS }
State_Chase: { /* ... */ RTS }
State_Attack: { /* ... */ RTS }
State_Retreat: { /* ... */ RTS }
}
```
**Timer Management:**
Use different timers for different purposes:
* `SprTimerA` - State transitions, cooldowns
* `SprTimerB` - Animation (automatically used by `%PlayAnimation`)
* `SprTimerC` - Movement changes, direction changes
* `SprTimerD` - Attack cooldowns
* `SprTimerE` - Special effects
* `SprTimerF` - Gravity/altitude (decrements by 2)
### 10.5. Centralized Handlers and Multi-Purpose Sprites
Many sprite files serve as central handlers for multiple distinct entities, using conditional logic to dispatch behaviors.
**Examples:**
* **Followers** (`followers.asm`) - Zora Baby, Old Man, Kiki
* **Mermaid** (`mermaid.asm`) - Mermaid (subtype 0), Maple (subtype 1), Librarian (subtype 2)
* **Zora** (`zora.asm`) - Various Zora NPCs with different roles
* **Collectible** (`collectible.asm`) - Different collectible items
* **Deku Leaf** (`deku_leaf.asm`) - Deku Leaf and Beach Whirlpool
**Implementation Pattern:**
```asm
Sprite_MultiPurpose_Long:
{
PHB : PHK : PLB
; Dispatch based on subtype
LDA.w SprSubtype, X
JSL JumpTableLocal
dw Type0_Routine
dw Type1_Routine
dw Type2_Routine
Type0_Routine:
JSR Type0_Draw
JSR Type0_Main
PLB : RTL
Type1_Routine:
JSR Type1_Draw
JSR Type1_Main
PLB : RTL
}
```
### 10.6. Overriding Vanilla Sprites
To replace vanilla sprite behavior while keeping the original sprite ID:
```asm
; In a patch file or at the start of your sprite file
pushpc
org $069283+($XX*2) ; Replace vanilla main pointer
dw NewCustomBehavior_Main
org $06865B+($XX*2) ; Replace vanilla prep pointer
dw NewCustomBehavior_Prep
pullpc
NewCustomBehavior_Main:
{
; Check if custom behavior should activate
LDA.l $7EF3XX : CMP.b #$YY : BNE .use_vanilla
JSL CustomImplementation_Long
RTS
.use_vanilla
JML $OriginalVanillaAddress
}
```
### 10.7. Interactive Objects and Environmental Triggers
**Player-Manipulated Objects:**
Objects like Ice Block and Minecart require precise collision and alignment:
```asm
; Round position to 8-pixel grid for proper alignment
RoundCoords:
{
LDA.b $00 : CLC : ADC.b #$04 : AND.b #$F8 : STA.b $00 : STA.w SprY, X
LDA.b $02 : CLC : ADC.b #$04 : AND.b #$F8 : STA.b $02 : STA.w SprX, X
JSR UpdateCachedCoords
RTS
}
```
**Environmental Triggers:**
Switch objects respond to player actions and modify game state:
```asm
; Mine switch changes track configuration
Sprite_Mineswitch_OnActivate:
{
LDA.w SprMiscA, X : BEQ .currently_off
; Switch is on, turn it off
STZ.w SprMiscA, X
JSR UpdateTrackTiles_Off
JMP .done
.currently_off
; Switch is off, turn it on
LDA.b #$01 : STA.w SprMiscA, X
JSR UpdateTrackTiles_On
.done
%PlaySFX2($14) ; Switch sound
RTS
}
```
### 10.8. Shop and Item Management
**Transaction System:**
```asm
Shopkeeper_SellItem:
{
; Check if player has enough rupees
REP #$20
LDA.l $7EF360 : CMP.w #ItemCost : BCC .not_enough
; Deduct rupees
SEC : SBC.w #ItemCost : STA.l $7EF360
SEP #$20
; Grant item
LDA.b #ItemID : STA.l $7EF3XX
%ShowUnconditionalMessage(ThankYouMessage)
RTS
.not_enough
SEP #$20
%ErrorBeep()
%ShowUnconditionalMessage(NotEnoughRupeesMessage)
RTS
}
```
**Item Granting with Quest Tracking:**
```asm
NPC_GrantQuestItem:
{
; Check if already received
LDA.l $7EF3XX : BNE .already_obtained
; Grant item
LDA.b #$01 : STA.l $7EF3XX
LDA.b #ItemID
JSL Link_ReceiveItem
%ShowUnconditionalMessage(ItemReceivedMessage)
RTS
.already_obtained
%ShowUnconditionalMessage(AlreadyHaveMessage)
RTS
}
```
### 10.9. Player State Manipulation
For cinematic sequences and special interactions:
```asm
Cutscene_LinkSleep:
{
; Prevent player input
%PreventPlayerMovement()
; Set Link's animation
LDA.b #$XX : STA.w LinkAction
; Play sleep animation
LDA.b #$XX : STA.w LinkGraphics
; Wait for timer
LDA.w SprTimerA, X : BNE .still_waiting
%AllowPlayerMovement()
%GotoAction(NextState)
.still_waiting
RTS
}
```
### 10.10. Error Handling and Player Feedback
**Robust Error Prevention:**
```asm
; Portal sprite checks for valid placement
Sprite_Portal_CheckValidTile:
{
LDA.w CurrentTileType : CMP.b #ValidTileMin : BCC .invalid
CMP.b #ValidTileMax : BCS .invalid
CMP.b #$XX : BEQ .invalid ; Check specific invalid tiles
; Valid placement
SEC
RTS
.invalid
%ErrorBeep()
STZ.w SprState, X ; Despawn sprite
CLC
RTS
}
```
**Clear Player Feedback:**
```asm
; Provide audio/visual feedback
%ErrorBeep() ; Sound for errors
%PlaySFX1($14) ; Sound for success
JSL Sprite_ShowMessageUnconditional ; Text feedback
```
## 11. Additional Resources
**Core Files:**
* `Core/sprite_macros.asm` - All available macros and their implementations
* `Core/sprite_functions.asm` - Reusable sprite functions
* `Core/sprite_new_table.asm` - Sprite table initialization
* `Core/symbols.asm` - RAM address definitions
* `Core/structs.asm` - Sprite structure definitions
**Documentation:**
* `Docs/Sprites/` - Detailed documentation for existing sprites
* `Docs/Sprites/Overlords.md` - Overlord system documentation
* `Sprites/all_sprites.asm` - See how sprites are organized and included
**Example Sprites:**
* **Simple Enemy:** `Sprites/Enemies/sea_urchin.asm` - Basic enemy with minimal logic
* **Advanced Enemy:** `Sprites/Enemies/booki.asm` - Dynamic AI with state management
* **Boss:** `Sprites/Bosses/kydreeok.asm` - Multi-part boss with child sprites
* **Interactive Object:** `Sprites/Objects/minecart.asm` - Complex player interaction
* **NPC:** `Sprites/NPCs/mask_salesman.asm` - Shop system and dialogue

View File

@@ -0,0 +1,48 @@
# BG Color / Overlay Regression — Resolved (March 2026)
## Resolution Summary
The "Too Bright" and "Flash to Day" bugs have been resolved by fixing the register management in `ZSCustomOverworld.asm`.
**The Problem:**
When transitioning from an area with an overlay (like Rain/Storms) to an area without one (Overlay ID `$FF`), the code cleared the Subscreen Enable register (`$1D`) but **failed to clear the Color Math Control register (`$9A`)**.
- Rain sets `$9A` to `$72` (Additive Math).
- If `$9A` remains `$72` in a normal area, the SNES PPU continues to perform additive color math using the Fixed Color registers (`$9C`/`$9D`).
- This caused the background to appear significantly brighter (approx +6 per channel), turning the dark night tint into a "bright yellow-ish green".
- This also caused the "Flash to Day" effect during transitions, as the additive brightness kicked in immediately.
**The Fix:**
Modified `ZSCustomOverworld.asm` in two key locations to ensure `$9A` is always cleared when no overlay is present.
1. **Walking Transitions (`Overworld_ReloadSubscreenOverlay_Interupt`):**
Added a check for Overlay `$FF` to explicitly clear `$9A`. This prevents the brightness glitch during scrolling.
```asm
; In Overworld_ReloadSubscreenOverlay_Interupt
CPX.b #$FF : BNE .checkScroll
LDA.b #$00 ; Disable Color Math
BRA .loadOverlay
```
2. **Dungeon/Warp/Bird Transitions (`Overworld_LoadBGColorAndSubscreenOverlay`):**
Added `STZ.b $9A` to the block handling the `$FF` case. This prevents the glitch when exiting dungeons or warping.
```asm
; In Overworld_LoadBGColorAndSubscreenOverlay
CMP.w #$00FF : BNE .noCustomFixedColor
SEP #$30
STZ.b $9A ; FIX: Clear color math
; ...
```
## Verification
- **Brightness:** The background color in normal areas should now correctly reflect the Time System tint without extra brightness.
- **Transitions:** Walking from a Rain area to a Normal area should no longer result in a brightness jump.
- **Song of Storms:** Summoning and dismissing storms should work correctly, with the overlay and color math engaging and disengaging as expected.
## Technical Details
- **File:** `Overworld/ZSCustomOverworld.asm`
- **Routines:** `Overworld_LoadBGColorAndSubscreenOverlay`, `Overworld_ReloadSubscreenOverlay_Interupt`.
- **Registers:** `$1D` (Subscreen), `$9A` (CGADDSUB Mirror), `$9C`/`$9D` (COLDATA Mirrors).
## Outstanding Issues
- None related to BG Color Brightness.

View File

@@ -0,0 +1,45 @@
# Issue: Lost Woods Transition Coordinate Desync
## Status: Active / Low Priority
**Created:** March 2026
**Impact:** Visual/Gameplay discontinuity when exiting the Lost Woods (Area 0x29) back to the West (0x28).
## Problem Description
The custom Lost Woods puzzle uses a coordinate manipulation trick (`INC/DEC $21`, `INC/DEC $E7`) to simulate an infinite loop.
- **Symptoms:**
- When completing the puzzle (Exit East -> 0x2A), the fix implemented (`LostWoods_ResetCoordinates`) correctly snaps Link to the left edge of the new screen, preventing him from skipping the map.
- **Regression:** When *returning* to the previous map (Exit West -> 0x28), Link may appear at incorrect coordinates or the camera may be misaligned relative to the player.
- The "Snapping" logic forces Link's X/Y to the base of Area 0x29 (e.g., X=0x0200). However, the transition logic in `ZSCustomOverworld.asm` uses these coordinates to calculate the *destination* position in the new area. If the snap happens too early or incorrectly, the destination calculation (Start X - Offset) might underflow or misalign.
## Technical Analysis
### Custom Logic (`lost_woods.asm`)
The puzzle modifies:
- `$21` / `$23`: Link's High-Byte Coordinates (World Grid Position).
- `$E1` / `$E7` / `$E9`: Overlay and BG Scroll Registers.
This desynchronizes the "Visible" position from the "Logical" position expected by the standard Overworld engine.
### ZSOW Transition Logic
`OverworldHandleTransitions` in `ZSCustomOverworld.asm` relies on:
- `$20` / `$22`: Link's 16-bit absolute coordinates.
- `Pool_OverworldTransitionPositionX/Y`: Lookup tables for screen boundaries.
### Root Cause Hypothesis
1. **Coordinate Mismatch:** The `LostWoods_ResetCoordinates` routine snaps Link to `X=0x0200` (Left edge of 0x29).
2. **Transition Calc:** When moving West to 0x28, the engine expects Link to be crossing the boundary.
3. **Vanilla vs. Custom:** Vanilla ALTTP does not use infinite looping coordinates in the overworld. This mechanic is entirely custom and fights the static grid nature of the engine.
## Future Investigation Strategy (Reference `usdasm`)
1. **Vanilla Transitions:** Study `Bank02.asm` in `usdasm` to see how `Module09_Overworld` handles coordinate handoffs.
- Look for `Overworld_ScrollMap` and `Overworld_HandleCardinalCollision`.
2. **Camera Re-centering:** Search for routines that "center" the camera on Link after a transition (`Overworld_SetCameraBoundaries`). We may need to manually invoke this *after* the transition logic finishes, rather than snapping coordinates *before*.
3. **Scroll Register Reset:** Instead of zeroing `$E1` etc., we might need to recalculate them based on the *new* area's properties immediately upon load.
## Workaround
The bug is non-fatal. Players can navigate out of the area, though the visual transition may be jarring.
## Related Files
- `Overworld/lost_woods.asm`
- `Overworld/ZSCustomOverworld.asm`
- `usdasm/bank_02.asm` (Reference)

26
Docs/README.md Normal file
View File

@@ -0,0 +1,26 @@
# Oracle of Secrets Documentation
Welcome to the documentation for Oracle of Secrets. This directory is organized to help you find information about the project's architecture, systems, and content.
## Directory Structure
- `./General/`: High-level project information, including development guidelines and build instructions.
- `./Core/`: Documentation for the core engine components, such as memory maps, the player engine (`Link.md`), and system interaction analysis.
- `./Features/`: Detailed analysis of major gameplay features.
- `./Features/Items/`: Information on custom and modified items.
- `./Features/Masks/`: Details on the mask transformation system.
- `./Features/Menu/`: Analysis of the custom menu and HUD.
- `./Features/Music/`: Guide to the music system and composition workflow.
- `./World/`: Information about the game world's construction.
- `./World/Overworld/`: Documentation for the overworld engine, including `ZSCustomOverworld` and the time system.
- `./World/Dungeons/`: Details on dungeon mechanics and custom features.
- `./Sprites/`: Analysis of all sprite types, including bosses, NPCs, and interactive objects.
- `./Guides/`: Step-by-step guides and tutorials, such as the sprite creation guide and the main quest flowchart.
## Key Documents
- **`General/DevelopmentGuidelines.md`**: The primary guide for coding standards, architecture, and best practices. Start here to understand the project's philosophy.
- **`Core/MemoryMap.md`**: A comprehensive map of custom WRAM and SRAM variables.
- **`Guides/QuestFlow.md`**: A detailed walkthrough of the main story and side-quest progression.
- **`Guides/SpriteCreationGuide.md`**: A tutorial for creating new custom sprites.
- **`World/Overworld/ZSCustomOverworld.md`**: A deep dive into the data-driven overworld engine that powers the game world.

77
Docs/Sprites/Bosses.md Normal file
View File

@@ -0,0 +1,77 @@
# Bosses Analysis
This document provides an analysis of the boss sprites found in the `Sprites/Bosses/` directory. These sprites are typically complex, with multiple phases and unique behaviors.
## File Overview
| Filename | Sprite ID(s) | Description |
|---|---|---|
| `arrghus.asm` | (Hooks `$1EB593`) | A custom version of Arrghus that spawns fireballs. |
| `dark_link.asm` | `Sprite_DarkLink` (`$C1`) | A multi-phase boss fight against a shadow version of Link. |
| `king_dodongo.asm`| (Hooks `$1E811A`) | A custom version of King Dodongo with a new health system. |
| `kydreeok.asm` | `Sprite_Kydreeok` | A multi-headed sea monster boss. Parent sprite for `kydreeok_head.asm`. |
| `kydreeok_head.asm`| `Sprite_KydreeokHead` | The individual head of the Kydreeok boss, which can be attacked. |
| `kydrog.asm` | `Sprite_KydrogNPC` | A cutscene version of Kydrog that appears before the boss fight. |
| `kydrog_boss.asm` | `Sprite_KydrogBoss` | The main Kydrog boss fight, a large amphibious creature. |
| `lanmola.asm` | (Hooks `$05A377`) | A custom version of the Lanmola boss. |
| `lanmola_expanded.asm`| (Expansion) | Contains additional data and drawing logic for the custom Lanmola. |
| `manhandla.asm` | `Sprite_Manhandla` | A two-phase boss fight against Manhandla, which later becomes a Big Chuchu. |
| `octoboss.asm` | (Hooks `Sprite_A2_Kholdstare`) | A custom boss fight against two octopuses, replacing Kholdstare. |
| `twinrova.asm` | `Sprite_Twinrova` (`$CE`) | A custom boss fight replacing Blind the Thief with the twin witches, Koume and Kotake. |
| `vampire_bat.asm` | (Subtype of Keese) | A vampire bat mini-boss. |
| `wolfos.asm` | `Sprite_Wolfos` | A Wolfos mini-boss that guards the Wolf Mask. |
## Detailed Boss Analysis
### `dark_link.asm`
- **Sprite ID:** `Sprite_DarkLink` (`$C1`)
- **Summary:** A complex, multi-action boss that mimics Link's abilities. The fight has multiple stages, including a Ganon-like form.
- **Key Logic:**
- The main routine is a large state machine driven by `SprAction, X`.
- **Actions:** Includes standard walking, sword slashes, jump attacks, dodging, and using items like bombs and the Magic Cape.
- **AI:** The AI in the `Handler` routine decides which action to take based on the distance to the player. It can choose to slash, dodge, or use a special attack.
- **Enrage Mechanic:** At a certain health threshold (`SprHealth < $20`), the boss can enter an "enraged" state (`Enraging` action), which refills its health and makes its attacks faster.
- **Ganon Subtype:** If `SprSubtype` is 5, it uses Ganon's draw and main logic, acting as a final phase.
### `kydreeok.asm` / `kydreeok_head.asm`
- **Sprite IDs:** `Sprite_Kydreeok` (body), `Sprite_KydreeokHead` (head, ID `$CF`)
- **Summary:** A large, stationary sea monster boss with multiple heads that act as its weak points. The main body sprite (`Kydreeok`) is a controller that spawns and manages the head sprites.
- **Key Logic:**
- **`Kydreeok_Prep`:** Spawns two `KydreeokHead` sprites (`SpawnLeftHead`, `SpawnRightHead`).
- **`Kydreeok_Main`:** The body moves around the arena, and the heads follow its position, controlled via shared RAM addresses (`LeftNeck1_X`, etc.).
- **`KydreeokHead_Main`:** The heads have their own AI. They move in a rotational pattern and randomly shoot fireballs at the player.
- **Damage:** Only the head sprites can be damaged. When both heads are defeated, the main body sprite is killed.
### `manhandla.asm`
- **Sprite ID:** `Sprite_Manhandla` (`$88`)
- **Summary:** A two-phase boss. The first phase is a plant-like creature with three heads. The second phase is a large Big Chuchu.
- **Key Logic:**
- **Phase 1 (Manhandla):** The main body (`Manhandla_Body`) moves around the room, and three head sprites are spawned as offspring. The boss is only vulnerable when the heads are defeated.
- **Phase Transition:** When all three heads are killed, `Sprite_Manhandla_CheckForNextPhaseOrDeath` sets `SprMiscD` to 1, refills the boss's health, and transitions to the `BigChuchu_Emerge` action.
- **Phase 2 (Big Chuchu):** The sprite changes its appearance and behavior to that of a large slime. It moves around, spawns smaller slime projectiles (`Chuchu_SpawnBlast`), and has a central flower weak point.
### `octoboss.asm`
- **Sprite ID:** (Replaces `Sprite_A2_Kholdstare`)
- **Summary:** A custom boss fight featuring two octopus brothers who replace the vanilla Kholdstare boss.
- **Key Logic:**
- The fight begins when the player approaches, triggering the `Emerge` sequence.
- The first octopus spawns its brother (`SpawnAndAwakeHisBrother`), and they both don hats (`SpawnPirateHats`).
- The two sprites then move independently, attacking the player. Their total health is tracked via `ReturnTotalHealth`.
- When their combined health is low enough, they surrender (`WaitMessageBeforeSurrender`), remove their hats, and one submerges to give the player the Quake Medallion.
### `twinrova.asm`
- **Sprite ID:** `Sprite_Twinrova` (`$CE`, replaces Blind the Thief)
- **Summary:** A custom, multi-phase boss fight against the twin witches Koume (fire) and Kotake (ice). This sprite completely replaces the vanilla Blind the Thief boss, including the cutscene leading up to it.
- **Key Logic:**
- **Trigger:** The fight begins when the Blind Maiden follower is brought into the boss room. `Follower_CheckBlindTrigger` detects this, and `Blind_SpawnFromMaiden` despawns the maiden and spawns the Twinrova sprite.
- **Phase 1:** The combined Twinrova form moves around the room, alternating between fire (`Twinrova_FireAttack`) and ice (`Twinrova_IceAttack`) attacks.
- **Phase 2:** When health is low, the boss splits. The `Twinrova_MoveState` action will randomly choose between `KoumeMode` (fire) and `KotakeMode` (ice). In these modes, the boss has different attack patterns, including spawning Keese and changing the floor tiles.
### `wolfos.asm`
- **Sprite ID:** `Sprite_Wolfos`
- **Summary:** A mini-boss that guards the Wolf Mask. It is designed to be subdued rather than killed.
- **Key Logic:**
- The Wolfos attacks Link with lunges and swipes.
- When its health is depleted, `Sprite_Wolfos_CheckIfDefeated` transitions it to the `Wolfos_Subdued` action instead of killing it.
- In the subdued state, it waits for the player to play the Song of Healing (`SongFlag`).
- Once the song is played, it grants the Wolf Mask (`ITEMGET` ID `$10F`) and then despawns.

View File

@@ -0,0 +1,31 @@
# Dark Link Sprite Analysis
## Overview
Dark Link (Sprite ID: `$C1`) is a boss sprite known for its dynamic and challenging combat. It features a variety of attacks, including sword slashes, jump attacks, and projectile spawning. A notable aspect of this sprite is its ability to transform into a Ganon-like entity via a subtype, suggesting a multi-phase boss encounter.
## Key Properties:
* **Sprite ID:** `$C1`
* **Number of Tiles:** 4
* **Health:** 34 (decimal)
* **Damage:** 0 (Damage is handled by spawned attacks or direct contact logic, not directly by the sprite's `!Damage` property.)
* **Special Properties:**
* `!DeflectProjectiles = 01` (Deflects all projectiles)
* `!ImperviousArrow = 01` (Impervious to arrows)
* `!Boss = 00` (Despite being a boss, this flag is not set, indicating custom boss logic rather than reliance on vanilla boss flags.)
## Subtypes:
* **Subtype `$05` (Ganon):** This subtype completely alters Dark Link's behavior to that of a Ganon boss, executing `Sprite_Ganon_Main` and `Sprite_Ganon_Draw`. This mechanism allows for a multi-stage boss fight or an entirely different boss using the same sprite slot.
* **Subtype `$01` (Sword Damage):** This subtype is used for a temporary sprite spawned during Dark Link's sword attacks to handle collision and damage detection.
## In-Game Behavior:
Dark Link is an active and engaging boss. It moves strategically towards Link, performs various sword attacks (including a jump attack with screen shake), can utilize a cape for evasion, and throws bombs. It reacts to damage with visual recoil and flashing, and enters an "enraging" state (indicated by a red palette change) which likely alters its attack patterns or aggression. The Ganon subtype suggests a significant shift in combat during the fight.
## Original Sprite Replaced:
The code does not explicitly state which vanilla sprite `dark_link` replaces. However, the integration of `GanonInit` and Ganon-related logic strongly suggests it either heavily modifies an existing Ganon boss fight or is a completely new boss utilizing a custom sprite ID.
## Development Goals for Oracle of Secrets:
* **Variety in Attacks:** Introduce more diverse attack patterns and abilities to enhance the fight's complexity and challenge.
* **Unique Oracle of Secrets Attacks:** Implement attacks that are thematic and unique to the Oracle of Secrets project, moving beyond standard ALTTP boss mechanics.
## Code Quality Notes:
The code, while functional and effective in creating a competent boss, is noted to be somewhat "messy" due to its origin from Zarby89's ZScream project. This implies that while it works, future modifications might require careful navigation through its structure.

View File

@@ -0,0 +1,65 @@
# Kydreeok Sprite Analysis
## Overview
The `kydreeok` sprite (ID: `Sprite_Kydreeok`, which is `$7A`) represents the main Kydreeok boss. It orchestrates the entire boss fight, including spawning and managing its child head sprites (`kydreeok_head`), controlling its own movement phases, and handling its overall defeat. This is a multi-headed boss where the heads are separate sprites.
## Key Properties:
* **Sprite ID:** `Sprite_Kydreeok` (`$7A`)
* **Description:** The main Kydreeok boss, controlling the overall fight and its child heads.
* **Number of Tiles:** 8
* **Health:** `00` (The boss's health is managed through its child heads and custom logic, not directly by this sprite's `!Health` property.)
* **Damage:** `00` (Damage dealt to Link is likely handled by its heads or custom logic.)
* **Special Properties:**
* `!Boss = 01` (This sprite is correctly identified as a boss.)
* `!Hitbox = $07`
## Main States/Actions (`Sprite_Kydreeok_Main` Jump Table):
The boss's behavior is divided into several phases:
* **`Kydreeok_Start` (0x00):** Initial state. Applies graphics and palette, prevents Link from passing through, and transitions to `Kydreeok_StageControl` after a timer. Stores its own sprite index in `Kydreeok_Id`.
* **`Kydreeok_StageControl` (0x01):** Manages the boss's movement stage, setting velocities and checking boundaries.
* **`Kydreeok_MoveXandY` (0x02):** Moves the boss in both X and Y directions towards Link, checking boundaries and handling damage.
* **`Kydreeok_MoveXorY` (0x03):** Moves the boss in either X or Y direction towards Link, checking boundaries and handling damage.
* **`Kydreeok_KeepWalking` (0x04):** Continues walking, with a random chance to transition to a flying state.
* **`Kydreeok_Dead` (0x05):** Handles the boss's death sequence, including visual effects (flickering, explosions) and eventually despawning the sprite.
* **`Kydreeok_Flying` (0x06):** The boss enters a flying state, moving towards Link at a set height, checking boundaries and handling damage.
## Initialization (`Sprite_Kydreeok_Prep`):
* Sets initial timers and movement speeds.
* Caches its own origin position.
* **Spawns its child heads:** Calls `JSR SpawnLeftHead` and `JSR SpawnRightHead`. A `SpawnCenterHead` routine is commented out, suggesting a potential for a three-headed boss.
* Initializes neck offsets to zero.
* Applies a custom palette (`JSR ApplyPalette`).
* Sets the boss theme music.
## Death and Respawn Logic (`Sprite_Kydreeok_CheckIfDead`, `MaybeRespawnHead`):
* **`Sprite_Kydreeok_CheckIfDead`:** This crucial routine checks the state of its child heads (`Offspring1_Id`, `Offspring2_Id`). If both heads are defeated, it triggers a "dead" phase, changes its graphics, respawns both heads, and then transitions to the `Kydreeok_Dead` state. This indicates a multi-phase boss where heads can be temporarily defeated.
* **`MaybeRespawnHead`:** Randomly respawns a head if its corresponding child sprite is dead, adding a dynamic challenge to the fight.
## Head Spawning (`SpawnLeftHead`, `SpawnRightHead`):
* These routines spawn `Sprite_KydreeokHead` (`$CF`) sprites.
* They assign `SprSubtype` to the spawned heads (`$00` for left, `$01` for right), allowing the child sprites to differentiate their behavior.
* They store the IDs of the spawned heads in global variables (`Offspring1_Id`, `Offspring2_Id`).
* They set the initial position of the heads relative to the main boss and initialize neck segment coordinates.
## Movement (`MoveBody`, `StopIfOutOfBounds`):
* **`MoveBody`:** Manages the main body's movement, calling `JSL Sprite_Move` and updating background scrolling based on its movement. It reuses logic from `Trinexx_MoveBody`.
* **`StopIfOutOfBounds`:** Prevents the boss from moving beyond screen boundaries. It also subtly adjusts the neck positions when hitting a boundary, creating a visual "pushing" effect.
## Palette Management (`ApplyPalette`, `ApplyEndPalette`):
* **`ApplyPalette`:** Sets the initial palette for the boss.
* **`ApplyEndPalette`:** Sets a different palette, likely for a defeated state or phase change.
## Graphics Transfer (`ApplyKydreeokGraphics`):
* Handles DMA transfer of graphics data (`kydreeok.bin`, `kydreeok_phase2.bin`) to VRAM, allowing for different graphical appearances across phases.
## Global Variables for Neck Control:
* `LeftNeck1_X` to `LeftNeck3_Y`, `RightNeck1_X` to `RightNeck3_Y`: Global RAM addresses used to store the coordinates of the neck segments, enabling the heads to track them.
* `Kydreeok_Id`: Stores the sprite index of the main Kydreeok boss.
* `Offspring1_Id`, `Offspring2_Id`: Store the sprite indices of the spawned heads.
## Discrepancies/Notes:
* The `!Health` and `!Damage` properties are `00`, confirming that the boss's health and damage are managed through its heads (`Sprite_KydreeokHead`) and custom logic within `Sprite_Kydreeok_CheckIfDead`.
* The `Sprite_Kydreeok_CheckIfDead` routine clearly defines a multi-phase fight where the heads can be defeated, respawned, and ultimately lead to the main boss's defeat.
* The commented-out `SpawnCenterHead` suggests a potential for a three-headed Kydreeok that was either removed or is an unimplemented feature.
* Reusing movement logic from `Trinexx_MoveBody` is efficient but should be considered for unique boss feel.
* Hardcoded addresses for `JSL` calls could be replaced with named labels for better maintainability.

View File

@@ -0,0 +1,68 @@
# Kydreeok Head Sprite Analysis
## Overview
The `kydreeok_head` sprite (ID: `Sprite_KydreeokHead`, which is `$CF`) is a child sprite of the main `Kydreeok` boss. It represents one of the multi-headed boss's individual heads, possessing independent movement, attack patterns, and damage handling. Its primary role is to move, rotate, and attack Link, contributing to the overall boss encounter.
## Key Properties:
* **Sprite ID:** `Sprite_KydreeokHead` (`$CF`)
* **Description:** Child sprite of the Kydreeok boss, responsible for individual head behavior.
* **Number of Tiles:** 7
* **Health:** `$C8` (200 decimal) - This high health value indicates it's a significant component of the boss fight.
* **Damage:** `00` (Damage is likely applied through its spawned attacks.)
* **Special Properties:**
* `!Boss = 00` (Not marked as a boss itself, as it's a component of a larger boss.)
* `!Hitbox = 09`
## Subtypes:
The `SprSubtype, X` register is crucial for differentiating the heads and their behavior:
* **Subtype `$00`:** Controls the "Left Head" via `Neck1_Control`.
* **Subtype `$01`:** Controls the "Right Head" via `Neck2_Control`.
This allows the same sprite ID to manage multiple distinct heads.
## Main States/Actions (`Sprite_KydreeokHead_Main` Jump Table):
The head's behavior is governed by a state machine:
* **`KydreeokHead_ForwardAnim` (0x00):** Default state, plays forward animation, handles damage, performs rotational movement, and randomly attacks. Transitions to other directional states based on Link's position.
* **`KydreeokHead_RightAnim` (0x01):** Plays right-facing animation, handles damage, rotation, and attacks.
* **`KydreeokHead_LeftAnim` (0x02):** Plays left-facing animation, handles damage, rotation, and attacks.
* **`KydreeokHead_FarRight` (0x03):** Plays far-right animation, moves towards Link, handles damage, rotation, and attacks.
* **`KydreeokHead_FarLeft` (0x04):** Plays far-left animation, moves towards Link, handles damage, rotation, and attacks.
* **`KydreeokHead_SummonFire` (0x05):** Moves towards Link, checks damage, and utilizes `JSL Sprite_Twinrova_FireAttack` to deal damage. The head sprite is then killed after a timer.
## Initialization (`Sprite_KydreeokHead_Prep`):
* Sets initial health to `$FF` (255 decimal), though the `!Health` property is `$C8`. This discrepancy might be overridden by the parent `Kydreeok` sprite or is a temporary value.
* Sets `SprBump = $09` (bump damage type).
* Initializes `SprMiscE, X` to `0`.
## Drawing (`Sprite_KydreeokHead_Draw`):
* Uses standard OAM allocation routines.
* The main drawing routine calls `JMP Sprite_KydreeokHead_DrawNeck`, indicating that the neck segments are drawn after the head.
* Includes logic for flashing when damaged.
## Neck Control (`KydreeokHead_NeckControl`, `Neck1_Control`, `Neck2_Control`, `Sprite_KydreeokHead_DrawNeck`, `DrawNeckPart`):
This is a sophisticated system for managing the multi-segmented neck:
* `KydreeokHead_NeckControl` dispatches to `Neck1_Control` (for the left head) or `Neck2_Control` (for the right head) based on `SprSubtype, X`.
* `Neck1_Control` and `Neck2_Control` manage the movement and positioning of three neck segments, ensuring they follow the head while maintaining specific distances.
* `Sprite_KydreeokHead_DrawNeck` and `DrawNeckPart` handle the rendering of these segments.
## Movement and Rotation (`KydreeokHead_RotationMove`, `RotateHeadUsingSpeedValues`, `MoveWithBody`):
* **`KydreeokHead_RotationMove`:** Generates random speeds, dispatches to neck control, moves with the main body, and applies rotational movement.
* **`RotateHeadUsingSpeedValues`:** Uses sine/cosine tables (`XSpeedSin`, `YSpeedSin`) to apply smooth rotational movement.
* **`MoveWithBody`:** Ensures the head's position is correctly offset and relative to the main `Kydreeok` boss, adjusting for left or right heads.
## Attacks (`RandomlyAttack`, `KydreeokHead_SummonFire`):
* **`RandomlyAttack`:** Randomly spawns a fire-based projectile (which is actually the `Sprite_KydreeokHead` itself entering the `SummonFire` state).
* **`KydreeokHead_SummonFire`:** This state is entered when a fire projectile is spawned. It moves towards Link and uses `JSL Sprite_Twinrova_FireAttack` to deal damage, after which the head sprite is killed.
## Key Macros/Functions Used:
* `%Set_Sprite_Properties`, `%GotoAction`, `%StartOnFrame`, `%PlayAnimation`, `%MoveTowardPlayer`
* `JSL JumpTableLocal`, `JSL Sprite_CheckDamageFromPlayer`, `JSL Sprite_CheckDamageToPlayer`, `JSL Sprite_DamageFlash_Long`
* `JSL GetRandomInt`, `JSL Sprite_MoveLong`, `JSL Sprite_IsToRightOfPlayer`
* `JSL Sprite_SpawnDynamically`, `JSL Sprite_SetSpawnedCoords`
* `JSL Sprite_Twinrova_FireAttack`, `JSL Fireball_SpawnTrailGarnish`
* `JSR GetDistance8bit`
## Discrepancies/Notes:
* The `!Health` property is `$C8`, but `Sprite_KydreeokHead_Prep` sets `SprHealth, X` to `$FF`. This needs clarification.
* The reuse of `JSL Sprite_Twinrova_FireAttack` for Kydreeok's head is an example of code reuse, but it's important to ensure it fits the thematic design of Kydreeok.
* The neck control system is quite intricate, highlighting advanced sprite design.
* Several hardcoded addresses for `JSL` calls could be replaced with named labels for better maintainability.

View File

@@ -0,0 +1,90 @@
# Kydrog Boss Sprite Analysis
## Overview
The `kydrog_boss` sprite (ID: `Sprite_KydrogBoss`, which is `$CB`) represents the main Kydrog boss. This boss features multiple phases, dynamic movement, and the ability to summon stalfos offspring. It's a complex encounter designed to challenge the player through varied attack patterns and phase transitions.
## Key Properties:
* **Sprite ID:** `Sprite_KydrogBoss` (`$CB`)
* **Description:** The main Kydrog boss, controlling its own movement, phases, and spawning stalfos offspring.
* **Number of Tiles:** 11
* **Health:** `00` (The boss's health is managed through `CheckForNextPhase` and `Sprite_KydrogBoss_CheckIfDead`, not directly by this property.)
* **Damage:** `00` (Damage dealt to Link is likely handled by its attacks or spawned offspring.)
* **Special Properties:**
* `!Boss = $01` (Correctly identified as a boss.)
* `!Shadow = 01` (Draws a shadow.)
* `!Hitbox = 03`
## Custom Variables:
* `!ConsecutiveHits = $AC`: Tracks consecutive hits on the boss, influencing its behavior.
* `!KydrogPhase = $7A`: Manages the current phase of the boss fight.
* `!WalkSpeed = 10`: Defines the boss's walking speed.
## Main States/Actions (`Sprite_KydrogBoss_Main` Jump Table):
The boss's behavior is governed by a detailed state machine:
* **`KydrogBoss_Init` (0x00):** Initial state, plays an "Arms Crossed" animation, and transitions to `KydrogBoss_WalkState` after an intro timer.
* **`KydrogBoss_WalkState` (0x01):** The primary walking state. Manages phase transitions, handles damage, taunting, and determines the next walking direction (forward, backward, left, right) based on Link's position and proximity.
* **`KydrogBoss_WalkForward` (0x02), `KydrogBoss_WalkLeft` (0x03), `KydrogBoss_WalkRight` (0x04), `KydrogBoss_WalkBackward` (0x05):** These states handle movement in specific directions, playing corresponding animations and executing core movement logic.
* **`KydrogBoss_TakeDamage` (0x06):** Manages the boss taking damage. Increments `!ConsecutiveHits`, plays a damage animation, spawns stalfos offspring, and can trigger an ascend action.
* **`KydrogBoss_TauntPlayer` (0x07):** Plays a taunting animation, handles damage, and transitions to `KydrogBoss_SummonStalfos`.
* **`KydrogBoss_SummonStalfos` (0x08):** Plays a summoning animation, handles damage, spawns stalfos offspring, and can throw a bone projectile at Link.
* **`KydrogBoss_Death` (0x09):** Handles the boss's death sequence, including killing spawned friends, playing a flickering animation, and despawning.
* **`KydrogBoss_Ascend` (0x0A):** The boss ascends off-screen, increasing its `SprHeight` and spawning stalfos offspring. Transitions to `KydrogBoss_Descend`.
* **`KydrogBoss_Descend` (0x0B):** The boss descends, tracking Link's position, decreasing its `SprHeight`, and spawning stalfos offspring. Transitions back to `KydrogBoss_WalkState`.
* **`KydrogBoss_Abscond` (0x0C):** The boss moves away from Link, increasing its speed, and transitions back to `KydrogBoss_WalkState`.
## Initialization (`Sprite_KydrogBoss_Prep`):
* Initializes `!KydrogPhase` to `00`.
* Sets initial health to `$A0` (160 decimal).
* Configures deflection (`SprDefl`), hitbox (`SprHitbox`), and bump damage (`SprBump`).
* Sets `SprGfxProps` to not invincible.
* Calls `JSR KydrogBoss_Set_Damage` to define its damage vulnerabilities.
* Sets initial sprite speeds and `!Harmless = 00`.
* Sets an intro timer (`SprTimerD = $80`).
## Death Check (`Sprite_KydrogBoss_CheckIfDead`):
* Monitors `SprHealth, X`. If health is zero or negative, it triggers the boss's death sequence, setting `SprState = $04` (kill sprite boss style) and `SprAction = $09` (KydrogBoss_Death stage).
## Phase Management (`CheckForNextPhase`):
This routine dynamically manages the boss's phases based on its current health:
* **Phase One (`!KydrogPhase = $00`):** Transitions to Phase Two when health drops below `$20`.
* **Phase Two (`!KydrogPhase = $01`):** Transitions to Phase Three when health drops below `$20`. Resets health to `$80`, sets action to `KydrogBoss_WalkState`, and increments `SprFlash, X`.
* **Phase Three (`!KydrogPhase = $02`):** Transitions to Phase Four when health drops below `$20`. Resets health to `$80`, sets action to `KydrogBoss_WalkState`.
* **Phase Four (`!KydrogPhase = $03`):** Sets action to `KydrogBoss_WalkState`.
## Damage Table (`KydrogBoss_Set_Damage`):
* Defines how KydrogBoss reacts to various attack types (Boomerang, Sword, Arrow, Bomb, etc.), stored in a damage properties table.
## Offspring Spawning (`RandomStalfosOffspring`, `Sprite_Offspring_Spawn`, `Sprite_Offspring_SpawnHead`):
* **`RandomStalfosOffspring`:** Randomly spawns either a normal stalfos offspring (`Sprite_Offspring_Spawn`) or a stalfos head offspring (`Sprite_Offspring_SpawnHead`), with a limit of 4 active stalfos.
* **`Sprite_Offspring_Spawn`:** Spawns a stalfos offspring (Sprite ID `$A7` or `$85`).
* **`Sprite_Offspring_SpawnHead`:** Spawns a stalfos head offspring (Sprite ID `$7C` or `$02`).
## Attacks (`Kydrog_ThrowBoneAtPlayer`):
* **`Kydrog_ThrowBoneAtPlayer`:** Spawns a bone projectile (Sprite ID `$A7`) that moves towards Link.
## Movement (`KydrogBoss_DoMovement`, `BounceBasedOnPhase`):
* **`KydrogBoss_DoMovement`:** Handles damage checks, applies damage to Link on contact, flashes when damaged, and incorporates phase-based bouncing and stalfos spawning.
* **`BounceBasedOnPhase`:** Adjusts the boss's bounce speed based on the current `!KydrogPhase`.
## Drawing (`Sprite_KydrogBoss_Draw`):
* Uses standard OAM allocation routines.
* Handles complex animation frames, x/y offsets, character data, properties, and sizes for drawing the boss.
* Utilizes 16-bit operations (`REP #$30`, `SEP #$30`) for precise drawing calculations.
## Other Routines:
* **`StopIfTooClose()` macro:** Prevents the boss from getting too close to Link.
* **`Sprite_CheckIfFrozen`:** Checks if the sprite is frozen and unfreezes it after a timer.
* **`GetNumberSpawnStalfos`:** Counts the number of active stalfos offspring.
* **`SpawnSplash`:** Spawns a splash effect.
* **`SpawnBossPoof`:** Spawns a boss poof effect.
* **`HandleMovingSplash`:** Handles splash effects during movement.
* **`SpawnMedallion` / `SpawnMedallionAlt`:** Spawns a medallion.
## Discrepancies/Notes:
* The main boss's health is intricately managed through `SprHealth, X` and `!KydrogPhase`, requiring a clear understanding of their interplay.
* The stalfos offspring are spawned using specific sprite IDs, which should be cross-referenced for full understanding.
* Many hardcoded values for timers, speeds, and offsets could be replaced with named constants for improved readability and maintainability.
* The code includes direct calls to sound effect functions (`JSL $0DBB8A`) and a commented-out call to `JSL $01F3EC` (Light Torch), which might be a leftover or an unimplemented feature.
## Hardcoded Activation Trigger:
* As noted by the user, the activation trigger for KydrogBoss is hardcoded. Specifically, in the `WaitForPlayerToApproach` routine, the boss checks `LDA.b $20 : CMP #$08C8`. `$20` represents Link's Y position, and `$08C8` is a hardcoded Y-coordinate. This means the boss will only activate when Link reaches this specific Y-coordinate, making it difficult to relocate the boss to other overworld maps without modifying this value.

View File

@@ -0,0 +1,79 @@
# Manhandla / Big Chuchu Sprite Analysis
## Overview
The `manhandla` sprite (ID: `Sprite_Manhandla`, which is `$88`) is a multi-phase boss. It begins as Manhandla, a multi-headed plant-like enemy, and upon the defeat of its individual heads, it transforms into a Big Chuchu. This design creates a dynamic and evolving boss encounter.
## Key Properties:
* **Sprite ID:** `Sprite_Manhandla` (`$88`)
* **Description:** A multi-phase boss that transforms from Manhandla to Big Chuchu.
* **Number of Tiles:** 3
* **Health:** `00` (Health is managed by its spawned heads in the first phase and then by its own `SprHealth` in the second phase.)
* **Damage:** `00` (Damage dealt to Link is likely handled by its heads or spawned projectiles.)
* **Special Properties:**
* `!Boss = 01` (Correctly identified as a boss.)
* `!DeathAnimation = 01` (Indicates custom death handling rather than a standard animation.)
* `!Hitbox = 00`
## Custom Variables:
* `Offspring1_Id`, `Offspring2_Id`, `Offspring3_Id`: Global variables used to track the sprite indices of the spawned Manhandla heads.
## Main States/Actions (`Sprite_Manhandla_Main` Jump Table):
The boss's behavior is governed by a detailed state machine across its phases:
* **`Manhandla_Intro` (0x00):** Initial state. Spawns the three Manhandla heads (`SpawnLeftManhandlaHead`, `SpawnRightManhandlaHead`, `SpawnCenterMandhandlaHead`) and transitions to `Manhandla_Body`.
* **`Manhandla_FrontHead` (0x01), `Manhandla_LeftHead` (0x02), `Manhandla_RightHead` (0x03):** These states are likely executed by the individual Manhandla head child sprites, managing their movement, damage, and contact with Link.
* **`BigChuchu_Main` (0x04):** The primary state for the Big Chuchu phase. Handles movement, damage, and can spawn Chuchu blasts.
* **`Flower_Flicker` (0x05):** A transitional state that flickers the background (BG2) and spawns a new `Sprite_Manhandla` (with `SprSubtype = $08`, representing the Big Chuchu head) after a timer, as part of the transformation.
* **`Manhandla_Body` (0x06):** The main state for the Manhandla body. Handles movement, damage, updates the positions of its spawned heads, and can spawn Mothula beams.
* **`BigChuchu_Emerge` (0x07):** Manages the emergence animation of the Big Chuchu.
* **`BigChuchu_Flower` (0x08):** A state for the Big Chuchu, possibly related to its visual appearance or an attack.
* **`BigChuchu_Dead` (0x09):** Handles the death sequence of the Big Chuchu.
* **`ChuchuBlast` (0x0A):** Manages the movement and damage of the spawned Chuchu blast projectile.
## Initialization (`Sprite_Manhandla_Prep`):
* Sets initial movement speeds and enables BG1 movement.
* Configures deflection properties (`SprDefl = $80`).
* Sets initial health to `$80`.
* Initializes `SprAction, X` based on `SprSubtype, X`.
## Phase Transition and Death Check (`Sprite_Manhandla_CheckForNextPhaseOrDeath`):
This critical routine orchestrates the boss's transformation:
* It checks if all three Manhandla heads (`Offspring1_Id`, `Offspring2_Id`, `Offspring3_Id`) are dead.
* If all heads are defeated, it triggers the transition to the Big Chuchu phase:
* Sets `SprMiscD, X = $01` (phase flag).
* Refills health (`SprHealth = $40`).
* Adjusts OAM entries (`SprNbrOAM = $08`).
* Sets `SprAction = $07` (BigChuchu_Emerge).
* It also manages the Big Chuchu's defeat, transitioning to `BigChuchu_Dead` when its health drops below `$04`.
## Head Spawning (`SpawnLeftManhandlaHead`, `SpawnRightManhandlaHead`, `SpawnCenterMandhandlaHead`):
* These routines spawn `Sprite_Manhandla` (`$88`) sprites as child heads.
* They assign specific `SprSubtype` values (`$03` for left, `$02` for right, `$01` for center) to differentiate the heads.
* They store the IDs of the spawned heads in global variables (`Offspring1_Id`, `Offspring2_Id`, `Offspring3_Id`).
* They set the initial position, health, and properties for each head.
## Head Positioning (`SetLeftHeadPos`, `SetRightHeadPos`, `SetCenterHeadPos`):
* These routines dynamically calculate and set the positions of the spawned heads relative to the main Manhandla body.
## Movement (`Sprite_Manhandla_Move`, `Manhandla_StopIfOutOfBounds`):
* **`Sprite_Manhandla_Move`:** The core movement logic for the Manhandla body, utilizing a jump table for `StageControl`, `MoveXandY`, `MoveXorY`, and `KeepWalking` states.
* **`Manhandla_StopIfOutOfBounds`:** Prevents the boss from moving beyond predefined screen boundaries.
## Attacks (`Chuchu_SpawnBlast`, `Mothula_SpawnBeams`):
* **`Chuchu_SpawnBlast`:** Spawns a Chuchu blast projectile (Sprite ID `$88` with `SprSubtype = $0A`).
* **`Mothula_SpawnBeams`:** Spawns beam projectiles (Sprite ID `$89`), called from `Manhandla_Body`.
## Drawing (`Sprite_Manhandla_Draw`, `Sprite_BigChuchu_Draw`):
* **`Sprite_Manhandla_Draw`:** Renders the Manhandla body and its heads.
* **`Sprite_BigChuchu_Draw`:** Renders the Big Chuchu form.
* Both utilize standard OAM allocation routines and handle animation frames, offsets, character data, properties, and sizes.
## Graphics and Palette (`ApplyManhandlaGraphics`, `ApplyManhandlaPalette`):
* **`ApplyManhandlaGraphics`:** Handles DMA transfer of graphics data (`manhandla.bin`) to VRAM.
* **`ApplyManhandlaPalette`:** Sets the custom palette for Manhandla.
## Discrepancies/Notes:
* The `!Health` property is `00`, indicating that the boss's health is managed by its heads in the first phase and then by its own `SprHealth` in the Big Chuchu phase.
* The `Sprite_Manhandla` ID (`$88`) is efficiently reused for the main boss, its heads, and the Chuchu blast projectile, with `SprSubtype` differentiating their roles.
* The reuse of `Mothula_SpawnBeams` for Manhandla is an example of code reuse.
* Hardcoded values for timers, speeds, and offsets could be replaced with named constants for improved readability and maintainability.
* A commented-out `org` for `Sprite_DoTheDeath#PrepareEnemyDrop.post_death_stuff` suggests potential modifications to the death routine.

View File

@@ -0,0 +1,254 @@
# Anti Kirby Sprite Analysis
## 1. Overview
The Anti Kirby sprite (`Sprite_AntiKirby`) is an enemy that exhibits unique behaviors, including a "suck" attack that can steal Link's items (bombs, arrows, rupees, or shield). It has distinct states for walking, sucking, being full (after stealing an item), and being "hatted" (presumably after Link gets his item back or a specific condition is met).
## 2. Sprite Properties
The sprite properties are defined at the beginning of `Sprites/Enemies/anti_kirby.asm`:
```asm
!SPRID = Sprite_AntiKirby
!NbrTiles = 02
!Harmless = 00
!HVelocity = 00
!Health = $08
!Damage = 04
!DeathAnimation = 00
!ImperviousAll = 00
!SmallShadow = 00
!Shadow = 01
!Palette = 00
!Hitbox = 03
!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
```
**Key Observations:**
* `!SPRID = Sprite_AntiKirby`: This uses a named constant for the sprite ID, which is good practice.
* `!Health = $08`: Anti Kirby has 8 health points.
* `!Damage = 04`: Deals half a heart of damage to Link.
* `!Hitbox = 03`: A relatively small hitbox.
* `!Shadow = 01`: It draws a shadow.
* `!Boss = 00`: It is not classified as a boss sprite, despite its complex behavior.
## 3. Main Structure (`Sprite_AntiKirby_Long`)
This routine follows the standard structure for sprites, calling the draw routine, shadow routine, and then the main logic if the sprite is active.
```asm
Sprite_AntiKirby_Long:
{
PHB : PHK : PLB
JSR Sprite_AntiKirby_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_AntiKirby_Main
.SpriteIsNotActive
PLB
RTL
}
```
## 4. Initialization (`Sprite_AntiKirby_Prep`)
The `_Prep` routine initializes several sprite-specific variables and sets its `SprBump`, `SprHealth`, and `SprPrize` based on Link's current sword level (or a similar progression metric, inferred from `LDA.l Sword : DEC : TAY`). This is an interesting way to scale enemy difficulty.
```asm
Sprite_AntiKirby_Prep:
{
PHB : PHK : PLB
STZ.w SprDefl, X
STZ.w SprTileDie, X
STZ.w SprMiscB, X
LDA.l Sword : DEC : TAY
LDA .bump_damage, Y : STA.w SprBump, X
LDA .health, Y : STA.w SprHealth, X
LDA .prize_pack, Y : STA.w SprPrize, X
PLB
RTL
.bump_damage
db $81, $88, $88, $88
.health
db 06, 10, 20, 20
.prize_pack
db 6, 3, 3, 3
}
```
**Insight:** The use of `LDA.l Sword : DEC : TAY` to index into `.bump_damage`, `.health`, and `.prize_pack` tables demonstrates a dynamic difficulty scaling mechanism based on player progression (likely sword upgrades). This is a valuable pattern for making enemies adapt to the player's power level.
## 5. Main Logic & State Machine (`Sprite_AntiKirby_Main`)
The `_Main` routine implements a complex state machine using `JSL JumpTableLocal` and a series of `dw` (define word) entries pointing to different states.
```asm
Sprite_AntiKirby_Main:
{
JSL Sprite_IsToRightOfPlayer
TYA : CMP #$01 : BNE .WalkRight
.WalkLeft
LDA.b #$40 : STA.w SprMiscC, X
JMP +
.WalkRight
STZ.w SprMiscC, X
+
JSL Sprite_DamageFlash_Long
JSL Sprite_CheckIfRecoiling
LDA.w SprAction, X
JSL JumpTableLocal
dw AntiKirby_Main ; State 0: Normal movement/attack
dw AntiKirby_Hurt ; State 1: Recoiling from damage
dw AntiKirby_BeginSuck ; State 2: Initiating suck attack
dw AntiKirby_Sucking ; State 3: Actively sucking Link
dw AntiKirby_Full ; State 4: Full after stealing item
dw AntiKirby_Hatted ; State 5: Hatted (after Link gets item back?)
dw AntiKirby_HattedHurt ; State 6: Hatted and hurt
dw AntiKirby_Death ; State 7: Death animation
; ... (State implementations below) ...
}
```
**State Breakdown:**
* **`AntiKirby_Main` (State 0):**
* Checks health and transitions to `AntiKirby_Full` if health is low (this seems like a bug, should probably be `AntiKirby_Death`).
* Randomly initiates the `AntiKirby_BeginSuck` state.
* Plays walking animation (`%PlayAnimation(0, 2, 10)`).
* Handles damage from player and transitions to `AntiKirby_Hurt`.
* Deals damage to Link on contact (`%DoDamageToPlayerSameLayerOnContact()`).
* Moves toward Link (`%MoveTowardPlayer(8)`) and bounces from tile collisions.
* **`AntiKirby_Hurt` (State 1):** Plays a hurt animation and waits for a timer (`SprTimerA`) to expire before returning to `AntiKirby_Main`.
* **`AntiKirby_BeginSuck` (State 2):**
* Plays a "suck" animation (`%PlayAnimation(4, 5, 10)`).
* Checks for damage from player.
* Checks Link's proximity (`$0E`, `$0F` are likely relative X/Y coordinates to Link). If Link is close enough, it transitions to `AntiKirby_Sucking` and sets up a projectile speed towards Link.
* **`AntiKirby_Sucking` (State 3):**
* Plays a "sucking" animation (`%PlayAnimation(5, 5, 10)`).
* Uses `JSL Sprite_DirectionToFacePlayer` and `JSL DragPlayer` to pull Link towards it if he's close enough.
* If Link is very close, it "consumes" Link, storing Link's position in `SprMiscB` and `SprMiscA`, sets a timer, and transitions to `AntiKirby_Full`.
* **`AntiKirby_Full` (State 4):**
* Plays a "full" animation (`%PlayAnimation(10, 10, 10)`).
* Sets Link's position to the stored `SprMiscA`/`SprMiscB` (effectively "spitting" Link out).
* Transitions to `AntiKirby_Hatted` after a timer.
* **`AntiKirby_Hatted` (State 5):**
* Plays a "hatted" animation (`%PlayAnimation(6, 8, 10)`).
* Moves toward Link, deals damage, and handles damage from player (transitions to `AntiKirby_HattedHurt`).
* **`AntiKirby_HattedHurt` (State 6):** Plays a hurt animation for the "hatted" state and returns to `AntiKirby_Hatted`.
* **`AntiKirby_Death` (State 7):** Sets `SprState` to `$06` (likely a death state) and plays a sound effect.
**Insight:** The `AntiKirby_Main` state's health check `LDA.w SprHealth, X : CMP.b #$01 : BCS .NotDead : %GotoAction(4)` seems to incorrectly transition to `AntiKirby_Full` (State 4) instead of `AntiKirby_Death` (State 7) when health is 0. This might be a bug or an intentional design choice for a specific game mechanic.
## 6. Item Stealing Logic (`AntiKirby_StealItem`)
This is a separate routine that is likely called when Anti Kirby successfully "sucks" Link. It checks Link's inventory and steals a random item (bomb, arrow, rupee, or shield).
```asm
AntiKirby_StealItem:
{
REP #$20
; ... (collision checks) ...
SEP #$20
LDA.w SprTimerA, X : CMP.b #$2E : BCS .exit ; Timer check
JSL GetRandomInt
AND.b #$03
INC A
STA.w SprMiscG, X
STA.w SprMiscE, X
CMP.b #$01 : BNE .dont_steal_bomb
LDA.l $7EF343 : BEQ .dont_steal_anything ; Check bombs
DEC A
STA.l $7EF343
RTS
.dont_steal_anything
SEP #$20
STZ.w SprMiscG,X
RTS
.dont_steal_bomb
CMP.b #$02 : BNE .dont_steal_arrow
LDA.l $7EF377 : BEQ .dont_steal_anything ; Check arrows
DEC A
STA.l $7EF377
RTS
.dont_steal_arrow
CMP.b #$03 : BNE .dont_steal_rupee
REP #$20
LDA.l $7EF360 : BEQ .dont_steal_anything ; Check rupees
DEC A
STA.l $7EF360
.exit
SEP #$20
RTS
; -----------------------------------------------------
.dont_steal_rupee
LDA.l $7EF35A : STA.w SprSubtype, X : BEQ .dont_steal_anything ; Check shield
CMP.b #$03 : BEQ .dont_steal_anything
LDA.b #$00
STA.l $7EF35A
RTS
}
```
**Key Observations:**
* Uses `REP #$20` and `SEP #$20` to explicitly control the accumulator size (16-bit for address calculations, 8-bit for item counts). This is crucial for correct memory access.
* Randomly selects an item to steal using `JSL GetRandomInt : AND.b #$03 : INC A`.
* Directly modifies SRAM addresses (`$7EF343` for bombs, `$7EF377` for arrows, `$7EF360` for rupees, `$7EF35A` for shield) to decrement item counts or remove the shield.
* The shield stealing logic (`LDA.l $7EF35A : STA.w SprSubtype, X : BEQ .dont_steal_anything : CMP.b #$03 : BEQ .dont_steal_anything : LDA.b #$00 : STA.l $7EF35A`) is a bit convoluted. It seems to check the shield type and only steals if it's not a specific type (possibly the Mirror Shield, which is type 3).
**Insight:** The `AntiKirby_StealItem` routine is a good example of how to interact directly with Link's inventory in SRAM. It also highlights the importance of explicitly managing the processor status flags (`REP`/`SEP`) when dealing with mixed 8-bit and 16-bit operations, especially when accessing memory.
## 7. Drawing (`Sprite_AntiKirby_Draw`)
The drawing routine uses `JSL Sprite_PrepOamCoord` and `JSL Sprite_OAM_AllocateDeferToPlayer` for OAM management. It then uses a series of tables (`.start_index`, `.nbr_of_tiles`, `.x_offsets`, `.y_offsets`, `.chr`, `.properties`, `.sizes`) to define the sprite's animation frames and tile data.
```asm
Sprite_AntiKirby_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprGfx, X : CLC : ADC.w SprFrame, X : TAY;Animation Frame
LDA .start_index, Y : STA $06
LDA.w SprFlash, X : STA $08
LDA.w SprMiscC, X : STA $09
PHX
LDX .nbr_of_tiles, Y ;amount of tiles -1
LDY.b #$00
.nextTile
; ... (OAM manipulation logic) ...
.start_index
db $00, $01, $02, $03, $04, $05, $06, $08, $0A, $0C, $0E, $10
.nbr_of_tiles
db 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1
; ... (other OAM tables) ...
}
```
**Key Observations:**
* The drawing logic includes a check for `SprMiscC, X` to determine if the sprite is facing left or right, and uses different `.x_offsets` tables (`.x_offsets` vs `.x_offsets_2`) accordingly. This is a common pattern for horizontal flipping.
* The `.properties` table defines the palette, priority, and flip bits for each tile.
* The `.sizes` table defines the size of each tile (e.g., `$02` for 16x16).
## 8. Advanced Design Patterns Demonstrated
* **Dynamic Difficulty Scaling:** The `_Prep` routine adjusts health, bump damage, and prize based on `Link's Sword` level.
* **Complex State Machine:** The `_Main` routine uses a jump table to manage multiple distinct behaviors (walking, sucking, full, hatted, hurt, death).
* **Direct SRAM Interaction:** The `AntiKirby_StealItem` routine directly modifies Link's inventory in SRAM, demonstrating how to implement item-related mechanics.
* **Explicit Processor Status Management:** The `AntiKirby_StealItem` routine explicitly uses `REP #$20` and `SEP #$20` to ensure correct 8-bit/16-bit operations when accessing SRAM.
* **Conditional Drawing/Flipping:** The `_Draw` routine uses `SprMiscC, X` to conditionally flip the sprite horizontally.

View File

@@ -0,0 +1,290 @@
# Booki Sprite Analysis
This document provides a detailed analysis of the `booki.asm` sprite, outlining its properties, core routines, and behavioral patterns.
## 1. Sprite Properties
The following `!SPRID` constants define Booki's fundamental characteristics:
```asm
!SPRID = Sprite_Booki
!NbrTiles = 02 ; Number of tiles used in a frame
!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = 00 ; Number of Health the sprite have (dynamically set in _Prep)
!Damage = 00 ; (08 is a whole heart), 04 is half heart
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 00 ; Unused in this template (can be 0 to 7)
!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = your sprite continue to live offscreen
!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layer for collision
!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk shallow water
!Blockable = 00 ; 01 = can be blocked by link's shield?
!Prize = 00 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
```
**Note:** `!Health` and `!Damage` are initially set to `00` but are dynamically determined during initialization.
## 2. Core Routines
### 2.1. `Sprite_Booki_Long` (Main Loop)
This is the primary entry point for Booki's per-frame execution, called by the game engine. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active.
```asm
Sprite_Booki_Long:
{
PHB : PHK : PLB ; Set up bank registers
JSR Sprite_Booki_Draw ; Call drawing routine
JSL Sprite_DrawShadow ; Draw a shadow (if !Shadow is 01)
JSL Sprite_CheckActive : BCC .SpriteIsNotActive ; Check if sprite is active
JSR Sprite_Booki_Main ; If active, run main logic
.SpriteIsNotActive
PLB ; Restore bank register
RTL ; Return from long routine
}
```
### 2.2. `Sprite_Booki_Prep` (Initialization)
This routine is executed once when Booki is first spawned. It dynamically sets Booki's health based on Link's current sword level and initializes `SprMiscB`.
```asm
Sprite_Booki_Prep:
{
PHB : PHK : PLB
LDA.l Sword : DEC A : TAY ; Get Link's sword level (0-3), adjust to 0-indexed
LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level
STZ.w SprMiscB, X ; Initialize SprMiscB to 0
PLB
RTL
.health ; Health values for each sword level
db $04, $08, $10, $18 ; 4, 8, 16, 24 HP
}
```
### 2.3. `Sprite_Booki_Main` (Behavioral State Machine)
This routine manages Booki's AI through a state machine, using `SprAction, X` to determine the current behavior. It utilizes `JumpTableLocal` for efficient state transitions.
```asm
Sprite_Booki_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal ; Jump to the routine specified by SprAction
dw StalkPlayer ; State 0
dw HideFromPlayer ; State 1
dw HiddenFromPlayer ; State 2
dw ApproachPlayer ; State 3
StalkPlayer:
{
%PlayAnimation(0,1,16) ; Animate frames 0-1 every 16 frames
JSR Sprite_Booki_Move ; Handle movement
RTS
}
HideFromPlayer:
{
%PlayAnimation(0,4,16) ; Animate frames 0-4 every 16 frames
LDA.w SprTimerA, X : BNE + ; Check timer
INC.w SprAction, X ; If timer is 0, transition to HiddenFromPlayer
+
RTS
}
HiddenFromPlayer:
{
%PlayAnimation(4,4,16) ; Animate frame 4 every 16 frames (static)
JSR Sprite_Booki_Move ; Handle movement
JSL GetRandomInt : AND.b #$03 : BEQ + ; Random chance to transition
INC.w SprAction, X ; If random condition met, transition to ApproachPlayer
+
RTS
}
ApproachPlayer:
{
%PlayAnimation(5,9,16) ; Animate frames 5-9 every 16 frames
JSR Sprite_Booki_Move ; Handle movement
RTS
}
}
```
### 2.4. `Sprite_Booki_Move` (Movement and Interaction Logic)
This routine is called by the various states in `Sprite_Booki_Main` to handle Booki's physical interactions and movement. It also manages Booki's "float" behavior (`SlowFloat` or `FloatAway`) based on `SprMiscB`.
```asm
Sprite_Booki_Move:
{
JSL Sprite_Move ; Apply velocity
JSL Sprite_BounceFromTileCollision ; Handle collision with tiles
JSL Sprite_PlayerCantPassThrough ; Prevent player from passing through Booki
JSL Sprite_DamageFlash_Long ; Handle damage flashing
JSL Sprite_CheckIfRecoiling ; Check for recoil state
JSL Sprite_IsToRightOfPlayer : CPY.b #$01 : BNE .ToRight ; Determine if Booki is to the right of Link
LDA.b #$01 : STA.w SprMiscC, X ; Set SprMiscC to 1 (for horizontal flip)
JMP .Continue
.ToRight
STZ.w SprMiscC, X ; Set SprMiscC to 0 (no flip)
.Continue
JSL Sprite_CheckDamageToPlayer ; Check if Booki damages Link
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage ; Check if Link damages Booki
LDA.b #$01 : STA.w SprMiscB, X ; If damaged, set SprMiscB to 1 (FloatAway state)
.no_damage
LDA.w SprMiscB, X
JSL JumpTableLocal ; Jump to movement routine based on SprMiscB
dw SlowFloat ; SprMiscB = 0
dw FloatAway ; SprMiscB = 1
SlowFloat:
{
LDY #$04
JSL GetRandomInt : AND.b #$04 ; Introduce some randomness to movement
JSL Sprite_FloatTowardPlayer ; Float towards Link
PHX
JSL Sprite_DirectionToFacePlayer ; Update facing direction
; Check if too close to player
LDA.b $0E : CMP.b #$1A : BCS .NotTooClose
LDA.b $0F : CMP.b #$1A : BCS .NotTooClose
LDA.b #$01 : STA.w SprMiscB, X ; If too close, switch to FloatAway
LDA.b #$20 : STA.w SprTimerA, X ; Set timer
%GotoAction(1) ; Transition to HideFromPlayer state
.NotTooClose
PLX
RTS
}
FloatAway:
{
JSL GetRandomInt : AND.b #$04 ; Introduce some randomness to movement
JSL Sprite_FloatAwayFromPlayer ; Float away from Link
PHX
JSL Sprite_DirectionToFacePlayer ; Update facing direction
; Check if far enough from player
LDA.b $0E : CMP.b #$1B : BCC .NotTooClose
LDA.b #$1B : CMP.b $0F : BCC .NotTooClose ; Corrected comparison for $0F
LDA.b #$00 : STA.w SprMiscB, X ; If far enough, switch to SlowFloat
%GotoAction(0) ; Transition to StalkPlayer state
.NotTooClose
PLX
RTS
}
}
```
### 2.5. `Sprite_Booki_Draw` (Drawing Routine)
This routine is responsible for rendering Booki's graphics. It uses a custom OAM (Object Attribute Memory) allocation and manipulation logic rather than the `%DrawSprite()` macro. It dynamically determines the animation frame and applies horizontal flipping based on `SprMiscC`.
```asm
Sprite_Booki_Draw:
{
JSL Sprite_PrepOamCoord ; Prepare OAM coordinates
JSL Sprite_OAM_AllocateDeferToPlayer ; Allocate OAM slots, deferring to player
LDA.w SprGfx, X : CLC : ADC.w SprFrame, X : TAY ; Calculate animation frame index
LDA .start_index, Y : STA $06 ; Store start index for tiles
LDA.w SprFlash, X : STA $08 ; Store flash status
LDA.w SprMiscC, X : STA $09 ; Store horizontal flip status (0 or 1)
PHX
LDX .nbr_of_tiles, Y ; Load number of tiles for current frame (minus 1)
LDY.b #$00 ; Initialize Y for OAM buffer offset
.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 ; Multiply by 2 for word access
REP #$20 ; Set A to 16-bit mode
LDA $00 : STA ($90), Y ; Store Y-coordinate
AND.w #$0100 : STA $0E ; Check if Y-coord is off-screen (high bit)
INY
LDA $02 : STA ($90), Y ; Store X-coordinate
CLC : ADC #$0010 : CMP.w #$0100 ; Check if X-coord is off-screen
SEP #$20 ; Set A to 8-bit mode
BCC .on_screen_y ; If on screen, continue
LDA.b #$F0 : STA ($90), Y ; If off-screen, move sprite off-screen
STA $0E
.on_screen_y
PLX ; Restore Tile Index
INY
LDA .chr, X : STA ($90), Y ; Store character (tile) number
INY
LDA.b $09 : BEQ .ToRight ; Check SprMiscC for horizontal flip
LDA.b #$29 : JMP .Prop ; If 1, use properties for flipped
.ToRight
LDA.b #$69 ; If 0, use properties for normal
.Prop
ORA $08 : STA ($90), Y ; Apply flash and store OAM properties
PHY
TYA : LSR #2 : TAY ; Calculate OAM buffer index for size
LDA.b #$02 : ORA $0F : STA ($92), Y ; Store size (16x16) in OAM buffer
PLY : INY
PLX : DEX : BPL .nextTile ; Loop for next tile
PLX ; Restore X (sprite index)
RTS
; =========================================================
; OAM Data Tables
.start_index
db $00, $01, $02, $03, $04, $05, $06, $07, $08, $09
.nbr_of_tiles
db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ; All frames use 1 tile (0-indexed)
.chr
db $0E, $0C, $0A, $2C, $2E, $2E, $0A, $2C, $0C, $0E ; Tile numbers for each frame
}
```
## 3. Key Behaviors and Implementation Details
* **Dynamic Health:** Booki's health is not a fixed property but is determined at spawn time based on Link's current sword level. This allows for dynamic difficulty scaling.
* **State Management:** Booki employs a robust state machine using `SprAction, X` and `JumpTableLocal` to manage its behaviors: `StalkPlayer`, `HideFromPlayer`, `HiddenFromPlayer`, and `ApproachPlayer`. Transitions between these states are triggered by timers, random chance, or player proximity.
* **Player Interaction:**
* **Stalking/Approaching:** Booki uses `Sprite_FloatTowardPlayer` to move towards Link.
* **Hiding/Floating Away:** Booki uses `Sprite_FloatAwayFromPlayer` to retreat from Link, often triggered by taking damage or getting too close.
* **Damage:** Booki can damage Link on contact (`Sprite_CheckDamageToPlayer`) and reacts to damage from Link by transitioning to a `FloatAway` state.
* **Directional Facing:** `SprMiscC, X` is used as a flag to control horizontal flipping in the drawing routine, ensuring Booki always faces Link.
* **Custom OAM Drawing:** Unlike many sprites that might use the `%DrawSprite()` macro, Booki implements its OAM drawing logic directly. This provides fine-grained control over its appearance, including dynamic tile selection and horizontal flipping. The `REP #$20` and `SEP #$20` instructions are used to temporarily switch the accumulator to 16-bit mode for coordinate calculations, demonstrating careful management of the Processor Status Register.
* **Randomness:** `GetRandomInt` is used to introduce variability in Booki's movement patterns and state transitions, making its behavior less predictable.
* **`SprMiscB` Usage:** This variable acts as a sub-state for movement, toggling between `SlowFloat` (approaching) and `FloatAway` (retreating) behaviors.
* **`SprTimerA` Usage:** Used in the `HideFromPlayer` state to control how long Booki remains in that state before transitioning.
* **`Sprite_PlayerCantPassThrough`:** Ensures Booki acts as a solid object that Link cannot simply walk through.

View File

@@ -0,0 +1,122 @@
# Business Scrub
## Overview
The Business Scrub is a custom enemy sprite that likely overrides a vanilla sprite ID. It is characterized by its low health and harmless contact damage, suggesting its primary threat comes from projectiles or other interactions defined within its main logic.
## Sprite Properties
* **`!SPRID`**: `$00` (Vanilla sprite ID, likely overridden)
* **`!NbrTiles`**: `$02`
* **`!Health`**: `$01`
* **`!Damage`**: `$00` (Harmless contact)
* **`!Harmless`**: `$00`
* **`!Hitbox`**: `$08`
* **`!ImperviousAll`**: `$00`
* **`!Statue`**: `$00`
* **`!Prize`**: `$00`
* **`!Boss`**: `$00`
* **Collision Properties**: All collision-related properties (`!Defl`, `!SprColl`, etc.) are set to `$00`, indicating that direct contact damage and knockback are not handled by these properties. Interaction and damage are likely managed within the sprite's main logic.
## Main Structure (`Sprite_BusinessScrub_Long`)
This routine is the main entry point for the Business Scrub, executed every frame. It sets up bank registers, calls the drawing routine, and then executes the main logic if the sprite is active.
```asm
Sprite_BusinessScrub_Long:
{
PHB : PHK : PLB
JSR Sprite_BusinessScrub_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_BusinessScrub_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_BusinessScrub_Prep`)
This routine runs once when the Business Scrub is spawned. It initializes the sprite's action state to `0` and sets a general-purpose timer (`SprTimerA`) to `120` frames (2 seconds).
```asm
Sprite_BusinessScrub_Prep:
{
PHB : PHK : PLB
%GotoAction(0)
%SetTimerA(120)
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_BusinessScrub_Main`)
The core behavior of the Business Scrub is managed by a state machine using `%SpriteJumpTable`. The current states are:
* **`State_Idle`**: The initial state where the scrub is idle. It plays an animation and checks for player proximity. If Link is within 80 pixels, it transitions to `State_Attacking`.
* **`State_Attacking`**: In this state, the scrub plays an attack animation, moves towards the player, and deals damage on contact. It also checks if it has been hit by the player and transitions to `State_Hurt` if so.
* **`State_Hurt`**: This state handles the sprite being hit, causing it to flash and be knocked back.
```asm
Sprite_BusinessScrub_Main:
{
%SpriteJumpTable(State_Idle, State_Attacking, State_Hurt)
State_Idle:
{
%PlayAnimation(0, 1, 15)
JSL GetDistance8bit_Long : CMP.b #$50 : BCS .player_is_far
%GotoAction(1)
.player_is_far
RTS
}
State_Attacking:
{
%PlayAnimation(2, 3, 8)
%MoveTowardPlayer(12)
%DoDamageToPlayerSameLayerOnContact()
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
%GotoAction(2)
.no_damage
RTS
}
State_Hurt:
{
JSL Sprite_DamageFlash_Long
RTS
}
}
```
## Drawing (`Sprite_BusinessScrub_Draw`)
The drawing routine uses the `%DrawSprite()` macro to render the sprite's graphics based on defined OAM data tables.
```asm
Sprite_BusinessScrub_Draw:
{
%DrawSprite()
.start_index
db $00, $02, $04, $06
.nbr_of_tiles
db 1, 1, 1, 1
.x_offsets
dw -8, 8, -8, 8, -8, 8, -8, 8
.y_offsets
dw -8, -8, -8, -8, -8, -8, -8, -8
.chr
db $C0, $C2, $C4, $C6, $C8, $CA, $CC, $CE
.properties
db $3B, $7B, $3B, $7B, $3B, $7B, $3B, $7B
}
```
## Design Patterns
* **State Machine**: Utilizes a clear state machine (`%SpriteJumpTable`) for managing different behaviors (Idle, Attacking, Hurt).
* **Player Interaction**: Incorporates distance checks (`GetDistance8bit_Long`) to trigger state changes and direct damage on contact (`DoDamageToPlayerSameLayerOnContact`).
* **Damage Handling**: Includes a basic damage reaction (`Sprite_CheckDamageFromPlayer`, `Sprite_DamageFlash_Long`).
* **Vanilla ID Override**: The use of `!SPRID = $00` suggests this sprite is intended to replace or modify the behavior of a vanilla sprite with ID $00.

View File

@@ -0,0 +1,328 @@
# Darknut Sprite Analysis
This document provides a detailed analysis of the `darknut.asm` sprite, outlining its properties, core routines, and behavioral patterns.
## 1. Sprite Properties
The following `!SPRID` constants define Darknut's fundamental characteristics:
```asm
!SPRID = Sprite_Darknut
!NbrTiles = 03 ; Number of tiles used in a frame
!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = 12 ; Number of Health the sprite have (dynamically set in _Prep)
!Damage = 00 ; (08 is a whole heart), 04 is half heart
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 00 ; Unused in this template (can be 0 to 7)
!Hitbox = 12 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = your sprite continue to live offscreen
!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layer for collision
!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk shallow water
!Blockable = 00 ; 01 = can be blocked by link's shield?
!Prize = 00 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
```
**Note:** `!Health` is initially set to `12` but is dynamically determined during initialization based on Link's sword level. `!Damage` is `00`, implying damage is handled through other means (e.g., contact with Link's sword).
## 2. Core Routines
### 2.1. `Sprite_Darknut_Long` (Main Loop)
This is the primary entry point for Darknut's per-frame execution, called by the game engine. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active.
```asm
Sprite_Darknut_Long:
{
PHB : PHK : PLB ; Set up bank registers
JSR Sprite_Darknut_Draw ; Call drawing routine
JSL Sprite_DrawShadow ; Draw a shadow (if !Shadow is 01)
JSL Sprite_CheckActive : BCC .SpriteIsNotActive ; Check if sprite is active
JSR Sprite_Darknut_Main ; If active, run main logic
.SpriteIsNotActive
PLB ; Restore bank register
RTL ; Return from long routine
}
```
### 2.2. `Sprite_Darknut_Prep` (Initialization)
This routine is executed once when Darknut is first spawned. It dynamically sets Darknut's health based on Link's current sword level, initializes `SprDefl` (deflection timer), and `SprTileDie` (tile for death animation).
```asm
Sprite_Darknut_Prep:
{
PHB : PHK : PLB
LDA.l $7EF359 : TAY ; Get Link's sword level (0-3), adjust to 0-indexed
LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level
LDA.b #$80 : STA.w SprDefl, X ; Initialize deflection timer
LDA.b #%01100000 : STA.w SprTileDie, X ; Set tile for death animation
PLB
RTL
.health ; Health values for each sword level
db $04, $06, $08, $0A ; 4, 6, 8, 10 HP
}
```
### 2.3. `Sprite_Darknut_Main` (Behavioral Logic)
This routine manages Darknut's AI, including probe spawning, parrying, movement, and animation. It uses `SprAction, X` to control its facing direction and animation.
```asm
Sprite_Darknut_Main:
{
JSL GetDistance8bit_Long : CMP.b #$80 : BCS .no_probe ; Check distance to Link
JSL Sprite_SpawnProbeAlways_long ; If close, spawn a probe
.no_probe
JSL Guard_ParrySwordAttacks ; Handle parrying Link's sword attacks
JSL Sprite_Move ; Apply velocity
JSL Sprite_BounceFromTileCollision ; Handle collision with tiles
JSL Sprite_DamageFlash_Long ; Handle damage flashing
JSL Sprite_CheckIfRecoiling ; Check for recoil state
JSL Sprite_CheckDamageFromPlayer : BCC .no_dano ; Check if Link damages Darknut
LDA.b #$FF : STA.w SprTimerD, X ; If damaged, set timer D
.no_dano
LDA.w SprTimerA, X : BEQ + ; Check timer A
LDA.b #$90 : STA.w SprTimerD, X ; If timer A is not 0, set timer D
+
LDA.w SprTimerD, X : BEQ ++ ; Check timer D
LDA.b #$08 : JSL Sprite_ApplySpeedTowardsPlayer ; Apply speed towards Link
JSL Sprite_DirectionToFacePlayer ; Update facing direction
TYA
STA.w SprMiscC, X ; Store facing direction in SprMiscC
STA.w SprMiscE, X ; Store facing direction in SprMiscE
STA.w SprAction, X ; Set SprAction to facing direction
JSL Guard_ChaseLinkOnOneAxis ; Chase Link along one axis
JMP +++
++
JSR Sprite_Darknut_BasicMove ; If no timers, use basic movement
+++
JSR Goriya_HandleTileCollision ; Handle tile collision (specific to Goriya, but used here)
LDA.w SprAction, X
JSL JumpTableLocal ; Jump to animation routine based on SprAction
dw FaceRight
dw FaceLeft
dw FaceDown
dw FaceUp
FaceUp:
{
%PlayAnimation(0,1,10) ; Animate frames 0-1 every 10 frames
RTS
}
FaceDown:
{
%StartOnFrame(2)
%PlayAnimation(2,3,10) ; Animate frames 2-3 every 10 frames
RTS
}
FaceLeft:
{
%StartOnFrame(4)
%PlayAnimation(4,5,10) ; Animate frames 4-5 every 10 frames
RTS
}
FaceRight:
{
%StartOnFrame(6)
%PlayAnimation(6,7,10) ; Animate frames 6-7 every 10 frames
RTS
}
}
```
### 2.4. `Sprite_Darknut_BasicMove` (Basic Movement Logic)
This routine defines Darknut's basic movement patterns, which are executed when no special conditions (like being damaged or timers) are active. It uses `SprAction, X` to determine the current movement direction.
```asm
Sprite_Darknut_BasicMove:
{
LDA.w SprAction, X
JSL JumpTableLocal ; Jump to movement routine based on SprAction
dw MoveRight
dw MoveLeft
dw MoveDown
dw MoveUp
MoveUp:
{
LDA.b #-DarknutSpeed : STA.w SprYSpeed, X ; Set Y-speed to negative (move up)
STZ.w SprXSpeed, X ; Clear X-speed
RTS
}
MoveDown:
{
LDA.b #DarknutSpeed : STA.w SprYSpeed, X ; Set Y-speed to positive (move down)
STZ.w SprXSpeed, X ; Clear X-speed
RTS
}
MoveLeft:
{
LDA.b #-DarknutSpeed : STA.w SprXSpeed, X ; Set X-speed to negative (move left)
STZ.w SprYSpeed, X ; Clear Y-speed
RTS
}
MoveRight:
{
LDA.b #DarknutSpeed : STA.w SprXSpeed, X ; Set X-speed to positive (move right)
STZ.w SprYSpeed, X ; Clear Y-speed
RTS
}
}
```
### 2.5. `Sprite_Darknut_Draw` (Drawing Routine)
This routine is responsible for rendering Darknut's graphics. It uses a custom OAM (Object Attribute Memory) allocation and manipulation logic, similar to Booki, to handle its multi-tile appearance and animation. It dynamically determines the animation frame and applies offsets for each tile.
```asm
Sprite_Darknut_Draw:
{
JSL Sprite_PrepOamCoord ; Prepare OAM coordinates
JSL Sprite_OAM_AllocateDeferToPlayer ; Allocate OAM slots, deferring to player
LDA.w SprGfx, X : CLC : ADC $0D90, X : TAY ; Calculate animation frame index
LDA .start_index, Y : STA $06 ; Store start index for tiles
LDA.w SprFlash, X : STA $08 ; Store flash status
PHX
LDX .nbr_of_tiles, Y ; Load number of tiles for current frame (minus 1)
LDY.b #$00 ; Initialize Y for OAM buffer offset
.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 ; Multiply by 2 for word access
REP #$20 ; Set A to 16-bit mode
LDA $00 : CLC : ADC .x_offsets, X : STA ($90), Y ; Store Y-coordinate with X-offset
AND.w #$0100 : STA $0E ; Check if Y-coord is off-screen (high bit)
INY
LDA $02 : CLC : ADC .y_offsets, X : STA ($90), Y ; Store X-coordinate with Y-offset
CLC : ADC #$0010 : CMP.w #$0100
SEP #$20 ; Set A to 8-bit mode
BCC .on_screen_y
LDA.b #$F0 : STA ($90), Y ; If off-screen, move sprite off-screen
STA $0E
.on_screen_y
PLX ; Restore Tile Index
INY
LDA .chr, X : STA ($90), Y ; Store character (tile) number
INY
LDA .properties, X : ORA $08 : STA ($90), Y ; Apply flash and store OAM properties
PHY
TYA : LSR #2 : TAY ; Calculate OAM buffer index for size
LDA.w .sizes, X : ORA $0F : STA ($92), Y ; store size in oam buffer
PLY : INY
PLX : DEX : BPL .nextTile ; Loop for next tile
PLX ; Restore X (sprite index)
RTS
; =========================================================
; OAM Data Tables
.start_index
db $00, $03, $06, $09, $0C, $0E, $10, $12
.nbr_of_tiles
db 2, 2, 2, 2, 1, 1, 1, 1
.x_offsets
dw 0, 0, 0
dw 0, 0, 0
dw 0, 0, 0
dw 0, 0, 0
dw 0, -12
dw 0, -12
dw 0, 12
dw 0, 12
.y_offsets
dw -4, 0, -12
dw -4, 0, -12
dw 0, 12, 20
dw 0, 12, 20
dw 0, 8
dw 0, 8
dw 0, 8
dw 0, 8
.chr
db $EF, $E6, $FF
db $EF, $E6, $FF
db $E2, $EF, $FF
db $E2, $EF, $FF
db $E0, $E8
db $E4, $E8
db $E0, $E8
db $E4, $E8
.properties
db $B9, $39, $B9
db $B9, $79, $B9
db $39, $39, $39
db $79, $39, $39
db $39, $79
db $39, $79
db $79, $39
db $79, $39
.sizes
db $00, $02, $00
db $00, $02, $00
db $02, $00, $00
db $02, $00, $00
db $02, $02
db $02, $02
db $02, $02
db $02, $02
}
```
## 3. Key Behaviors and Implementation Details
* **Dynamic Health:** Darknut's health is determined at spawn time based on Link's current sword level, similar to Booki, allowing for dynamic difficulty scaling.
* **Probe Spawning:** Darknut has the ability to spawn a probe (`Sprite_SpawnProbeAlways_long`) when Link is within a certain distance, adding a ranged attack or detection mechanism.
* **Parrying Mechanics:** The `Guard_ParrySwordAttacks` routine suggests Darknut can actively defend against Link's sword attacks, potentially deflecting them or becoming temporarily invulnerable.
* **Chasing on One Axis:** When damaged or under certain timer conditions, Darknut uses `Guard_ChaseLinkOnOneAxis` to pursue Link along either the horizontal or vertical axis, making its movement more predictable but still challenging.
* **Basic Movement:** Darknut has a set of basic directional movements (`MoveUp`, `MoveDown`, `MoveLeft`, `MoveRight`) that it cycles through when not actively chasing or reacting to damage.
* **Custom OAM Drawing:** Darknut utilizes a custom OAM drawing routine, similar to Booki, to handle its multi-tile sprite. This routine precisely positions and animates multiple 8x8 tiles to form the larger Darknut sprite. The use of `REP #$20` and `SEP #$20` for 16-bit coordinate calculations is also present here.
* **`SprDefl` and `SprTileDie`:** `SprDefl` is used as a deflection timer, likely related to the parrying mechanic. `SprTileDie` specifies a custom tile to be used during its death animation.
* **`SprMiscC` and `SprMiscE`:** These variables are used to store Darknut's facing direction, which influences both its movement and animation. `SprMiscC` is likely used for animation frame selection, while `SprMiscE` might be used for other directional logic.
* **`Goriya_HandleTileCollision`:** The use of a collision handler named `Goriya_HandleTileCollision` suggests code reuse from another sprite, indicating a shared collision logic for certain enemy types.

View File

@@ -0,0 +1,122 @@
# Eon Scrub
## Overview
The Eon Scrub is a custom enemy sprite, likely a variation of the Business Scrub, that overrides a vanilla sprite ID. It shares similar characteristics with the Business Scrub, including low health and harmless contact damage, implying its primary threat comes from projectiles or other interactions defined within its main logic.
## Sprite Properties
* **`!SPRID`**: `$00` (Vanilla sprite ID, likely overridden)
* **`!NbrTiles`**: `$02`
* **`!Health`**: `$01`
* **`!Damage`**: `$00` (Harmless contact)
* **`!Harmless`**: `$00`
* **`!Hitbox`**: `$08`
* **`!ImperviousAll`**: `$00`
* **`!Statue`**: `$00`
* **`!Prize`**: `$00`
* **`!Boss`**: `$00`
* **Collision Properties**: All collision-related properties (`!Defl`, `!SprColl`, etc.) are set to `$00`, indicating that direct contact damage and knockback are not handled by these properties. Interaction and damage are likely managed within the sprite's main logic.
## Main Structure (`Sprite_EonScrub_Long`)
This routine is the main entry point for the Eon Scrub, executed every frame. It sets up bank registers, calls the drawing routine, and then executes the main logic if the sprite is active.
```asm
Sprite_EonScrub_Long:
{
PHB : PHK : PLB
JSR Sprite_EonScrub_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_EonScrub_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_EonScrub_Prep`)
This routine runs once when the Eon Scrub is spawned. It initializes the sprite's action state to `0` and sets a general-purpose timer (`SprTimerA`) to `120` frames (2 seconds).
```asm
Sprite_EonScrub_Prep:
{
PHB : PHK : PLB
%GotoAction(0)
%SetTimerA(120)
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_EonScrub_Main`)
The core behavior of the Eon Scrub is managed by a state machine using `%SpriteJumpTable`. The current states are:
* **`State_Idle`**: The initial state where the scrub is idle. It plays an animation and checks for player proximity. If Link is within 80 pixels, it transitions to `State_Attacking`.
* **`State_Attacking`**: In this state, the scrub plays an attack animation, moves towards the player, and deals damage on contact. It also checks if it has been hit by the player and transitions to `State_Hurt` if so.
* **`State_Hurt`**: This state handles the sprite being hit, causing it to flash and be knocked back.
```asm
Sprite_EonScrub_Main:
{
%SpriteJumpTable(State_Idle, State_Attacking, State_Hurt)
State_Idle:
{
%PlayAnimation(0, 1, 15)
JSL GetDistance8bit_Long : CMP.b #$50 : BCS .player_is_far
%GotoAction(1)
.player_is_far
RTS
}
State_Attacking:
{
%PlayAnimation(2, 3, 8)
%MoveTowardPlayer(12)
%DoDamageToPlayerSameLayerOnContact()
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
%GotoAction(2)
.no_damage
RTS
}
State_Hurt:
{
JSL Sprite_DamageFlash_Long
RTS
}
}
```
## Drawing (`Sprite_EonScrub_Draw`)
The drawing routine uses the `%DrawSprite()` macro to render the sprite's graphics based on defined OAM data tables.
```asm
Sprite_EonScrub_Draw:
{
%DrawSprite()
.start_index
db $00, $02, $04, $06
.nbr_of_tiles
db 1, 1, 1, 1
.x_offsets
dw -8, 8, -8, 8, -8, 8, -8, 8
.y_offsets
dw -8, -8, -8, -8, -8, -8, -8, -8
.chr
db $C0, $C2, $C4, $C6, $C8, $CA, $CC, $CE
.properties
db $3B, $7B, $3B, $7B, $3B, $7B, $3B, $7B
}
```
## Design Patterns
* **State Machine**: Utilizes a clear state machine (`%SpriteJumpTable`) for managing different behaviors (Idle, Attacking, Hurt).
* **Player Interaction**: Incorporates distance checks (`GetDistance8bit_Long`) to trigger state changes and direct damage on contact (`DoDamageToPlayerSameLayerOnContact`).
* **Damage Handling**: Includes a basic damage reaction (`Sprite_CheckDamageFromPlayer`, `Sprite_DamageFlash_Long`).
* **Vanilla ID Override**: The use of `!SPRID = $00` suggests this sprite is intended to replace or modify the behavior of a vanilla sprite with ID $00.

View File

@@ -0,0 +1,469 @@
# Goriya Sprite Analysis
This document provides a detailed analysis of the `goriya.asm` sprite, outlining its properties, core routines, and behavioral patterns.
## 1. Sprite Properties
The following `!SPRID` constants define Goriya's fundamental characteristics:
```asm
!SPRID = Sprite_Goriya
!NbrTiles = 03 ; Number of tiles used in a frame
!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = 00 ; Number of Health the sprite have (dynamically set in _Prep)
!Damage = 00 ; (08 is a whole heart), 04 is half heart
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 00 ; Unused in this template (can be 0 to 7)
!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = your sprite continue to live offscreen
!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layer for collision
!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk shallow water
!Blockable = 00 ; 01 = can be blocked by link's shield?
!Prize = 00 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
```
**Note:** `!Health` and `!Damage` are initially set to `00` but are dynamically determined during initialization.
## 2. Core Routines
### 2.1. `Sprite_Goriya_Long` (Main Loop)
This is the primary entry point for Goriya's per-frame execution, called by the game engine. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active. Notably, it checks `SprSubtype, X` to determine if it's the main Goriya sprite or a boomerang, and calls the appropriate drawing routine.
```asm
Sprite_Goriya_Long:
{
PHB : PHK : PLB
LDA.w SprSubtype, X : BEQ + ; Check SprSubtype
JSR Sprite_Boomerang_Draw ; If SprSubtype is not 0, draw as boomerang
JMP ++
+
JSR Sprite_Goriya_Draw ; If SprSubtype is 0, draw as Goriya
JSL Sprite_DrawShadow
++
JSL Sprite_CheckActive : BCC .SpriteIsNotActive ; Check if sprite is active
JSR Sprite_Goriya_Main ; If active, run main logic
.SpriteIsNotActive
PLB
RTL
}
```
### 2.2. `Sprite_Goriya_Prep` (Initialization)
This routine is executed once when Goriya is first spawned. It sets Goriya's health to `08` (one heart).
```asm
Sprite_Goriya_Prep:
{
PHB : PHK : PLB
LDA.b #$08 : STA.w SprHealth, X ; Set health to 8
PLB
RTL
}
```
### 2.3. `Sprite_Goriya_Main` (Behavioral State Machine)
This routine manages Goriya's AI through a state machine, using `SprAction, X` to determine the current behavior. It utilizes `JumpTableLocal` for efficient state transitions, including walking in different directions and a boomerang attack state.
```asm
Sprite_Goriya_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal ; Jump to the routine specified by SprAction
dw Goriya_WalkingUp
dw Goriya_WalkingDown
dw Goriya_WalkingLeft
dw Goriya_WalkingRight
dw BoomerangAttack
Goriya_WalkingUp:
{
%PlayAnimation(0, 1, 10) ; Animate frames 0-1 every 10 frames
JSR Sprite_Goriya_Move ; Handle movement
RTS
}
Goriya_WalkingDown:
{
%PlayAnimation(2, 3, 10) ; Animate frames 2-3 every 10 frames
JSR Sprite_Goriya_Move ; Handle movement
RTS
}
Goriya_WalkingLeft:
{
%StartOnFrame(4)
%PlayAnimation(4, 5, 10) ; Animate frames 4-5 every 10 frames
JSR Sprite_Goriya_Move ; Handle movement
RTS
}
Goriya_WalkingRight:
{
%StartOnFrame(6)
%PlayAnimation(6, 7, 10) ; Animate frames 6-7 every 10 frames
JSR Sprite_Goriya_Move ; Handle movement
RTS
}
BoomerangAttack:
{
%PlayAnimation(0, 3, 6) ; Animate frames 0-3 every 6 frames
LDA.w SprTimerD, X : BNE + ; Check timer D
LDA.b #$16
JSL Sprite_ApplySpeedTowardsPlayer ; Apply speed towards Link
%SetTimerD($50) ; Set timer D
+
JSL Sprite_Move ; Apply velocity
JSL Sprite_SpawnSparkleGarnish ; Spawn sparkle effect
JSL Sprite_CheckDamageToPlayer : BCC .no_dano ; Check if Goriya damages Link
LDA.b #$FF : STA.w SprTimerD, X ; If damaged, set timer D
JSL Sprite_InvertSpeed_XY ; Invert speed
.no_dano
JSL Sprite_CheckDamageFromPlayer : BCC + ; Check if Link damages Goriya
JSL Sprite_InvertSpeed_XY ; If damaged, invert speed
+
JSL Sprite_CheckTileCollision ; Check for tile collision
LDA.w SprCollision, X : BEQ + ; If no collision
STZ.w SprState, X ; Clear sprite state (despawn?)
+
RTS
}
}
```
### 2.4. `Sprite_Goriya_Move` (Movement and Interaction Logic)
This routine is called by the various walking states in `Sprite_Goriya_Main` to handle Goriya's physical interactions and movement. It also manages the logic for throwing a boomerang and changing movement directions.
```asm
Sprite_Goriya_Move:
{
JSL Sprite_Move
JSL Sprite_BounceFromTileCollision
JSL Sprite_PlayerCantPassThrough
JSL Sprite_DamageFlash_Long
JSL Sprite_CheckDamageToPlayer
JSL Sprite_CheckDamageFromPlayer
JSL Sprite_CheckIfRecoiling
JSR Goriya_HandleTileCollision ; Handle tile collision and change direction
LDA.w SprTimerD, X : BNE ++
JSL GetRandomInt : AND.b #$9F : BNE ++
LDA.b #$04 : STA.w SprMiscB, X ; Set SprMiscB for boomerang attack
%SetTimerD($FF)
JSR Goriya_BoomerangAttack ; Spawn boomerang
JMP +
++
LDA.w SprTimerC, X : BNE +
JSL GetRandomInt : AND.b #$03
STA.w SprMiscB, X ; Set SprMiscB for new movement direction
%SetTimerC(60)
+
LDA.w SprMiscB, X
JSL JumpTableLocal ; Jump to movement routine based on SprMiscB
dw Goriya_MoveUp
dw Goriya_MoveDown
dw Goriya_MoveLeft
dw Goriya_MoveRight
dw Goriya_Wait
Goriya_MoveUp:
{
LDA.b #-GoriyaMovementSpeed : STA.w SprYSpeed, X
STZ.w SprXSpeed, X
%GotoAction(0) ; Transition to Goriya_WalkingUp
LDA.b #$00 : STA.w SprMiscE, X
RTS
}
Goriya_MoveDown:
{
LDA.b #GoriyaMovementSpeed : STA.w SprYSpeed, X
STZ.w SprXSpeed, X
%GotoAction(1) ; Transition to Goriya_WalkingDown
LDA.b #$01 : STA.w SprMiscE, X
RTS
}
Goriya_MoveLeft:
{
STZ.w SprYSpeed, X
LDA.b #-GoriyaMovementSpeed : STA.w SprXSpeed, X
%GotoAction(2) ; Transition to Goriya_WalkingLeft
LDA.b #$02 : STA.w SprMiscE, X
RTS
}
Goriya_MoveRight:
{
STZ.w SprYSpeed, X
LDA.b #GoriyaMovementSpeed : STA.w SprXSpeed, X
%GotoAction(3) ; Transition to Goriya_WalkingRight
LDA.b #$03 : STA.w SprMiscE, X
RTS
}
Goriya_Wait:
{
STZ.w SprXSpeed, X
STZ.w SprYSpeed, X
%GotoAction(0) ; Transition to Goriya_WalkingUp (default)
RTS
}
}
```
### 2.5. `Goriya_HandleTileCollision`
This routine is called to handle Goriya's collision with tiles. Upon collision, it randomly selects a new movement direction and sets a timer (`SprTimerC`).
```asm
Goriya_HandleTileCollision:
{
JSL Sprite_CheckTileCollision
LDA.w SprCollision, X : BEQ ++
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X ; Randomly choose new direction
+
STA.w SprMiscE, X
%SetTimerC(60) ; Set timer C for 60 frames
++
RTS
}
```
### 2.6. `Goriya_BoomerangAttack`
This routine is responsible for spawning the boomerang sprite. It sets up the boomerang's initial properties, including its `SprSubtype` (to differentiate it from the main Goriya), action, position, and health.
```asm
Goriya_BoomerangAttack:
{
LDA.b #$2C ; Sprite ID for boomerang (assuming $2C is the boomerang sprite ID)
JSL Sprite_SpawnDynamically : BMI + ; Spawn a new sprite dynamically
LDA.b #$01 : STA.w SprSubtype, Y ; Set SprSubtype to 1 for the boomerang
LDA.b #$04 : STA.w SprAction, Y ; Set action for boomerang (e.g., flying)
LDA.w SprX, X : STA.w SprX, Y ; Copy Goriya's X position to boomerang
LDA.w SprY, X : STA.w SprY, Y ; Copy Goriya's Y position to boomerang
LDA.w SprXH, X : STA.w SprXH, Y
LDA.w SprYH, X : STA.w SprYH, Y
LDA.b #$01 : STA.w SprNbrOAM, Y ; Set number of OAM entries
LDA.b #$40 : STA.w SprHealth, Y ; Set boomerang health
LDA.b #$00 : STA.w SprHitbox, Y ; Set boomerang hitbox
+
RTS
}
```
### 2.7. `Sprite_Goriya_Draw` (Goriya Drawing Routine)
This routine is responsible for rendering Goriya's graphics. It uses a custom OAM allocation and manipulation logic to handle its multi-tile appearance and animation. It dynamically determines the animation frame and applies offsets for each tile.
```asm
Sprite_Goriya_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprGfx, X : CLC : ADC.w SprFrame, X : TAY ; Calculate animation frame index
LDA.w .start_index, Y : STA $06
LDA.w SprFlash, X : STA $08
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
; Put the sprite out of the way
LDA.b #$F0 : STA ($90), Y : 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 : ORA $08 : 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, $02, $04, $06, $08, $0A, $0C, $0E
.nbr_of_tiles
db 1, 1, 1, 1, 1, 1, 1, 1
.x_offsets
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
.y_offsets
dw 0, -9
dw 0, -9
dw 0, -10
dw 0, -10
dw 0, -10
dw 0, -9
dw 0, -9
dw -1, -10
.chr
; Body Head
db $E4, $C0
db $E4, $C0
db $E6, $C2
db $E6, $C2
db $E2, $C4
db $E0, $C4
db $E2, $C4
db $E0, $C4
.properties
db $2D, $2D
db $6D, $2D
db $2D, $2D
db $6D, $2D
db $2D, $2D
db $2D, $2D
db $6D, $6D
db $6D, $6D
}
```
### 2.8. `Sprite_Boomerang_Draw` (Boomerang Drawing Routine)
This routine is responsible for rendering the boomerang's graphics. It also uses a custom OAM allocation and manipulation logic.
```asm
Sprite_Boomerang_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprGfx, X : CLC : ADC.w SprFrame, X : TAY;Animation Frame
LDA .start_index, Y : STA $06
LDA.w SprFlash, X : STA $08
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 : ORA $08 : STA ($90), Y
PHY
TYA : LSR #2 : TAY
LDA #$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 $26
db $26
db $26
db $26
.properties
db $22
db $A2
db $E2
db $62
}
```
## 3. Key Behaviors and Implementation Details
* **Dual Role (Goriya and Boomerang):** The `Sprite_Goriya_Long` routine uses `SprSubtype, X` to determine if the current sprite instance is the main Goriya or a boomerang it has thrown. This allows a single sprite ID to manage two distinct entities.
* **Boomerang Attack:** Goriya actively throws a boomerang (`Goriya_BoomerangAttack`) at Link, which then becomes an independent sprite with its own drawing and movement logic (`BoomerangAttack` state in `Sprite_Goriya_Main` and `Sprite_Boomerang_Draw`).
* **Dynamic Health:** Goriya's health is set to a fixed value of `08` (one heart) during initialization.
* **State Management:** Goriya uses `SprAction, X` and `JumpTableLocal` to manage its walking states (`Goriya_WalkingUp`, `Goriya_WalkingDown`, `Goriya_WalkingLeft`, `Goriya_WalkingRight`) and its `BoomerangAttack` state.
* **Movement Patterns:** Goriya moves in random directions, with `Goriya_HandleTileCollision` triggering a new random direction upon hitting a tile. It also has a `Goriya_Wait` state.
* **Custom OAM Drawing:** Both the Goriya and its boomerang utilize custom OAM drawing routines (`Sprite_Goriya_Draw` and `Sprite_Boomerang_Draw`) for precise control over their multi-tile graphics and animation. The use of `REP`/`SEP` for 16-bit coordinate calculations is present in both.
* **Code Reuse:** The `Goriya_HandleTileCollision` routine is notably reused by the Darknut sprite, indicating a shared and modular approach to tile collision handling for certain enemy types.
* **`SprMiscB` Usage:** This variable is used to store the current movement direction (0-4) for Goriya's random movement.
* **`SprMiscE` Usage:** This variable also stores the current movement direction, likely for animation or other directional logic.
* **`SprTimerC` and `SprTimerD` Usage:** These timers are used to control the duration of movement states and the frequency of boomerang attacks.

View File

@@ -0,0 +1,433 @@
# Helmet Chuchu Sprite Analysis
This document provides a detailed analysis of the `helmet_chuchu.asm` sprite, outlining its properties, core routines, and behavioral patterns.
## 1. Sprite Properties
The following `!SPRID` constants define Helmet Chuchu's fundamental characteristics:
```asm
!SPRID = Sprite_HelmetChuchu
!NbrTiles = 03 ; Number of tiles used in a frame
!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = $10 ; Number of Health the sprite have
!Damage = 04 ; (08 is a whole heart), 04 is half heart
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 00 ; Unused in this template (can be 0 to 7)
!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = your sprite continue to live offscreen
!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layer for collision
!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk shallow water
!Blockable = 00 ; 01 = can be blocked by link's shield?
!Prize = 00 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
```
**Note:** `!Health` is initially set to `$10` but is dynamically determined during initialization based on Link's sword level.
## 2. Core Routines
### 2.1. `Sprite_HelmetChuchu_Long` (Main Loop)
This is the primary entry point for Helmet Chuchu's per-frame execution. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active.
```asm
Sprite_HelmetChuchu_Long:
{
PHB : PHK : PLB
JSR Sprite_HelmetChuchu_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_HelmetChuchu_Main
.SpriteIsNotActive
PLB
RTL
}
```
### 2.2. `Sprite_HelmetChuchu_Prep` (Initialization)
This routine is executed once when Helmet Chuchu is first spawned. It sets its health based on Link's sword level, randomly assigns an initial `SprAction` (determining its type and initial frame), and initializes `SprMiscB` and `SprMiscD` to zero.
```asm
Sprite_HelmetChuchu_Prep:
{
PHB : PHK : PLB
LDA.l Sword : DEC A : TAY
LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level
JSL GetRandomInt : AND.b #$02 : STA.w SprAction, X ; Randomly set initial action (0, 1, or 2)
STZ.w SprMiscB, X
STZ.w SprMiscD, X
LDA.w SprAction, X : BNE +
LDA.b #$04 : STA.w SprFrame, X ; If action 0, set frame to 4 (Helmet Green)
+
CMP.b #$02 : BNE +
LDA.b #$02 : STA.w SprFrame, X ; If action 2, set frame to 2 (Mask Red)
+
PLB
RTL
.health
db $08, $0C, $0F, $10 ; Health values for each sword level
}
```
### 2.3. `Sprite_HelmetChuchu_Main` (Behavioral State Machine)
This routine manages Helmet Chuchu's AI through a state machine, using `SprAction, X` to determine its current behavior. It includes states for different Chuchu types (Green/Red, Helmet/No Helmet, Mask/No Mask) and separate states for the detached helmet and mask.
```asm
Sprite_HelmetChuchu_Main:
{
JSL Sprite_DamageFlash_Long
%SpriteJumpTable(GreenChuchu_Helmet,
GreenChuchu_NoHelmet,
RedChuchu_Masked,
RedChuchu_NoMask,
HelmetSubtype,
MaskSubtype)
GreenChuchu_Helmet:
{
%StartOnFrame(4)
%PlayAnimation(4, 5, 16)
JSR Sprite_CheckForHookshot : BCC +
LDA.w SprFlash, X : BEQ +
%GotoAction(1) ; Transition to GreenChuchu_NoHelmet if hookshot hit and not flashing
+
JSL Sprite_CheckDamageFromPlayer
JSR Sprite_Chuchu_Move
RTS
}
GreenChuchu_NoHelmet:
{
%StartOnFrame(0)
%PlayAnimation(0, 1, 16)
LDA.w SprMiscD, X : BNE +
JSR HelmetChuchu_SpawnHookshotDrag ; Spawn detached helmet
LDA.b #$01 : STA.w SprMiscD, X ; Set flag to prevent re-spawning
+
JSL Sprite_CheckDamageFromPlayer
JSR Sprite_Chuchu_Move
RTS
}
RedChuchu_Masked:
{
%StartOnFrame(2)
%PlayAnimation(2, 3, 16)
JSR Sprite_CheckForHookshot : BCC +
LDA.w SprFlash, X : BEQ +
%GotoAction(3) ; Transition to RedChuchu_NoMask if hookshot hit and not flashing
+
JSL Sprite_CheckDamageFromPlayer
JSR Sprite_Chuchu_Move
RTS
}
RedChuchu_NoMask:
{
%StartOnFrame(6)
%PlayAnimation(6, 7, 16)
LDA.w SprMiscD, X : BNE +
JSR HelmetChuchu_SpawnHookshotDrag ; Spawn detached mask
LDA.b #$01 : STA.w SprMiscD, X ; Set flag to prevent re-spawning
+
JSL Sprite_CheckDamageFromPlayer
JSR Sprite_Chuchu_Move
RTS
}
HelmetSubtype:
{
%StartOnFrame(8)
%PlayAnimation(8, 8, 16)
JSL Sprite_Move
JSL Sprite_CheckIfLifted
JSL Sprite_CheckIfRecoiling
JSL ThrownSprite_TileAndSpriteInteraction_long
RTS
}
MaskSubtype:
{
%StartOnFrame(8)
%PlayAnimation(9, 9, 16)
JSL Sprite_Move
JSL Sprite_CheckIfLifted
JSL Sprite_CheckIfRecoiling
JSL ThrownSprite_TileAndSpriteInteraction_long
RTS
}
}
```
### 2.4. `Sprite_Chuchu_Move` (Movement and Interaction Logic)
This routine handles Helmet Chuchu's movement, which involves bouncing towards or recoiling from the player. It uses `SprMiscB, X` to switch between these two behaviors.
```asm
Sprite_Chuchu_Move:
{
JSL Sprite_Move
JSL Sprite_BounceFromTileCollision
JSL Sprite_PlayerCantPassThrough
JSL Sprite_CheckIfRecoiling
LDA.w SprMiscB, X
JSL JumpTableLocal
dw BounceTowardPlayer
dw RecoilFromPlayer
BounceTowardPlayer:
{
JSL GetRandomInt : AND.b #$02 : STA $09 ; Speed
JSL GetRandomInt : AND.b #$07 : STA $08 ; Height
JSL Sprite_MoveAltitude
DEC.w $0F80,X : DEC.w $0F80,X
LDA.w SprHeight, X : BPL .aloft
STZ.w SprHeight, X
LDA.b $08 : STA.w $0F80, X ; set height from 08
LDA.b $09
JSL Sprite_ApplySpeedTowardsPlayer
.aloft
LDA.w SprHeight, X : BEQ .dontmove
JSL Sprite_Move
.dontmove
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
INC.w SprMiscB, X ; Switch to RecoilFromPlayer
LDA.b #$20 : STA.w SprTimerB, X
.no_damage
JSL Sprite_CheckDamageToPlayer : BCC .no_attack
INC.w SprMiscB, X ; Switch to RecoilFromPlayer
LDA.b #$20 : STA.w SprTimerB, X
.no_attack
RTS
}
RecoilFromPlayer:
{
JSL GetRandomInt : AND.b #$02 : STA $09 ; Speed
LDA.w SprX, X : CLC : ADC $09 : STA $04
LDA.w SprY, X : SEC : SBC $09 : STA $06
LDA.w SprXH, X : ADC #$00 : STA $05
LDA.w SprYH, X : ADC #$00 : STA $07
LDA $09 : STA $00 : STA $01
JSL Sprite_ProjectSpeedTowardsEntityLong
LDA.w SprTimerB, X : BNE .not_done
JSR HelmetChuchu_SpawnHookshotDrag ; Spawn detached helmet/mask
STZ.w SprMiscB, X ; Switch back to BounceTowardPlayer
.not_done
RTS
}
}
```
### 2.5. `HelmetChuchu_SpawnHookshotDrag`
This routine is responsible for spawning the detached helmet or mask as a separate sprite when the Chuchu is hit by a hookshot. It determines whether to spawn a helmet or a mask based on the Chuchu's current `SprAction`.
```asm
HelmetChuchu_SpawnHookshotDrag:
{
; Based on the subtype either spawn the helmet or the mask
PHX
LDA.w SprAction, X : CMP.b #$01 : BEQ .spawn_helmet
CMP.b #$03 : BEQ .spawn_mask
.spawn_helmet
LDA.b #$05 ; Sprite ID for helmet/mask (assuming $05 is the ID)
JSL Sprite_SpawnDynamically : BMI .no_space
LDA.b #$05 : STA.w SprAction, Y ; Set action for detached helmet
JMP .prepare_mask
.no_space
JMP .no_space2
.spawn_mask
LDA.b #$05 ; Sprite ID for helmet/mask
JSL Sprite_SpawnDynamically : BMI .no_space2
LDA.b #$04 : STA.w SprAction, Y ; Set action for detached mask
.prepare_mask
JSL Sprite_SetSpawnedCoordinates
LDA.b #$10 : STA.w SprHealth, Y
LDA.b #$00 : STA.w SprMiscB, Y
LDA.b #$80 : STA.w SprTimerA, Y
LDA.b #$01 : STA.w SprNbrOAM, Y
LDA.w .speed_x, X : STA.w SprXSpeed, Y
LDA.w .speed_y, X : STA.w SprYSpeed, Y
.no_space2
PLX
RTS
.speed_x
db 16, -11, -16, 11
.speed_y
db 0, 11, 0, -11
}
```
### 2.6. `Sprite_CheckForHookshot`
This routine checks if a hookshot is currently active and interacting with the Chuchu. It iterates through ancilla slots to find a hookshot (`$1F`) and returns with the carry flag set if found.
```asm
Sprite_CheckForHookshot:
{
PHX
LDX.b #$0A
.next_ancilla
LDA.w $0C4A, X : CMP.b #$1F : BNE .not_hooker ; Check ancilla type (assuming $1F is hookshot)
PLX
SEC ; Carry set if hookshot found
RTS
.not_hooker
DEX
BPL .next_ancilla
PLX
CLC ; Carry clear if no hookshot found
RTS
}
```
### 2.7. `Sprite_HelmetChuchu_Draw` (Drawing Routine)
This routine is responsible for rendering Helmet Chuchu's graphics. It uses a custom OAM allocation and manipulation logic to handle its multi-tile appearance and animation, dynamically adjusting based on its current state (helmet/mask, color, animation frame).
```asm
Sprite_HelmetChuchu_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprGfx, X : CLC : ADC.w SprFrame, X : TAY;Animation Frame
LDA .start_index, Y : STA $06
LDA.w SprFlash, X : STA $08
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 : 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 : ORA $08 : 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
; =======================================================
; chr prop
; Mask $04 $37
; Helmet $08 $3B
.start_index
db $00, $02, $03, $06, $08, $0A, $0C, $0E, $0F, $10
.nbr_of_tiles
db 1, 0, 2, 1, 1, 1, 1, 0, 0, 0
.y_offsets
dw 0, -8
dw 0
dw 0, -8, -8
dw 0, -4
dw 0, -8
dw 0, -4
dw 0, -8
dw 0
dw 0
dw 0
.chr
; No Helmet Green
db $26, $16
db $24
; Mask Red
db $26, $16, $04
db $24, $04
; Helmet Green
db $26, $08
db $24, $08
; No Helmet Red
db $26, $16
db $24
; Mask
db $04
; Helmet
db $08
.properties
db $2B, $2B
db $2B
db $25, $25, $27
db $25, $27
db $2B, $29
db $2B, $29
db $25, $25
db $25
; mask
db $27
; helmet
db $29
}
```
## 3. Key Behaviors and Implementation Details
* **Dynamic Appearance and State:** Helmet Chuchu is a highly dynamic sprite that changes its appearance and behavior based on whether it has a helmet/mask and its color (green/red). This is managed through its `SprAction` and `SprFrame` values.
* **Conditional Damage Handling:** The Chuchu's vulnerability to damage is tied to the presence of its helmet or mask. When hit by a hookshot, the helmet/mask detaches, making the Chuchu vulnerable.
* **Hookshot Interaction:** Special logic (`Sprite_CheckForHookshot`) is implemented to detect interaction with Link's hookshot, which triggers the detachment of the helmet/mask.
* **Detached Helmet/Mask as Separate Sprites:** When the helmet or mask is detached, it is spawned as an independent sprite (`HelmetSubtype` or `MaskSubtype`) with its own movement (`Sprite_Move`), collision (`ThrownSprite_TileAndSpriteInteraction_long`), and interaction logic. This demonstrates a sophisticated use of child sprites.
* **Movement Patterns:** The Chuchu moves by bouncing towards (`BounceTowardPlayer`) and recoiling from (`RecoilFromPlayer`) the player, with randomness introduced in speed and height. This creates a distinct and challenging movement pattern.
* **Custom OAM Drawing:** The `Sprite_HelmetChuchu_Draw` routine is a complex example of custom OAM manipulation. It dynamically selects tiles and properties based on the Chuchu's current state, allowing for seamless transitions between helmeted, masked, and vulnerable forms.
* **`SprMiscB` Usage:** This variable controls the Chuchu's movement sub-states (`BounceTowardPlayer` and `RecoilFromPlayer`). It also plays a role in the detached helmet/mask sprites.
* **`SprMiscD` Usage:** This variable acts as a flag to ensure that the `HelmetChuchu_SpawnHookshotDrag` routine is called only once when the helmet/mask is detached.
* **`SprTimerB` Usage:** Used to control the duration of the recoil state.

View File

@@ -0,0 +1,293 @@
# Keese
## Overview
The Keese sprite (`!SPRID = $11`) is a versatile enemy that encompasses multiple variations: Ice Keese, Fire Keese, and Vampire Bat. Its behavior is dynamically determined by its `SprSubtype`.
## Subtypes
* `00` - Ice Keese
* `01` - Fire Keese
* `02` - Vampire Bat
## Sprite Properties
* **`!SPRID`**: `$11` (Vanilla sprite ID for Keese/Vampire Bat)
* **`!NbrTiles`**: `08`
* **`!Harmless`**: `00`
* **`!HVelocity`**: `00`
* **`!Health`**: `00` (Dynamically set in `_Prep` based on subtype)
* **`!Damage`**: `00` (Damage is handled by projectiles or specific attack logic)
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `01`
* **`!Palette`**: `00`
* **`!Hitbox`**: `00`
* **`!Persist`**: `00`
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `00` (Dynamically set in `_Prep` based on subtype)
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_Keese_Long`)
This routine dispatches to different drawing and main logic routines based on the sprite's `SprSubtype`.
```asm
Sprite_Keese_Long:
{
PHB : PHK : PLB
LDA.w SprSubtype, X : CMP.b #$02 : BEQ +
JSR Sprite_Keese_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Keese_Main
.SpriteIsNotActive
JMP ++
+
JSR Sprite_VampireBat_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC ++
JSR Sprite_VampireBat_Main
++
PLB
RTL
}
```
## Initialization (`Sprite_Keese_Prep`)
This routine initializes sprite properties upon spawning, including health and prize, based on its subtype.
```asm
Sprite_Keese_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.b #$30 : STA.w SprTimerC, X
LDA.w SprSubtype, X : CMP.b #$02 : BNE +
LDA.b #$20 : STA.w SprHealth, X
BRA ++
+
LDA.b #$03 : STA.w SprNbrOAM, X
LDA.b #$03 : STA.w SprPrize, X
++
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Keese_Main`)
The Keese's behavior is managed by a state machine with `Keese_Idle` and `Keese_FlyAround` states.
* **`Keese_Idle`**: The sprite remains stationary until Link is within a certain distance, then transitions to `Keese_FlyAround`.
* **`Keese_FlyAround`**: The Keese flies around, plays an animation, checks for collisions with Link and tiles, and can initiate an attack. It uses `GetRandomInt` for varied movement and `Sprite_ProjectSpeedTowardsPlayer` to move towards Link.
```asm
Sprite_Keese_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw Keese_Idle
dw Keese_FlyAround
Keese_Idle:
{
STZ.w SprFrame, X
; Wait til the player is nearby then fly around
LDA.w SprTimerC, X : BEQ .move
JSL GetDistance8bit_Long : CMP.b #$20 : BCS +
.move
INC.w SprAction, X
JSL GetRandomInt
STA.w SprTimerA, X
+
RTS
}
Keese_FlyAround:
{
%PlayAnimation(0,5,8)
JSL Sprite_CheckDamageToPlayer
JSL Sprite_CheckDamageFromPlayer : BCC +
JSL ForcePrizeDrop_long
+
JSL Sprite_DamageFlash_Long
JSL Sprite_BounceFromTileCollision
JSL GetRandomInt : AND.b #$3F : BNE +
LDA.b #$10 : STA.w SprTimerC, X
+
JSR Sprite_Keese_Attack
LDA.w SprTimerA, X : AND.b #$10 : BNE +
LDA.b #$40
JSL Sprite_ProjectSpeedTowardsPlayer
+
JSL Sprite_SelectNewDirection
JSL Sprite_Move
LDA.w SprTimerA, X : BNE +
STZ.w SprAction, X
+
RTS
}
}
```
## Attack Logic (`Sprite_Keese_Attack`)
This routine handles the Keese's attack, which varies by subtype:
* **Ice Keese (`SprSubtype = 0`)**: Spawns sparkle garnish and a blind laser trail.
* **Fire Keese (`SprSubtype = 1`)**: Utilizes `Sprite_Twinrova_FireAttack`.
```asm
Sprite_Keese_Attack:
{
LDA.w SprTimerC, X : BEQ +
LDA.w SprSubtype, X : BEQ ++
JSL Sprite_Twinrova_FireAttack
JMP +
++
JSL Sprite_SpawnSparkleGarnish
JSL BlindLaser_SpawnTrailGarnish
+
RTS
}
```
## Drawing (`Sprite_Keese_Draw`)
The drawing routine handles OAM allocation, animation, and palette adjustments based on the sprite's subtype. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_Keese_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, X : TAY ;Animation Frame
LDA .start_index, Y : STA $06
LDA.w SprFlash, X : STA $08
LDA.w SprMiscB, X : STA $09
LDA.w SprSubtype, X : CMP.b #$01 : BNE +
LDA.b #$0A : EOR $08 : STA $08
+
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
; If SprMiscA != 0, then use 4th sheet
LDA.b $09 : BEQ +
LDA .chr_2, X : STA ($90), Y
JMP ++
+
LDA .chr, X : STA ($90), Y
++
INY
LDA .properties, X : ORA $08 : 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, $01, $03, $04, $06, $08
.nbr_of_tiles
db 0, 1, 0, 1, 1, 0
.x_offsets
dw 0
dw -4, 4
dw 0
dw -4, 4
dw -4, 4
dw 0
.y_offsets
dw 0
dw 0, 0
dw 0
dw 0, 0
dw 0, 0
dw 0
.chr
db $80
db $A2, $A2
db $82
db $84, $84
db $A4, $A4
db $A0
.chr_2
db $C0
db $E2, $E2
db $C2
db $C4, $C4
db $E4, $E4
db $E0
.properties
db $35
db $35, $75
db $35
db $35, $75
db $35, $75
db $35
.sizes
db $02
db $02, $02
db $02
db $02, $02
db $02, $02
db $02
}
```
## Design Patterns
* **Subtype-based Behavior**: The sprite uses `SprSubtype` to implement distinct behaviors and appearances for Ice Keese, Fire Keese, and Vampire Bat, all under a single `!SPRID`.
* **Dynamic Property Initialization**: Health and prize values are set dynamically during the `_Prep` routine based on the sprite's subtype.
* **Conditional Drawing and Palette**: The drawing routine adjusts the sprite's palette and potentially its graphics based on its subtype, allowing for visual differentiation.
* **Randomized Movement**: Utilizes `GetRandomInt` to introduce variability in movement patterns, making the enemy less predictable.
* **Projectile Attacks**: Implements different projectile attacks based on subtype, showcasing varied offensive capabilities.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, which is crucial for accurate sprite rendering.

View File

@@ -0,0 +1,242 @@
# Leever
## Overview
The Leever sprite is a custom implementation that overrides the vanilla Leever behavior (`Sprite_71_Leever`). It features distinct states for being underground, emerging, attacking, and digging back down, with randomized timers controlling its transitions.
## Vanilla Override
This custom Leever implementation hooks into the vanilla sprite ID $71. It uses a custom flag at `$0FFF` to determine whether to execute its custom logic (`Sprite_Leever_Long`) or fall back to the original vanilla Leever behavior (`Sprite_71_Leever`).
```asm
pushpc
Sprite_71_Leever = $06CBA2
org $069365 : dw Sprite_71_Leever_Alt
Sprite_71_Leever_Alt:
{
LDA.w $0FFF : BEQ +
JSL Sprite_Leever_Long
JMP ++
+
JSR Sprite_71_Leever
++
RTS
}
assert pc() <= $06A5C0
pullpc
```
## Sprite Properties
Explicit sprite properties (`!SPRID`, `!Health`, etc.) are not defined within this file, suggesting it either inherits vanilla properties for sprite ID $71 or these are defined in a separate configuration file.
## Main Structure (`Sprite_Leever_Long`)
This routine is the main entry point for the custom Leever logic, executed every frame. It handles bank setup, conditional drawing (skipping drawing when underground), and dispatches to the main logic if the sprite is active.
```asm
Sprite_Leever_Long:
{
PHB : PHK : PLB
LDA.w SprAction, X : BEQ +
JSR Sprite_Leever_Draw
+
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Leever_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Movement Routine (`Sprite_Leever_Move`)
A shared routine for handling the Leever's movement, including applying speed towards the player, moving the sprite, and bouncing off tiles.
```asm
Sprite_Leever_Move:
{
JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_Move
JSL Sprite_BounceFromTileCollision
RTS
}
```
## Main Logic & State Machine (`Sprite_Leever_Main`)
The Leever's core behavior is managed by a state machine with four distinct states:
* **`Leever_Underground`**: The Leever moves underground. After a timer (`SprTimerA`) expires, it transitions to `Leever_Emerge`.
* **`Leever_Emerge`**: The Leever plays a backwards animation as it emerges. After a randomized timer, it transitions to `Leever_Attack`.
* **`Leever_Attack`**: The Leever plays an attack animation, checks for damage to/from Link, and moves. After a timer, it transitions to `Leever_Dig`.
* **`Leever_Dig`**: The Leever plays an animation as it digs back into the ground. After a randomized timer, it transitions back to `Leever_Underground`.
```asm
Sprite_Leever_Main:
{
JSL Sprite_DamageFlash_Long
LDA.w SprAction, X
JSL JumpTableLocal
dw Leever_Underground
dw Leever_Emerge
dw Leever_Attack
dw Leever_Dig
Leever_Underground:
{
LDA.w SprTimerA, X : BNE +
LDA.b #$40 : STA.w SprTimerA, X
INC.w SprAction, X
+
LDA.b #$10
JSR Sprite_Leever_Move
RTS
}
Leever_Emerge:
{
%PlayAnimBackwards(3, 2, 10)
LDA.w SprTimerA, X : BNE +
JSL GetRandomInt
AND.b #$3F
ADC.b #$A0
STA.w $0DF0,X
INC.w SprAction, X
STZ.w SprXSpeed, X : STZ.w SprYSpeed, X
+
RTS
}
Leever_Attack:
{
%PlayAnimation(0, 1, 10)
LDA.w SprTimerA, X : BNE +
LDA.b #$7F : STA.w SprTimerA, X
INC.w SprAction, X
+
PHX
JSL Sprite_CheckIfRecoiling
JSL Sprite_CheckDamageToPlayerSameLayer
JSL Sprite_CheckDamageFromPlayer
PLX
LDA.b #$0C
JSR Sprite_Leever_Move
RTS
}
Leever_Dig:
{
%PlayAnimation(2, 3, 10)
LDA.w SprTimerA, X : BNE +
JSL GetRandomInt
AND.b #$1F
ADC.b #$40
STA.w $0DF0,X
STZ.w SprAction, X
+
LDA.b #$08
JSR Sprite_Leever_Move
RTS
}
}
```
## Drawing (`Sprite_Leever_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_Leever_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA $0DC0, X : CLC : ADC $0D90, X : TAY;Animation Frame
LDA .start_index, Y : STA $06
LDA.w SprFlash, X : STA $08
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 : ORA $08 : 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, $01, $02, $03
.nbr_of_tiles
db 0, 0, 0, 0
.x_offsets
dw 0
dw 0
dw 0
dw 0
.y_offsets
dw 0
dw 0
dw 0
dw 0
.chr
db $C4
db $C6
db $C2
db $C0
.properties
db $33
db $33
db $33
db $33
.sizes
db $02
db $02
db $02
db $02
}
```
## Design Patterns
* **Vanilla Override**: Explicitly overrides a vanilla sprite's behavior, demonstrating how to replace existing game logic with custom implementations.
* **Conditional Logic**: Uses a custom flag (`$0FFF`) to dynamically switch between vanilla and custom behaviors, offering flexibility in game design.
* **Emerging/Digging State Machine**: Implements a robust state machine to manage the Leever's characteristic emerging from and digging back into the ground, with randomized timers for unpredictable transitions.
* **Animation Control**: Utilizes `%PlayAnimBackwards` for specific animation effects, such as the Leever emerging from the ground.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, essential for accurate sprite rendering.

View File

@@ -0,0 +1,79 @@
# Octoboss Sprite Analysis
## Overview
The `octoboss` sprite (ID: `Sprite_Octoboss`, which is `$3C`) is a multi-phase boss, likely an octopus-like creature. It features a unique mechanic involving a "brother" Octoboss, and can summon stalfos offspring. The fight progresses through distinct phases including emergence, movement, taunting, ascending, submerging, and a surrender sequence.
## Key Properties:
* **Sprite ID:** `Sprite_Octoboss` (`$3C`)
* **Description:** A multi-phase boss with a "brother" Octoboss, capable of summoning stalfos and engaging in various movement and attack patterns.
* **Number of Tiles:** 11
* **Health:** `00` (Health is managed by `ReturnTotalHealth` and `CheckForNextPhase`, combining the health of both Octobosses.)
* **Damage:** `00` (Damage dealt to Link is likely handled by its attacks or spawned offspring.)
* **Special Properties:**
* `!Boss = $01` (Correctly identified as a boss.)
* `!Shadow = 01` (Draws a shadow.)
* `!Hitbox = 03`
## Custom Variables:
* `!ConsecutiveHits = $AC`: Tracks consecutive hits on the boss.
* `!KydrogPhase = $7A`: Tracks the current phase of the boss fight. (Note: This variable name is `KydrogPhase`, suggesting shared logic or a copy-paste from the Kydrog boss.)
* `!WalkSpeed = 10`: Defines the boss's walking speed.
* `BrotherSpr = $0EB0`: Stores the sprite index of the "brother" Octoboss.
## Main Logic Flow (`Sprite_Octoboss_Main` and `Sprite_Octoboss_Secondary`):
The Octoboss has two primary main routines, `Sprite_Octoboss_Main` and `Sprite_Octoboss_Secondary`, which are called based on `SprMiscF, X`. This suggests different forms or behaviors for the two Octobosses.
**`Sprite_Octoboss_Main` Jump Table (for the primary Octoboss):**
* **`WaitForPlayerToApproach` (0x00):** Waits for Link to reach a specific Y-coordinate (`$08C8`) to trigger activation.
* **`Emerge` (0x01):** Emerges from the water, preventing Link's movement.
* **`EmergedShowMessage` (0x02):** Displays an introductory message.
* **`SpawnAndAwakeHisBrother` (0x03):** Spawns a "brother" Octoboss (`Sprite_Octoboss` with `SprMiscF = $01`) and stores its ID in `BrotherSpr`.
* **`WaitForBrotherEmerge` (0x04):** Waits for the brother to emerge and displays a message.
* **`SpawnPirateHats` (0x05):** Spawns "boss poof" effects for both Octobosses, changes their frames, and spawns walls using `Overworld_DrawMap16_Persist` macros.
* **`IdlePhase` (0x06):** Idles, can spawn fireballs, and checks total health (`ReturnTotalHealth`) to potentially trigger surrender.
* **`PickDirection` (0x07):** Picks a random direction and speed for movement.
* **`Moving` (0x08):** Moves around, handles boundary checks, spawns splash effects, and checks total health to potentially trigger surrender.
* **`WaitMessageBeforeSurrender` (0x09):** Displays a surrender message (`$004A`), sets the brother's action, and transitions to `RemoveHat`.
* **`RemoveHat` (0x0A):** Removes hats from both Octobosses with "boss poof" effects, displays a message (`$004B`), and transitions to `Submerge`.
* **`Submerge` (0x0B):** Submerges, playing an animation and spawning a splash effect.
* **`SubmergeWaitWall` (0x0C):** Submerges further, drawing walls using `Overworld_DrawMap16_Persist` macros.
* **`EmergeWaitGiveItem` (0x0D):** Emerges, spawns a medallion (`SpawnMedallion`), and sets an SRAM flag.
* **`SubmergeForeverKill` (0x0E):** Submerges completely, despawns, and allows Link to move again.
**`Sprite_Octoboss_Secondary` Jump Table (for the secondary/brother Octoboss, when `SprMiscF, X` is set):**
This routine largely mirrors `Sprite_Octoboss_Main` but includes specific states like `WaitDialog` and `Moving2`, suggesting slight behavioral differences.
## Initialization (`Sprite_Octoboss_Long` and `Sprite_Octoboss_Prep`):
* **`Sprite_Octoboss_Long`:** Main entry point. Handles sprite initialization, including setting OAM properties, bulletproofing, frame, and palette values. It also checks for boss defeat and medallion spawning.
* **`Sprite_Octoboss_Prep`:** Initializes `!KydrogPhase` to `00`, sets initial health (`$A0`), configures deflection, hitbox, bump damage, and calls `JSR KydrogBoss_Set_Damage` to set up the damage table. Sets initial sprite speeds and an intro timer.
## Health Management (`Sprite_Octoboss_CheckIfDead`, `ReturnTotalHealth`, `CheckForNextPhase`):
* **`Sprite_Octoboss_CheckIfDead`:** Monitors `SprHealth, X`. If health is zero or negative, it triggers the boss's death sequence.
* **`ReturnTotalHealth`:** Calculates the combined health of both Octobosses (`SprHealth, X` and `SprHealth, Y` of `BrotherSpr`).
* **`CheckForNextPhase`:** Manages the boss's phases based on health thresholds, similar to the Kydrog boss.
## Offspring Spawning (`RandomStalfosOffspring`, `Sprite_Offspring_Spawn`, `Sprite_Offspring_SpawnHead`):
* These routines are identical to those found in `kydrog_boss.asm`, spawning stalfos offspring.
## Attacks (`Chuchu_SpawnBlast`, `Mothula_SpawnBeams`, `Kydrog_ThrowBoneAtPlayer`):
* **`Chuchu_SpawnBlast`:** Spawns a Chuchu blast projectile.
* **`Mothula_SpawnBeams`:** Spawns beam projectiles.
* **`Kydrog_ThrowBoneAtPlayer`:** Spawns a bone projectile (reused from Kydrog).
## Drawing (`Sprite_Octoboss_Draw`, `Sprite_Octoboss_Draw2`):
* **`Sprite_Octoboss_Draw`:** Draws the primary Octoboss.
* **`Sprite_Octoboss_Draw2`:** Draws the secondary/brother Octoboss.
* Both use standard OAM allocation routines and handle complex animation frames, offsets, character data, properties, and sizes.
## Other Routines:
* **`SpawnSplash`:** Spawns a splash effect.
* **`SpawnBossPoof`:** Spawns a boss poof effect.
* **`HandleMovingSplash`:** Handles splash effects during movement.
* **`SpawnMedallion` / `SpawnMedallionAlt`:** Spawns a medallion.
## Discrepancies/Notes:
* **Shared Variables/Code with Kydrog:** The extensive use of `!KydrogPhase`, `KydrogBoss_Set_Damage`, and `Kydrog_ThrowBoneAtPlayer` suggests significant code reuse or copy-pasting from the Kydrog boss. This could lead to unexpected interactions or make maintenance challenging if changes are made to one boss but not the other. Refactoring shared logic into common functions or macros would be beneficial.
* **`Sprite_Octoboss_Secondary`:** The existence of a separate main routine for the "brother" Octoboss indicates that the two Octobosses might have slightly different behaviors or phases.
## Hardcoded Activation Trigger:
* As noted by the user, the activation trigger for Octoboss is hardcoded. In the `WaitForPlayerToApproach` routine, the boss checks `LDA.b $20 : CMP #$08C8`. `$20` represents Link's Y position, and `$08C8` is a hardcoded Y-coordinate. This means the boss will only activate when Link reaches this specific Y-coordinate, making it difficult to relocate the boss to other overworld maps without modifying this value. This hardcoded dependency needs to be addressed for improved reusability.

View File

@@ -0,0 +1,547 @@
# Octorok
## Overview
The Octorok sprite (`!SPRID = $08`) is a complex enemy implementation that supports both land-based and water-based variations. It features dynamic behavior, including transformation between forms, distinct movement patterns, and projectile attacks.
## Sprite Properties
* **`!SPRID`**: `$08` (Vanilla sprite ID for Octorok)
* **`!NbrTiles`**: `05`
* **`!Harmless`**: `00`
* **`!HVelocity`**: `00`
* **`!Health`**: `00` (Health is likely set dynamically or is vanilla)
* **`!Damage`**: `00` (Damage is likely from projectiles)
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00` (Shadow is drawn conditionally in `_Long`)
* **`!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_Octorok_Long`)
This routine acts as a dispatcher, determining whether to execute Land Octorok or Water Octorok logic based on `SprSubtype`.
```asm
Sprite_Octorok_Long:
{
PHB : PHK : PLB
JSR Sprite_Octorok_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
LDA.w SprSubtype, X : BEQ +
JSL Sprite_DrawWaterRipple
JSR Sprite_WaterOctorok_Main
JMP ++
+
JSL Sprite_DrawShadow
JSR Sprite_Octorok_Main
++
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Octorok_Prep`)
This routine is currently empty, indicating that initial setup is minimal or handled by vanilla routines.
## Land Octorok Main Logic (`Sprite_Octorok_Main`)
This routine handles the behavior of a land-based Octorok, including movement and potential transformation into a Water Octorok.
* **Movement**: Calls `Sprite_Octorok_Move` for general movement.
* **Transformation**: Checks the tile type the Octorok is on. If it's a water tile, the Octorok transforms into a Water Octorok by setting `SprSubtype, X` to `01`.
* **Directional States**: Uses a jump table to manage animations for moving in different directions (Down, Up, Left, Right).
```asm
Sprite_Octorok_Main:
{
JSR Sprite_Octorok_Move
; TILETYPE 08
LDA.l $7FF9C2,X : CMP.b #$08 : BEQ .water_tile
; TILETYPE 09
CMP.b #$09 : BNE .not_water_tile
.water_tile
LDA.b #$01 : STA.w SprSubtype, X
STZ.w SprAction, X
STZ.w SprMiscG, X
RTS
.not_water_tile
LDA.w SprAction, X
JSL JumpTableLocal
dw Octorok_MoveDown
dw Octorok_MoveUp
dw Octorok_MoveLeft
dw Octorok_MoveRight
Octorok_MoveDown:
{
%PlayAnimation(0,1,10)
RTS
}
Octorok_MoveUp:
{
%StartOnFrame(2)
%PlayAnimation(2,3,10)
RTS
}
Octorok_MoveLeft:
{
%StartOnFrame(4)
%PlayAnimation(4,5,10)
RTS
}
Octorok_MoveRight:
{
%StartOnFrame(6)
%PlayAnimation(6,7,10)
RTS
}
}
```
## Octorok Movement (`Sprite_Octorok_Move`)
This shared routine handles the Octorok's general movement, damage reactions, and tile collision.
* **Damage & Collision**: Handles damage flash (`Sprite_DamageFlash_Long`), movement (`Sprite_Move`), and checks for damage to/from Link.
* **Directional Logic**: Sets the sprite's action based on its direction (`SprMiscC, X`).
* **Tile Collision**: Detects tile collisions and changes the Octorok's direction accordingly.
* **Barrage Logic**: Contains logic related to a potential projectile barrage (`octorok_used_barrage`).
```asm
Sprite_Octorok_Move:
{
JSL Sprite_DamageFlash_Long
JSL Sprite_Move
JSL Sprite_CheckDamageFromPlayer
JSL Sprite_CheckDamageToPlayer
; Set the SprAction based on the direction
LDA.w SprMiscC, X : AND.b #$03 : TAY
LDA.w .direction, Y : STA.w SprAction, X
LDA.w SprMiscF, X : AND.b #$01 : BNE .octorok_used_barrage
LDA.w SprMiscC, X : AND.b #$02 : ASL A : STA.b $00
INC.w SprDelay, X
LDA.w SprDelay, X
LSR A
LSR A
LSR A
AND.b #$03
ORA.b $00
STA.w SprGfx, X
LDA.w SprTimerA, X : BNE .wait
INC.w SprMiscF,X
LDY.w SprType,X
LDA.w .timer-8,Y : STA.w SprTimerA,X
RTS
.wait
LDY.w SprMiscC, X
LDA.w .speed_x, Y : STA.w SprXSpeed, X
LDA.w .speed_y, Y : STA.w SprYSpeed, X
JSL Sprite_CheckTileCollision
LDA.w $0E70, X : BEQ .no_collision
LDA.w SprMiscC,X : EOR.b #$01 : STA.w SprMiscC,X
BRA .exit
.no_collision
RTS
.octorok_used_barrage
STZ.w SprXSpeed, X : STZ.w SprYSpeed,X
LDA.w SprTimerA, X : BNE Octorock_ShootEmUp
INC.w SprMiscF, X
LDA.w SprMiscC, X
PHA
JSL GetRandomInt : AND.b #$3F : ADC.b #$30 : STA.w SprTimerA, X
AND.b #$03 : STA.w SprMiscC, X
PLA
CMP.w SprMiscC, X : BEQ .exit
EOR.w SprMiscC, X : BNE .exit
LDA.b #$08 : STA.w SprTimerB,X
.exit
RTS
.direction
db 3, 2, 0, 1
.speed_x
db 24, -24, 0, 0
.speed_y
db 0, 0, 24, -24
.timer
db 60, 128, 160, 128
}
```
## Octorok Projectile Logic (`Octorock_ShootEmUp`)
This routine determines the Octorok's shooting behavior, allowing for both single-shot and four-way attacks.
```asm
Octorock_ShootEmUp:
{
; Use SprMiscD as a flag to shoot 4 ways for awhile before going back to single shot
LDA.w SprMiscD, X : BEQ .continue
LDA.w SprTimerD, X : BNE .four_ways
LDA.b #$01 : STA.w SprMiscD, X
.continue
JSL GetRandomInt : AND.b #$1F : BNE .single_shot
.four_ways
LDA.b #$01 : STA.w SprMiscD, X
LDA.b #$20 : STA.w SprTimerD, X
JSR Octorok_Shoot4Ways
RTS
.single_shot
JSR Octorok_ShootSingle
RTS
}
```
## Water Octorok Main Logic (`Sprite_WaterOctorok_Main`)
This routine governs the behavior of a water-based Octorok, including its attack patterns and states.
* **Attack**: Calls `Sprite_WaterOctorok_Attack`.
* **Facing Directions**: Uses a jump table to manage animations for facing different directions (Down, Up, Left, Right) and a hidden state.
```asm
Sprite_WaterOctorok_Main:
{
JSR Sprite_WaterOctorok_Attack
LDA.w SprAction, X
JSL JumpTableLocal
dw WaterOctorok_FaceDown
dw WaterOctorok_FaceUp
dw WaterOctorok_FaceLeft
dw WaterOctorok_FaceRight
dw WaterOctorok_FaceHidden
WaterOctorok_FaceDown:
{
%PlayAnimation(0,1,10)
RTS
}
WaterOctorok_FaceUp:
{
%StartOnFrame(2)
%PlayAnimation(2,3,10)
RTS
}
WaterOctorok_FaceLeft:
{
%StartOnFrame(4)
%PlayAnimation(4,5,10)
RTS
}
WaterOctorok_FaceRight:
{
%StartOnFrame(6)
%PlayAnimation(6,7,10)
RTS
}
WaterOctorok_FaceHidden:
{
%StartOnFrame(8)
%PlayAnimation(8,8,10)
RTS
}
}
```
## Water Octorok Attack Logic (`Sprite_WaterOctorok_Attack`)
This routine manages the Water Octorok's attack states, including hiding, emerging, attacking, and re-hiding.
* **States**: Uses `SprMiscG, X` as a state machine for `WaterOctorok_Hidden`, `WaterOctorok_PoppingUp`, `WaterOctorok_Attacking`, and `WaterOctorok_Hiding`.
* **`WaterOctorok_Hidden`**: Remains hidden until Link is within a certain distance, then transitions to `WaterOctorok_PoppingUp`.
* **`WaterOctorok_PoppingUp`**: Emerges from the water, faces Link, and then transitions to `WaterOctorok_Attacking`.
* **`WaterOctorok_Attacking`**: Shoots a single projectile (`Octorok_ShootSingle`) after a timer, then transitions to `WaterOctorok_Hiding`.
* **`WaterOctorok_Hiding`**: Hides back in the water and transitions to `WaterOctorok_Hidden`.
```asm
Sprite_WaterOctorok_Attack:
{
JSL Sprite_DamageFlash_Long
JSL Sprite_CheckDamageToPlayer
LDA.w SprMiscG, X
JSL JumpTableLocal
dw WaterOctorok_Hidden
dw WaterOctorok_PoppingUp
dw WaterOctorok_Attacking
dw WaterOctorok_Hiding
WaterOctorok_Hidden:
{
LDA.w SprTimerA, X : BEQ +
RTS
+
JSL GetDistance8bit_Long
CMP.b #$40 : BCC .not_close_enough ; LD < 64
INC.w SprMiscG, X
%SetTimerA($10)
.not_close_enough
RTS
}
WaterOctorok_PoppingUp:
{
JSL Sprite_CheckDamageFromPlayer
LDA.w SprTimerA, X : BNE +
INC.w SprMiscG, X
%SetTimerA($20)
JSL Sprite_DirectionToFacePlayer
; LDA.w SprMiscC, X : AND.b #$03 : TAY
; LDA.w Sprite_Octorok_Move_direction, Y : STA.w SprAction, X
+
RTS
}
WaterOctorok_Attacking:
{
JSL Sprite_CheckDamageFromPlayer
LDA.w SprTimerA, X : BNE +
INC.w SprMiscG, X
%SetTimerA($10)
RTS
+
JSR Octorok_ShootSingle
RTS
}
WaterOctorok_Hiding:
{
LDA.w SprTimerA, X : BNE +
LDA.b #$04 : STA.w SprAction, X
STZ.w SprMiscG, X
%SetTimerA($40)
+
RTS
}
}
```
## Projectile Spawning (`Octorok_ShootSingle`, `Octorok_Shoot4Ways`, `Octorok_SpawnRock`)
These routines handle the spawning and animation of Octorok projectiles.
* **`Octorok_ShootSingle`**: Manages the animation and timing for shooting a single rock projectile.
* **`Octorok_Shoot4Ways`**: Manages the animation, timing, and direction changes for shooting rock projectiles in four cardinal directions.
* **`Octorok_SpawnRock`**: Spawns a rock projectile (sprite ID `$0C`) with specific initial offsets and speeds based on the Octorok's current direction.
```asm
Octorok_ShootSingle:
{
LDA.w SprTimerA, X : CMP.b #$1C : BNE .bide_time
PHA
JSR Octorok_SpawnRock
PLA
.bide_time
LSR #3
TAY
LDA.w .mouth_anim_step, Y : STA.w SprMiscB, X
RTS
.mouth_anim_step
db $00, $02, $02, $02
db $01, $01, $01, $00
db $00, $00, $00, $00
db $02, $02, $02, $02
db $02, $01, $01, $00
}
Octorok_Shoot4Ways:
{
LDA.w SprTimerA, X
PHA
CMP.b #$80 : BCS .animate
AND.b #$0F : BNE .delay_turn
PHA
LDY.w SprMiscC, X
LDA.w .next_direction, Y : STA.w SprMiscC, X
PLA
.delay_turn
CMP.b #$08 : BNE .animate
JSR Octorok_SpawnRock
.animate
PLA
LSR #4
TAY
LDA.w .mouth_anim_step, Y : STA.w SprMiscB, X
RTS
.next_direction
db $02, $03, $01, $00
.mouth_anim_step
db $02, $02, $02, $02
db $02, $02, $02, $02
db $01, $00
}
Octorok_SpawnRock:
{
LDA.b #$07 : JSL SpriteSFX_QueueSFX2WithPan
LDA.b #$0C : JSL Sprite_SpawnDynamically : BMI .fired_a_blank
PHX
LDA.w SprMiscC,X
TAX
LDA.b $00 : CLC : ADC.w .offset_x_low,X : STA.w SprX,Y
LDA.b $01 : ADC.w .offset_x_high,X : STA.w SprXH,Y
LDA.b $02 : CLC : ADC.w .offset_y_low,X : STA.w SprY,Y
LDA.b $03 : ADC.w .offset_y_high,X : STA.w SprYH,Y
LDA.w SprMiscC,Y
TAX
LDA.w .rock_speed_x,X : STA.w SprXSpeed,Y
LDA.w .rock_speed_y,X : STA.w SprYSpeed,Y
PLX
.fired_a_blank
RTS
.offset_x_low
db 12, -12, 0, 0
.offset_x_high
db 0, -1, 0, 0
.offset_y_low
db 4, 4, 12, -12
.offset_y_high
db 0, 0, 0, -1
.rock_speed_x
db 44, -44, 0, 0
.rock_speed_y
db 0, 0, 44, -44
}
```
## Drawing (`Sprite_Octorok_Draw`)
The drawing routine handles OAM allocation, animation, and palette adjustments. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_Octorok_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, X : TAY ;Animation Frame
LDA .start_index, Y : STA $06
LDA.w SprFlash : STA $08
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
INY
LDA .chr, X : STA ($90), Y
INY
LDA .properties, X : ORA $08 : 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, $04, $05, $06, $07, $08
.nbr_of_tiles
db 0, 0, 0, 0, 0, 0, 0, 0, 0
.chr
db $80
db $80
db $82
db $82
db $A0
db $A2
db $A0
db $A2
db $AA ; Water Octorok
.properties
db $0D
db $4D
db $0D
db $4D
db $0D
db $0D
db $4D
db $4D
db $3D ; Water Octorok
}
```
## Design Patterns
* **Subtype-based Behavior**: The Octorok utilizes `SprSubtype` to implement distinct behaviors for Land and Water Octoroks, including different main logic routines and conditional drawing (shadow vs. water ripple).
* **Dynamic Transformation**: A Land Octorok can dynamically transform into a Water Octorok if it moves onto a water tile, showcasing a unique environmental interaction.
* **Complex State Machines**: Both Land and Water Octoroks employ intricate state machines to manage their movement, attack patterns, and emerging/hiding behaviors, making them engaging enemies.
* **Projectile Attacks**: The Octorok can perform both single-shot and four-way projectile attacks, adding variety to its offensive capabilities.
* **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 and positioning.

View File

@@ -0,0 +1,221 @@
# Pols Voice Sprite Analysis
This document provides a detailed analysis of the `pols_voice.asm` sprite, outlining its properties, core routines, and behavioral patterns.
## 1. Sprite Properties
The following `!SPRID` constants define Pols Voice's fundamental characteristics:
```asm
!SPRID = Sprite_PolsVoice
!NbrTiles = 02 ; Number of tiles used in a frame
!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = 10 ; Number of Health the sprite have
!Damage = 00 ; (08 is a whole heart), 04 is half heart
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 00 ; Unused in this template (can be 0 to 7)
!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = your sprite continue to live offscreen
!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layer for collision
!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk shallow water
!Blockable = 00 ; 01 = can be blocked by link's shield?
!Prize = 00 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
```
**Note:** `!Health` is set to `10` and is not dynamically determined by Link's sword level.
## 2. Core Routines
### 2.1. `Sprite_PolsVoice_Long` (Main Loop)
This is the primary entry point for Pols Voice's per-frame execution. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active.
```asm
Sprite_PolsVoice_Long:
{
PHB : PHK : PLB
JSR Sprite_PolsVoice_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_PolsVoice_Main
.SpriteIsNotActive
PLB
RTL
}
```
### 2.2. `Sprite_PolsVoice_Prep` (Initialization)
This routine is executed once when Pols Voice is first spawned. It initializes `SprTimerA` to `$80` and clears `SprDefl` and `SprTileDie`.
```asm
Sprite_PolsVoice_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprTimerA, X
STZ.w SprDefl, X
STZ.w SprTileDie, X
PLB
RTL
}
```
### 2.3. `Sprite_PolsVoice_Main` (Behavioral State Machine)
This routine manages Pols Voice's AI through a state machine, using `SprAction, X` to determine its current behavior. It includes states for moving around and hopping around, with a unique interaction based on the flute song.
```asm
Sprite_PolsVoice_Main:
{
JSR PolsVoice_CheckForFluteSong ; Check for flute song interaction
%SpriteJumpTable(PolsVoice_MoveAround,
PolsVoice_HopAround)
PolsVoice_MoveAround:
{
%StartOnFrame(0)
%PlayAnimation(0,3,10)
;$09 = speed, $08 = max height
LDA #$05 : STA $09
LDA #$02 : STA $08
JSL Sprite_BounceTowardPlayer
JSL Sprite_BounceFromTileCollision
JSL Sprite_DamageFlash_Long
%DoDamageToPlayerSameLayerOnContact()
JSL GetRandomInt : AND #$3F : BNE .not_done ; Random chance to change state
LDA #$04 : STA.w SprTimerA, X
%GotoAction(1) ; Transition to PolsVoice_HopAround
.not_done
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage ; Check if Link damages Pols Voice
JSL Sprite_DirectionToFacePlayer
; Apply the speed positive or negative speed
LDA $0E : BPL .not_up
LDA #$20 : STA.w SprYSpeed, X
BRA .not_down
.not_up
LDA #$E0 : STA.w SprYSpeed, X
.not_down
LDA $0F : BPL .not_right
LDA #$20 : STA.w SprXSpeed, X
BRA .not_left
.not_right
LDA #$E0 : STA.w SprXSpeed, X
.not_left
LDA #$04 : STA.w SprTimerA, X
%GotoAction(1) ; Transition to PolsVoice_HopAround
.no_damage
RTS
}
PolsVoice_HopAround:
{
%StartOnFrame(4)
%PlayAnimation(4,4,10)
JSL Sprite_MoveXyz
JSL Sprite_BounceFromTileCollision
JSL Sprite_DamageFlash_Long
%DoDamageToPlayerSameLayerOnContact()
LDA.w SprTimerA, X : BNE .not_done ; If timer A is not 0
%GotoAction(0) ; Transition back to PolsVoice_MoveAround
.not_done
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage ; Check if Link damages Pols Voice
JSL Sprite_InvertSpeed_XY ; Invert speed
.no_damage
RTS
}
}
```
### 2.4. `PolsVoice_CheckForFluteSong`
This routine checks if the player is currently playing the flute (`SongFlag`). If the flute is being played, Pols Voice despawns (`STZ.w SprState, X`) and forces a prize drop.
```asm
PolsVoice_CheckForFluteSong:
{
; If the player plays the flute
LDA.b SongFlag : BEQ + ; Check SongFlag
LDA.b #$03 : STA.w SprState, X ; Set sprite state to despawn
JSL ForcePrizeDrop_long ; Force prize drop
+
RTS
}
```
### 2.5. `Sprite_PolsVoice_Draw` (Drawing Routine)
This routine is responsible for rendering Pols Voice's graphics. It uses the `%DrawSprite()` macro, which reads from a set of data tables to handle its appearance and animation.
```asm
Sprite_PolsVoice_Draw:
{
%DrawSprite()
.start_index
db $00, $01, $02, $03, $04
.nbr_of_tiles
db 0, 0, 0, 0, 1
.x_offsets
dw 0
dw 0
dw 0
dw 0
dw 0, 0
.y_offsets
dw 0
dw 0
dw 0
dw 0
dw -4, -20
.chr
db $6C
db $6A
db $6C
db $6A
db $6E, $4E
.properties
db $3B
db $3B
db $3B
db $7B
db $3B, $3B
.sizes
db $02
db $02
db $02
db $02
db $02, $02
}
```
## 3. Key Behaviors and Implementation Details
* **Fixed Health:** Unlike many other sprites, Pols Voice has a fixed health of `10` and its health is not dynamically scaled based on Link's sword level.
* **State Management:** Pols Voice uses `SprAction, X` and `%SpriteJumpTable` to manage its `PolsVoice_MoveAround` and `PolsVoice_HopAround` states. Transitions between these states are triggered by timers or random chance.
* **Movement Patterns:** Pols Voice moves by bouncing towards the player (`Sprite_BounceTowardPlayer`) and also has a hopping movement (`PolsVoice_HopAround`). It reacts to tile collisions by bouncing (`Sprite_BounceFromTileCollision`).
* **Flute Song Interaction:** A unique and defining characteristic of Pols Voice is its vulnerability to the flute song. When Link plays the flute (`SongFlag` is set), Pols Voice immediately despawns and drops a prize (`ForcePrizeDrop_long`). This is a classic Zelda enemy mechanic.
* **Damage Reaction:** When damaged by Link, Pols Voice inverts its speed (`Sprite_InvertSpeed_XY`) and transitions to the `PolsVoice_HopAround` state, providing a temporary reprieve or change in behavior.
* **Custom OAM Drawing:** Pols Voice uses the `%DrawSprite()` macro with OAM data tables to render its appearance and animations.
* **`SprTimerA` Usage:** This timer controls the duration of the `PolsVoice_HopAround` state before transitioning back to `PolsVoice_MoveAround`.

View File

@@ -0,0 +1,233 @@
# Poltergeist Sprite Analysis
This document provides a detailed analysis of the `poltergeist.asm` sprite, outlining its properties, core routines, and behavioral patterns.
## 1. Sprite Properties
The following `!SPRID` constants define Poltergeist's fundamental characteristics:
```asm
!SPRID = Sprite_PolsVoice
!NbrTiles = 02 ; Number of tiles used in a frame
!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = 10 ; Number of Health the sprite have
!Damage = 00 ; (08 is a whole heart), 04 is half heart
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 00 ; Unused in this template (can be 0 to 7)
!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = your sprite continue to live offscreen
!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layer for collision
!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk shallow water
!Blockable = 00 ; 01 = can be blocked by link's shield?
!Prize = 00 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
```
**Note:** `!Health` is set to `10` and is dynamically determined during initialization based on Link's sword level.
## 2. Core Routines
### 2.1. `Sprite_Poltergeist_Long` (Main Loop)
This is the primary entry point for Poltergeist's per-frame execution. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active.
```asm
Sprite_Poltergeist_Long:
{
PHB : PHK : PLB
JSR Sprite_Poltergeist_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Poltergeist_Main
.SpriteIsNotActive
PLB
RTL
}
```
### 2.2. `Sprite_Poltergeist_Prep` (Initialization)
This routine is executed once when Poltergeist is first spawned. It sets its health based on Link's sword level and initializes `SprTimerA` and `SprTimerB`.
```asm
Sprite_Poltergeist_Prep:
{
PHB : PHK : PLB
LDA.l Sword : DEC A : TAY
LDA.w .health, Y : STA.w SprHealth, X
LDA.b #$80 : STA.w SprTimerA, X
LDA.b #$80 : STA.w SprTimerB, X
PLB
RTL
.health
db $06, $0A, $0C, $10
}
```
### 2.3. `Sprite_Poltergeist_Main` (Behavioral State Machine)
This routine manages Poltergeist's AI through a state machine, using `SprAction, X` to determine its current behavior. It includes states for moving, attacking, and being stunned.
```asm
Sprite_Poltergeist_Main:
{
JSL Sprite_DamageFlash_Long
%SpriteJumpTable(Poltergeist_Move,
Poltergeist_Attack,
Poltergeist_Stunned)
Poltergeist_Move:
{
%PlayAnimation(0, 1, 16)
JSR Sprite_Poltergeist_Move
RTS
}
Poltergeist_Attack:
{
%PlayAnimation(2, 3, 16)
JSL Sprite_Move
JSL Sprite_CheckDamageToPlayer
LDA.w SprTimerA, X : BNE + ; If timer A is not 0
%GotoAction(0) ; Transition back to Poltergeist_Move
+
RTS
}
Poltergeist_Stunned:
{
%PlayAnimation(4, 5, 16)
JSL Sprite_Move
JSL Sprite_CheckDamageToPlayer
LDA.w SprTimerA, X : BNE + ; If timer A is not 0
%GotoAction(0) ; Transition back to Poltergeist_Move
+
RTS
}
}
```
### 2.4. `Sprite_Poltergeist_Move` (Movement Logic)
This routine handles Poltergeist's movement patterns, including moving towards Link, bouncing from tile collisions, and changing direction randomly.
```asm
Sprite_Poltergeist_Move:
{
JSL Sprite_Move
JSL Sprite_BounceFromTileCollision
JSL Sprite_PlayerCantPassThrough
JSL Sprite_CheckIfRecoiling
LDA.w SprTimerC, X : BNE ++ ; Check timer C
JSL GetRandomInt : AND #$3F : BNE ++ ; Random chance to change direction
LDA.b #$40 : STA.w SprTimerC, X
JSL Sprite_SelectNewDirection
++
LDA.w SprTimerA, X : BNE + ; Check timer A
JSL Sprite_IsToRightOfPlayer : CPY.b #$01 : BNE .ToRight
%GotoAction(1) ; Transition to Poltergeist_Attack
JMP .Continue
.ToRight
%GotoAction(1) ; Transition to Poltergeist_Attack
LDA.b #$20 : STA.w SprTimerA, X
JMP .Continue
+
%GotoAction(0) ; Transition to Poltergeist_Move
.Continue
LDA.w SprMiscB, X
JSL JumpTableLocal
dw PoltergeistMove
PoltergeistMove:
{
JSL GetRandomInt : AND.b #$03
JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_CheckTileCollision
JSL Sprite_CheckDamageFromPlayer
JSL Sprite_CheckDamageToPlayer
RTS
}
}
```
### 2.5. `Sprite_Poltergeist_Draw` (Drawing Routine)
This routine is responsible for rendering Poltergeist's graphics. It uses the `%DrawSprite()` macro, which reads from a set of data tables to handle its multi-tile appearance and animation.
```asm
Sprite_Poltergeist_Draw:
{
%DrawSprite()
.start_index
db $00, $03, $06, $09, $0C, $0F
.nbr_of_tiles
db 2, 2, 2, 2, 2, 2
.x_offsets
dw 0, 0, 8
dw 8, 0, 0
dw 0, 0, 8
dw 0, 0, 8
dw 0, 8, 0
dw 0, 8, 0
.y_offsets
dw -8, 0, -8
dw -8, 0, -8
dw 0, -8, -8
dw 0, -8, -8
dw 0, -8, -8
dw 0, -8, -8
.chr
db $3A, $02, $3B
db $3A, $02, $3B
db $20, $00, $01
db $22, $10, $11
db $20, $00, $01
db $22, $10, $11
.properties
db $2B, $2B, $2B
db $6B, $6B, $6B
db $2B, $2B, $2B
db $2B, $2B, $2B
db $6B, $6B, $6B
db $6B, $6B, $6B
.sizes
db $00, $02, $00
db $00, $02, $00
db $02, $00, $00
db $02, $00, $00
db $02, $00, $00
db $02, $00, $00
}
```
## 3. Key Behaviors and Implementation Details
* **Dynamic Health:** Poltergeist's health is determined at spawn time based on Link's current sword level, allowing for dynamic difficulty scaling.
* **State Management:** Poltergeist uses `SprAction, X` and `%SpriteJumpTable` to manage its `Poltergeist_Move`, `Poltergeist_Attack`, and `Poltergeist_Stunned` states. Transitions between these states are triggered by timers and player proximity.
* **Movement Patterns:** Poltergeist moves towards Link (`Sprite_ApplySpeedTowardsPlayer`) with random direction changes (`Sprite_SelectNewDirection`). It also handles bouncing from tile collisions and cannot be passed through by Link.
* **Attack Behavior:** Poltergeist transitions to an `Poltergeist_Attack` state, which likely involves a direct contact attack or a projectile, and then returns to its movement state after a timer.
* **Stunned State:** When damaged, Poltergeist enters a `Poltergeist_Stunned` state, during which it is temporarily incapacitated. It recovers from this state after a timer.
* **Conditional Invulnerability:** The sprite properties indicate `!ImpervSwordHammer = 00`, but the code does not explicitly set it to `01` when stunned. This might be an oversight or handled by a global routine. However, the presence of `SprDefl` in `_Prep` suggests some form of deflection is intended.
* **Custom OAM Drawing:** Poltergeist uses the `%DrawSprite()` macro with detailed OAM data tables to render its multi-tile appearance and animations across its different states.
* **`SprTimerA`, `SprTimerB`, `SprTimerC` Usage:** These timers control the duration of attack and stunned states, and the frequency of direction changes.

View File

@@ -0,0 +1,287 @@
# Puffstool Sprite Analysis
This document provides a detailed analysis of the `puffstool.asm` sprite, outlining its properties, core routines, and behavioral patterns.
## 1. Sprite Properties
The following `!SPRID` constants define Puffstool's fundamental characteristics:
```asm
!SPRID = Sprite_Puffstool
!NbrTiles = 02 ; Number of tiles used in a frame
!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = 0 ; Number of Health the sprite have (dynamically set in _Prep)
!Damage = 0 ; (08 is a whole heart), 04 is half heart
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 0 ; Unused in this template (can be 0 to 7)
!Hitbox = 0 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = your sprite continue to live offscreen
!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layer for collision
!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk shallow water
!Blockable = 00 ; 01 = can be blocked by link's shield?
!Prize = 0 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
```
**Note:** `!Health`, `!Damage`, `!Hitbox`, and `!Prize` are initially set to `0` but are dynamically determined during initialization.
## 2. Core Routines
### 2.1. `Sprite_Puffstool_Long` (Main Loop)
This is the primary entry point for Puffstool's per-frame execution. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active.
```asm
Sprite_Puffstool_Long:
{
PHB : PHK : PLB
JSR Sprite_Puffstool_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Puffstool_Main
.SpriteIsNotActive
PLB
RTL
}
```
### 2.2. `Sprite_Puffstool_Prep` (Initialization)
This routine is executed once when Puffstool is first spawned. It sets its health based on Link's sword level and initializes `SprDefl`.
```asm
Sprite_Puffstool_Prep:
{
PHB : PHK : PLB
LDA.l $7EF359 : TAY
LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level
LDA.b #$80 : STA.w SprDefl, X
PLB
RTL
.health
db $04, $08, $0A, $10 ; Health values for each sword level
}
```
### 2.3. `Sprite_Puffstool_Main` (Behavioral State Machine)
This routine manages Puffstool's AI through a state machine, using `SprAction, X` to determine its current behavior. It includes states for walking, being stunned, and spawning spores.
```asm
Sprite_Puffstool_Main:
{
%SpriteJumpTable(Puffstool_Walking,
Puffstool_Stunned,
Puffstool_Spores)
Puffstool_Walking:
{
%PlayAnimation(0,6,10)
JSL Sprite_PlayerCantPassThrough
LDA.w SprTimerA, X : BNE + ; If timer A is not 0
JSL Sprite_SelectNewDirection ; Select a new direction
+
JSL Sprite_MoveXyz
JSL Sprite_BounceFromTileCollision
JSL Sprite_DamageFlash_Long
JSL ThrownSprite_TileAndSpriteInteraction_long ; Interact with thrown objects
JSL Sprite_CheckIfRecoiling
JSL Sprite_CheckDamageFromPlayer : BCC .no_dano ; Check if Link damages Puffstool
%GotoAction(1) ; Transition to Puffstool_Stunned
%SetTimerA($60)
%SetTimerF($20)
.no_dano
RTS
}
Puffstool_Stunned:
{
%PlayAnimation(7,7,10)
JSL Sprite_CheckIfLifted
JSL Sprite_DamageFlash_Long
JSL ThrownSprite_TileAndSpriteInteraction_long
LDA.w SprTimerA, X : BNE + ; If timer A is not 0
%GotoAction(0) ; Transition back to Puffstool_Walking
JSL GetRandomInt : AND.b #$1F : BEQ .bomb ; Random chance to spawn bomb
JSR Puffstool_SpawnSpores ; Spawn spores
RTS
.bomb
LDA.b #$4A ; SPRITE 4A (Bomb sprite ID)
LDY.b #$0B
JSL Sprite_SpawnDynamically : BMI .no_space
JSL Sprite_SetSpawnedCoordinates
JSL Sprite_TransmuteToBomb ; Transform into a bomb
.no_space
+
RTS
}
Puffstool_Spores:
{
%StartOnFrame(8)
%PlayAnimation(8,11,10)
JSL Sprite_MoveXyz
JSL Sprite_CheckDamageToPlayerSameLayer
LDA.w SprTimerC, X : BNE + ; If timer C is not 0
JSL ForcePrizeDrop_long ; Force prize drop
STZ.w SprState, X ; Clear sprite state (despawn?)
+
RTS
}
}
```
### 2.4. `Puffstool_SpawnSpores`
This routine is responsible for spawning spore projectiles. It plays a sound effect and then spawns multiple spore sprites, setting their initial properties like speed, altitude, and timers.
```asm
Puffstool_SpawnSpores:
{
LDA.b #$0C ; SFX2.0C
JSL $0DBB7C ; SpriteSFX_QueueSFX2WithPan
LDA.b #$03 : STA.b $0D ; Number of spores to spawn
.nth_child
LDA.b #$B1 ; Spore sprite ID (assuming $B1 is the spore sprite ID)
JSL Sprite_SpawnDynamically : BMI .no_space
JSL Sprite_SetSpawnedCoordinates
PHX
LDX.b $0D
LDA.w .speed_x, X : STA.w SprXSpeed, Y
LDA.w .speed_y, X : STA.w SprYSpeed, Y
LDA.b #$20 : STA.w $0F80, Y ; Altitude
LDA.b #$FF : STA.w $0E80, Y ; Gravity
LDA.b #$40 : STA.w SprTimerC, Y
LDA.b #$01 : STA.w SprSubtype, Y
LDA.b #$02 : STA.w SprAction, Y
PLX
.no_space
DEC.b $0D
BPL .nth_child
RTS
.speed_x
db 11, -11, -11, 11
.speed_y
db 0, 11, 0, -11
}
```
### 2.5. `Sprite_Puffstool_Draw` (Drawing Routine)
This routine is responsible for rendering Puffstool's graphics. It uses the `%DrawSprite()` macro, which reads from a set of data tables to handle its multi-tile appearance and animation.
```asm
Sprite_Puffstool_Draw:
{
%DrawSprite()
.start_index
db $00, $02, $04, $06, $08, $0A, $0C, $0E, $0F, $10, $11, $12
.nbr_of_tiles
db 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
.x_offsets
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0
dw 0
dw 0
dw 0
dw 4
.y_offsets
dw -8, 0
dw 0, -8
dw 0, -8
dw 0, -8
dw 0, -8
dw 0, -8
dw 0, -8
dw 0
dw 0
dw 0
dw 0
dw 4
.chr
db $C0, $D0
db $D2, $C2
db $D4, $C4
db $D2, $C2
db $D0, $C0
db $D2, $C2
db $D4, $C4
db $D6
db $EA
db $C8
db $E8
db $F7
.properties
db $33, $33
db $33, $33
db $33, $33
db $33, $33
db $33, $33
db $73, $73
db $73, $73
db $3D
db $33
db $33
db $33
db $33
.sizes
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02
db $02
db $02
db $02
db $00
}
```
## 3. Key Behaviors and Implementation Details
* **Dynamic Health:** Puffstool's health is determined at spawn time based on Link's current sword level, allowing for dynamic difficulty scaling.
* **State Management:** Puffstool uses `SprAction, X` and `%SpriteJumpTable` to manage its `Puffstool_Walking`, `Puffstool_Stunned`, and `Puffstool_Spores` states.
* **Movement Patterns:** In its walking state, Puffstool moves with random direction changes (`Sprite_SelectNewDirection`) and interacts with the environment (`Sprite_MoveXyz`, `Sprite_BounceFromTileCollision`).
* **Stunned State and Counter-Attack:** When damaged, Puffstool enters a `Puffstool_Stunned` state. After a timer, it either spawns multiple spores (`Puffstool_SpawnSpores`) or, with a random chance, transforms into a bomb (`Sprite_TransmuteToBomb`). This provides a unique counter-attack mechanism.
* **Spore Attack:** Puffstool can spawn multiple spore projectiles (`Puffstool_SpawnSpores`) that have their own movement and interaction logic. These spores are spawned with initial speed, altitude, and gravity.
* **Bomb Spawning/Transformation:** A unique behavior where Puffstool can transform into a bomb (`Sprite_TransmuteToBomb`) when stunned, adding an element of surprise and danger.
* **Interaction with Thrown Objects:** The use of `ThrownSprite_TileAndSpriteInteraction_long` suggests Puffstool can be lifted and thrown by Link, or interacts with other thrown objects.
* **Custom OAM Drawing:** Puffstool uses the `%DrawSprite()` macro with detailed OAM data tables to render its multi-tile appearance and animations across its different states.
* **`SprTimerA`, `SprTimerF`, `SprTimerC` Usage:** These timers control the duration of the stunned state, the delay before spawning spores/bombs, and the lifespan of the spawned spores.

View File

@@ -0,0 +1,221 @@
# Sea Urchin Sprite Analysis
This document provides a detailed analysis of the `sea_urchin.asm` sprite, outlining its properties, core routines, and behavioral patterns.
## 1. Sprite Properties
The following `!SPRID` constants define Sea Urchin's fundamental characteristics:
```asm
!SPRID = Sprite_SeaUrchin
!NbrTiles = 04 ; Number of tiles used in a frame
!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = 06 ; Number of Health the sprite have
!Damage = 04 ; (08 is a whole heart), 04 is half heart
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 01 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 00 ; Unused in this template (can be 0 to 7)
!Hitbox = 00 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = your sprite continue to live offscreen
!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layer for collision
!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk shallow water
!Blockable = 01 ; 01 = can be blocked by link's shield?
!Prize = 03 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
```
**Note:** `!Health` is fixed at `06` and `!Damage` is `04` (half a heart).
## 2. Core Routines
### 2.1. `Sprite_SeaUrchin_Long` (Main Loop)
This is the primary entry point for Sea Urchin's per-frame execution. It handles drawing, shadow rendering, and then dispatches to the main logic routine if the sprite is active.
```asm
Sprite_SeaUrchin_Long:
{
PHB : PHK : PLB
JSR Sprite_SeaUrchin_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_SeaUrchin_Main
.SpriteIsNotActive
PLB
RTL
}
```
### 2.2. `Sprite_SeaUrchin_Prep` (Initialization)
This routine is executed once when Sea Urchin is first spawned. It sets the initial prize and conditionally modifies its imperviousness and prize based on the `WORLDFLAG` (likely for different game states or areas, such as the Eon Sea).
```asm
Sprite_SeaUrchin_Prep:
{
PHB : PHK : PLB
LDA #$01 : STA.w SprPrize, X ; Default prize
LDA.w WORLDFLAG : BEQ + ; Check WORLDFLAG
; Eon Sea Urchin impervious to sword
LDA.b #%10000100 : STA.w SprDefl, X ; Set imperviousness flags
LDA.b #$07 : STA.w SprPrize, X ; Change prize for Eon Sea
+
PLB
RTL
}
```
### 2.3. `Sprite_SeaUrchin_Main` (Behavioral State Machine)
This routine manages Sea Urchin's AI through a simple state machine, using `SprAction, X` to determine its current behavior. It includes `Idle` and `Death` states.
```asm
Sprite_SeaUrchin_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw Idle
dw Death
Idle:
{
%PlayAnimation(0,3,8)
%PlayerCantPassThrough()
%DoDamageToPlayerSameLayerOnContact()
JSL Sprite_CheckDamageFromPlayer : BCC .NoDamage
%GotoAction(1) ; Transition to Death state if damaged
.NoDamage
RTS
}
Death:
{
LDA.b #$06 : STA.w SprState, X ; Set sprite state to despawn
LDA.b #$0A : STA.w SprTimerA, X
STZ.w SprPrize,X
JSL ForcePrizeDrop_long ; Force prize drop
LDA.b #$09 : JSL SpriteSFX_QueueSFX3WithPan ; Play sound effect
RTS
}
}
```
### 2.4. `Sprite_SeaUrchin_Draw` (Drawing Routine)
This routine is responsible for rendering Sea Urchin's graphics. It uses a custom OAM allocation and manipulation logic to handle its multi-tile appearance and animation.
```asm
Sprite_SeaUrchin_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.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, $04, $05, $06, $07
.nbr_of_tiles
db 0, 0, 0, 0, 0, 0, 0, 0
.x_offsets
dw 0
dw 0
dw 0
dw 0
dw 0
dw 0
dw 0
dw 0
.y_offsets
dw 0
dw -1
dw 0
dw -1
dw 0
dw -1
dw 0
dw -1
.chr
db $EA
db $EC
db $EA
db $EC
db $EA
db $EC
db $EA
db $EC
.properties
db $29
db $29
db $69
db $69
db $29
db $29
db $69
db $69
}
```
## 3. Key Behaviors and Implementation Details
* **Fixed Health:** Sea Urchin has a fixed health of `06` and its health is not dynamically scaled based on Link's sword level.
* **Dynamic Prize Drop and Imperviousness:** A notable feature is its conditional behavior based on the `WORLDFLAG`. If this flag is set (e.g., indicating a specific game area like the Eon Sea), the Sea Urchin becomes impervious to sword attacks (`SprDefl`) and drops a different prize. This demonstrates how global game state variables can influence individual sprite properties.
* **State Management:** Sea Urchin uses a simple state machine with `Idle` and `Death` states, managed by `SprAction, X` and `JumpTableLocal`.
* **Movement Patterns:** The Sea Urchin has a simple idle animation (`%PlayAnimation(0,3,8)`) and does not exhibit complex movement behaviors. It remains stationary but can be pushed by Link (`%PlayerCantPassThrough()`).
* **Damage Handling:** Upon taking damage from Link (`Sprite_CheckDamageFromPlayer`), the Sea Urchin transitions to its `Death` state. In this state, it despawns (`STZ.w SprState, X`), forces a prize drop (`ForcePrizeDrop_long`), and plays a sound effect (`SpriteSFX_QueueSFX3WithPan`).
* **Custom OAM Drawing:** Sea Urchin utilizes a custom OAM drawing routine to render its multi-tile appearance and animation. The drawing logic includes coordinate calculations with `REP`/`SEP` for 16-bit operations.

View File

@@ -0,0 +1,313 @@
# Thunder Ghost Sprite Analysis
This document provides a detailed analysis of the `thunder_ghost.asm` sprite, outlining its properties, core routines, and behavioral patterns.
## 1. Sprite Properties
The following `!SPRID` constants define Thunder Ghost's fundamental characteristics:
```asm
!SPRID = Sprite_ThunderGhost
!NbrTiles = 03 ; Number of tiles used in a frame
!Harmless = 00 ; 00 = Sprite is Harmful, 01 = Sprite is Harmless
!HVelocity = 00 ; Is your sprite going super fast? put 01 if it is
!Health = 10 ; Number of Health the sprite have (dynamically set in _Prep)
!Damage = 00 ; (08 is a whole heart), 04 is half heart
!DeathAnimation = 00 ; 00 = normal death, 01 = no death animation
!ImperviousAll = 00 ; 00 = Can be attack, 01 = attack will clink on it
!SmallShadow = 00 ; 01 = small shadow, 00 = no shadow
!Shadow = 00 ; 00 = don't draw shadow, 01 = draw a shadow
!Palette = 00 ; Unused in this template (can be 0 to 7)
!Hitbox = 09 ; 00 to 31, can be viewed in sprite draw tool
!Persist = 00 ; 01 = your sprite continue to live offscreen
!Statis = 00 ; 00 = is sprite is alive?, (kill all enemies room)
!CollisionLayer = 00 ; 01 = will check both layer for collision
!CanFall = 00 ; 01 sprite can fall in hole, 01 = can't fall
!DeflectArrow = 00 ; 01 = deflect arrows
!WaterSprite = 00 ; 01 = can only walk shallow water
!Blockable = 00 ; 01 = can be blocked by link's shield?
!Prize = 00 ; 00-15 = the prize pack the sprite will drop from
!Sound = 00 ; 01 = Play different sound when taking damage
!Interaction = 00 ; ?? No documentation
!Statue = 00 ; 01 = Sprite is statue
!DeflectProjectiles = 00 ; 01 = Sprite will deflect ALL projectiles
!ImperviousArrow = 00 ; 01 = Impervious to arrows
!ImpervSwordHammer = 00 ; 01 = Impervious to sword and hammer attacks
!Boss = 00 ; 00 = normal sprite, 01 = sprite is a boss
```
**Note:** `!Health` is initially set to `10` but is dynamically determined during initialization based on Link's sword level.
## 2. Core Routines
### 2.1. `Sprite_ThunderGhost_Long` (Main Loop)
This is the primary entry point for Thunder Ghost's per-frame execution. It handles drawing, shadow rendering (conditionally), and then dispatches to the main logic routine if the sprite is active.
```asm
Sprite_ThunderGhost_Long:
{
PHB : PHK : PLB
JSR Sprite_ThunderGhost_Draw
LDA.w SprAction, X : CMP.b #$03 : BCS + ; Don't draw shadow if casting thunder
JSL Sprite_DrawShadow
+
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_ThunderGhost_Main
.SpriteIsNotActive
PLB
RTL
}
```
### 2.2. `Sprite_ThunderGhost_Prep` (Initialization)
This routine is executed once when Thunder Ghost is first spawned. It sets its health based on Link's sword level and initializes `SprTimerA` and `SprTimerB`.
```asm
Sprite_ThunderGhost_Prep:
{
PHB : PHK : PLB
LDA.l Sword : DEC A : TAY
LDA.w .health, Y : STA.w SprHealth, X ; Set health based on sword level
LDA.b #$08 : STA.w SprTimerB, X
LDA.b #$08 : STA.w SprTimerA, X
PLB
RTL
.health
db $06, $0A, $0C, $10 ; Health values for each sword level
}
```
### 2.3. `Sprite_ThunderGhost_Main` (Behavioral State Machine)
This routine manages Thunder Ghost's AI through a state machine, using `SprAction, X` to determine its current behavior. It includes states for facing forward, left, and right, as well as states for casting thunder in different directions.
```asm
Sprite_ThunderGhost_Main:
{
%SpriteJumpTable(ThunderGhostFaceForward,
ThunderGhostLeft,
ThunderGhostRight,
CastThunderLeft,
CastThunderRight)
ThunderGhostFaceForward:
{
%PlayAnimation(0, 1, 16)
JSR Sprite_ThunderGhost_Move
RTS
}
ThunderGhostLeft:
{
%PlayAnimation(2, 3, 16)
JSR Sprite_ThunderGhost_Move
RTS
}
ThunderGhostRight:
{
%PlayAnimation(4, 5, 16)
JSR Sprite_ThunderGhost_Move
RTS
}
CastThunderLeft:
{
%StartOnFrame(6)
%PlayAnimation(6, 6, 16)
JSL Sprite_CheckDamageToPlayer
JSL Sprite_MoveLong
LDA.w SprTimerA, X : BNE + ; If timer A is not 0
STZ.w SprState, X ; Clear sprite state (despawn?)
+
RTS
}
CastThunderRight:
{
%StartOnFrame(6)
%PlayAnimation(7, 7, 16)
JSL Sprite_CheckDamageToPlayer
JSL Sprite_MoveLong
LDA.w SprTimerA, X : BNE + ; If timer A is not 0
STZ.w SprState, X ; Clear sprite state (despawn?)
+
RTS
}
}
```
### 2.4. `Sprite_ThunderGhost_Move` (Movement and Interaction Logic)
This routine handles Thunder Ghost's movement, collision, and interaction with the player. It also includes logic for randomly triggering lightning attacks and changing its facing direction.
```asm
Sprite_ThunderGhost_Move:
{
JSL Sprite_Move
JSL Sprite_BounceFromTileCollision
JSL Sprite_PlayerCantPassThrough
JSL Sprite_DamageFlash_Long
JSL Sprite_CheckIfRecoiling
LDA.w SprTimerC, X : BNE ++ ; Check timer C
JSL GetRandomInt : AND #$3F : BNE ++ ; Random chance to spawn lightning
LDA.b #$40 : STA.w SprTimerC, X ; Set timer C
JSR SpawnLightningAttack ; Spawn lightning attack
++
LDA.w SprTimerA, X : BNE + ; Check timer A
JSL Sprite_IsToRightOfPlayer : CPY.b #$01 : BNE .ToRight ; Determine if to the right of Link
%GotoAction(1) ; Transition to ThunderGhostLeft
JMP .Continue
.ToRight
%GotoAction(2) ; Transition to ThunderGhostRight
LDA.b #$20 : STA.w SprTimerA, X ; Set timer A
JMP .Continue
+
%GotoAction(0) ; Transition to ThunderGhostFaceForward
.Continue
LDA.w SprMiscB, X
JSL JumpTableLocal
dw ThunderGhostMove
ThunderGhostMove:
{
JSL GetRandomInt : AND.b #$03
JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_CheckTileCollision
JSL Sprite_CheckDamageFromPlayer
JSL Sprite_CheckDamageToPlayer
RTS
}
}
```
### 2.5. `SpawnLightningAttack`
This routine is responsible for spawning the lightning attack sprite. It sets up the lightning's initial properties, including its `SprSubtype`, action, position, and speed, based on Thunder Ghost's position relative to Link.
```asm
SpawnLightningAttack:
{
PHX
LDA.b #$CD ; Sprite ID for lightning (assuming $CD is the lightning sprite ID)
JSL Sprite_SpawnDynamically : BMI .no_space
; Use SprXSpeed, SprYSpeed, SprXRound, SprYRound
; SprX, SprY, SprXH, SprY, to cast the lightning spell
; and make it move off to the bottom left or bottom right
; Y is the ID of the new attack sprite
; X is the ID of the current source sprite
; Left 0 or Right 1
PHY
JSL Sprite_IsToRightOfPlayer : TAY : CMP.b #$01 : BEQ + ; Determine if to the right of Link
LDA.b #$00
JMP .Continue
+
LDA.b #$01
.Continue
CLC : ADC.b #$03
PLY
STA.w SprSubtype, Y ; Set SprSubtype for lightning
STA.w SprAction, Y ; Set action for lightning
LDA.w SprX, X : STA.w SprX, Y
LDA.w SprY, X : STA.w SprY, Y
LDA.w SprXH, X : STA.w SprXH, Y
LDA.w SprYH, X : STA.w SprYH, Y
LDA.w SprXSpeed, X : STA.w SprXSpeed, Y
LDA.w SprYSpeed, X : STA.w SprYSpeed, Y
LDA.b #$02 : STA.w SprXRound, Y
LDA.b #$02 : STA.w SprYRound, Y
LDA.b #$30 : STA.w SprTimerA, Y
LDA.b #$30 : STA.w SprTimerB, Y
.no_space
PLX
RTS
}
```
### 2.6. `Sprite_ThunderGhost_Draw` (Drawing Routine)
This routine is responsible for rendering Thunder Ghost's graphics. It uses the `%DrawSprite()` macro, which reads from a set of data tables to handle its multi-tile appearance and animation.
```asm
Sprite_ThunderGhost_Draw:
{
%DrawSprite()
.start_index
db $00, $03, $06, $09, $0C, $0F, $12, $15
.nbr_of_tiles
db 2, 2, 2, 2, 2, 2, 2, 2
.x_offsets
dw 0, 0, 8
dw 8, 0, 0
dw 0, 0, 8
dw 0, 0, 8
dw 0, 8, 0
dw 0, 8, 0
dw -12, -8, -16
dw 12, 16, 20
.y_offsets
dw -8, 0, -8
dw -8, 0, -8
dw 0, -8, -8
dw 0, -8, -8
dw 0, -8, -8
dw 0, -8, -8
dw 12, 24, 20
dw 12, 24, 12
.chr
db $3A, $02, $3B
db $3A, $02, $3B
db $20, $00, $01
db $22, $10, $11
db $20, $00, $01
db $22, $10, $11
db $28, $2A, $2B
db $28, $2A, $2B
.properties
db $2B, $2B, $2B
db $6B, $6B, $6B
db $2B, $2B, $2B
db $2B, $2B, $2B
db $6B, $6B, $6B
db $6B, $6B, $6B
db $2B, $2B, $2B
db $6B, $2B, $2B
.sizes
db $00, $02, $00
db $00, $02, $00
db $02, $00, $00
db $02, $00, $00
db $02, $00, $00
db $02, $00, $00
db $02, $00, $00
db $02, $00, $00
}
```
## 3. Key Behaviors and Implementation Details
* **Dynamic Health:** Thunder Ghost's health is determined at spawn time based on Link's current sword level, allowing for dynamic difficulty scaling.
* **Conditional Shadow Drawing:** The shadow is not drawn when Thunder Ghost is in its `CastThunderLeft` or `CastThunderRight` states, suggesting a visual distinction during its attack.
* **Lightning Attack:** Thunder Ghost has a random chance to spawn a lightning attack (`SpawnLightningAttack`) at regular intervals, which then becomes an independent sprite with its own movement and interaction logic.
* **State Management:** Thunder Ghost uses `SprAction, X` and `%SpriteJumpTable` to manage its facing direction (forward, left, right) and its thunder-casting states.
* **Movement Patterns:** Thunder Ghost moves randomly and applies speed towards the player, while also bouncing from tile collisions and being unable to be passed through by Link.
* **Projectile Spawning with Directional Logic:** The `SpawnLightningAttack` routine demonstrates how to spawn a projectile (`$CD`) and initialize its properties, including its `SprSubtype` and `SprAction`, based on Thunder Ghost's position relative to Link.
* **`SprTimerA`, `SprTimerB`, `SprTimerC` Usage:** These timers are used to control the frequency of lightning attacks and the duration of facing/movement states.
* **`Sprite_MoveLong`:** Used in the `CastThunderLeft` and `CastThunderRight` states, suggesting a specific movement behavior during the attack animation.

View File

@@ -0,0 +1,80 @@
# Twinrova Boss Sprite Analysis
## Overview
The `twinrova` sprite (ID: `Sprite_Twinrova`, which is `$CE`) is a complex, multi-phase boss designed to override the vanilla Blind and Blind Maiden sprites. It features a dramatic transformation from Blind Maiden into Twinrova, followed by alternating phases where Twinrova switches between Koume (fire) and Kotake (ice) forms, each possessing distinct attacks and environmental interactions.
## Key Properties:
* **Sprite ID:** `Sprite_Twinrova` (`$CE`)
* **Description:** A multi-phase boss that transforms from Blind Maiden, then alternates between fire (Koume) and ice (Kotake) forms.
* **Number of Tiles:** 6
* **Health:** `00` (Health is managed by `Sprite_Twinrova_CheckIfDead` and phase transitions.)
* **Damage:** `00` (Damage dealt to Link is likely handled by its attacks.)
* **Special Properties:**
* `!Boss = 01` (Correctly identified as a boss.)
* `!Shadow = 01` (Draws a shadow.)
* `!Hitbox = 03`
* `!CollisionLayer = 01` (Checks both layers for collision.)
## Custom Variables/Macros:
* `!AnimSpeed = 8`: Defines the animation speed for various states.
* `Twinrova_Front()`, `Twinrova_Back()`, `Twinrova_Ready()`, `Twinrova_Attack()`, `Show_Koume()`, `Show_Kotake()`, `Twinrova_Hurt()`: Macros for playing specific animations, enhancing code readability.
* `$AC`: A RAM address used to store the current attack type (Fire or Ice).
## Main Logic Flow (`Sprite_Twinrova_Main`):
The boss's behavior is governed by a detailed state machine:
* **`Twinrova_Init` (0x00):** Initial state. Displays an introductory message and transitions to `Twinrova_MoveState`.
* **`Twinrova_MoveState` (0x01):** The core movement and phase management state. It checks `SprHealth, X` to determine if Twinrova is in Phase 1 (single entity) or Phase 2 (alternating forms).
* **Phase 1:** Twinrova moves around, randomly spawning Fire/Ice Keese, or preparing Fire/Ice attacks.
* **Phase 2:** Twinrova alternates between `Twinrova_KoumeMode` (fire) and `Twinrova_KotakeMode` (ice) forms.
* **`Twinrova_MoveForwards` (0x02), `Twinrova_MoveBackwards` (0x03):** Handles movement using `Sprite_FloatTowardPlayer` and `Sprite_CheckTileCollision`.
* **`Twinrova_PrepareAttack` (0x04):** Prepares either a Fire or Ice attack based on the value in `$AC`.
* **`Twinrova_FireAttack` (0x05):** Executes a Fire attack. Restores floor tiles, uses `JSL Sprite_Twinrova_FireAttack` (a shared function for the actual attack), and randomly releases fireballs (`ReleaseFireballs`).
* **`Twinrova_IceAttack` (0x06):** Executes an Ice attack using `JSL Sprite_Twinrova_IceAttack` (a shared function).
* **`Twinrova_Hurt` (0x07):** Manages Twinrova taking damage. Plays a hurt animation and, after a timer, determines whether to dodge or retaliate with a fire or ice attack.
* **`Twinrova_KoumeMode` (0x08):** Koume (fire) form. Spawns pit hazards (`AddPitHazard`), falling tiles (`Ganon_SpawnFallingTilesOverlord`), and fireballs (`Sprite_SpawnFireball`). Uses `RageModeMove` for dynamic movement.
* **`Twinrova_KotakeMode` (0x09):** Kotake (ice) form. Can spawn lightning (`JSL $1DE612`) and uses `RageModeMove` for dynamic movement.
* **`Twinrova_Dead` (0x0A):** Handles Twinrova's death sequence, killing all spawned friends and playing a hurt animation.
## Initialization (`Sprite_Twinrova_Prep`):
* Checks for the presence of the Blind Maiden (`$7EF3CC = $06`). If the Maiden is present, Twinrova is killed, indicating that Twinrova spawns *from* the Blind Maiden.
* Sets initial health to `$5A` (90 decimal).
* Configures deflection (`SprDefl = $80`), bump damage (`SprBump = $04`), and ensures Twinrova is not invincible.
* Configures Blind Boss startup parameters and initializes various timers and `SprMisc` variables.
## Death Check (`Sprite_Twinrova_CheckIfDead`):
* Monitors `SprHealth, X`. If health is zero or negative, it triggers the boss's death sequence, setting `SprState = $04` (kill sprite boss style) and `SprAction = $0A` (Twinrova_Dead stage).
## Movement (`RageModeMove`, `DoRandomStrafe`, `VelocityOffsets`):
* **`RageModeMove`:** A sophisticated routine for dynamic, floaty movement. It randomly determines a movement mode (predictive movement towards player, random strafe, random dodge, stay in place) based on timers and probabilities. It also handles evasive actions.
* **`DoRandomStrafe`:** Generates random strafing movement.
* **`VelocityOffsets`:** A table defining X and Y speed offsets for movement.
## Environmental Interactions (`Twinrova_RestoreFloorTile`, `RestoreFloorTile`, `AddPitHazard`, `Ganon_SpawnFallingTilesOverlord`):
* **`Twinrova_RestoreFloorTile` / `RestoreFloorTile`:** Restores floor tiles, likely after they have been modified by an attack.
* **`AddPitHazard`:** Adds a pit hazard to the floor.
* **`Ganon_SpawnFallingTilesOverlord`:** Spawns falling tiles (reused from Ganon's mechanics).
## Drawing (`Sprite_Twinrova_Draw`):
* Uses standard OAM allocation routines.
* Handles complex animation frames, x/y offsets, character data, properties, and sizes for drawing Twinrova.
* Utilizes 16-bit operations for precise drawing calculations.
## Graphics Transfer (`ApplyTwinrovaGraphics`):
* Handles DMA transfer of graphics data (`twinrova.bin`) to VRAM.
## Attack Spawning (`Fireball_Configure`, `ReleaseFireballs`, `Sprite_SpawnFireKeese`, `Sprite_SpawnIceKeese`, `JSL Sprite_SpawnFireball`, `JSL $1DE612` (Sprite_SpawnLightning)):
* Twinrova can spawn various projectiles and enemies, including fireballs, Fire Keese, Ice Keese, and lightning.
## Blind Maiden Integration:
Twinrova's fight is deeply integrated with the Blind Maiden mechanics:
* **`Follower_BasicMover`:** This routine is hooked to check if the follower is the Blind Maiden, triggering the transformation to Twinrova.
* **`Follower_CheckBlindTrigger`:** Checks if the Blind Maiden follower is within a specific trigger area.
* **`Blind_SpawnFromMaiden`:** This is the core routine for the transformation. It applies Twinrova graphics, sets Twinrova's initial state and position based on the Maiden's, and sets various timers and properties.
* **`SpritePrep_Blind_PrepareBattle`:** This routine is overridden to handle Twinrova's prep or to despawn if a room flag is set.
## Discrepancies/Notes:
* **Health Management:** The `!Health` property is `00`. The boss's health is managed by `Sprite_Twinrova_CheckIfDead` and phase transitions.
* **Code Reuse:** There is extensive code reuse from other sprites/bosses (e.g., `Sprite_Twinrova_FireAttack` is also used by KydreeokHead, `Ganon_SpawnFallingTilesOverlord` in Koume mode, `Sprite_SpawnFireKeese`/`Sprite_SpawnIceKeese` in MoveState). This is an efficient practice but requires careful management to ensure thematic consistency and avoid unintended side effects.
* **Hardcoded Addresses:** Several `JSL` calls are to hardcoded addresses (e.g., `JSL $1DE612` for lightning). These should ideally be replaced with named labels for better maintainability.
* **Blind Maiden Overrides:** The boss heavily relies on overriding vanilla Blind Maiden behavior, which is a common ROM hacking technique but requires careful understanding of the original game's code.
* **`TargetPositions`:** This table is defined but appears unused in the provided code.

View File

@@ -0,0 +1,46 @@
# Vampire Bat Mini-Boss Sprite Analysis
## Overview
The `vampire_bat` sprite is a mini-boss, a specialized enemy that utilizes the generic Keese sprite ID (`$11`) but differentiates its behavior through `SprSubtype = 02`. It features more complex movement patterns and attacks compared to a standard Keese, including ascending, flying around, descending, and spawning other Keese.
## Key Properties:
* **Sprite ID:** `0x11` (Custom Keese Subtype 02)
* **Description:** A mini-boss variant of the Keese, with enhanced movement and attack capabilities.
* **Number of Tiles:** 8 (Inherited from the base Keese sprite.)
* **Health:** `32` (decimal, set in `Sprite_Keese_Prep` based on subtype.)
* **Damage:** `00` (Damage dealt to Link is likely handled by its attacks.)
* **Special Properties:**
* `!Boss = 00` (Not marked as a boss, but functions as a mini-boss/special enemy.)
* `!Shadow = 01` (Draws a shadow.)
## Main Logic Flow (`Sprite_VampireBat_Main`):
The Vampire Bat's behavior is governed by a state machine:
* **`VampireBat_Idle` (0x00):** Waits for Link to approach within a specified distance (`$24`). Transitions to `VampireBat_Ascend`.
* **`VampireBat_Ascend` (0x01):** Plays an ascending animation, increases its `SprHeight` to `$50`, and randomly spawns a Fire Keese (`Sprite_SpawnFireKeese`). Transitions to `VampireBat_FlyAround`.
* **`VampireBat_FlyAround` (0x02):** Plays a flying animation, moves towards Link (`Sprite_ProjectSpeedTowardsPlayer`), and randomly selects new directions (`Sprite_SelectNewDirection`). Transitions to `VampireBat_Descend` after a timer.
* **`VampireBat_Descend` (0x03):** Plays a descending animation, decreases its `SprHeight` until it's on the ground, and randomly uses `Sprite_Twinrova_FireAttack`. Transitions back to `VampireBat_Idle` after a timer.
## Initialization (from `Sprite_Keese_Prep` in `keese.asm`):
The Vampire Bat does not have its own `_Prep` routine and relies on the generic `Sprite_Keese_Prep` routine in `keese.asm`. When `SprSubtype = 02`:
* `SprHealth` is set to `$20` (32 decimal).
* `SprDefl` is set to `$80`.
* `SprTimerC` is set to `$30`.
## Drawing (`Sprite_VampireBat_Draw`):
* This routine is called from `Sprite_Keese_Long` in `keese.asm` when `SprSubtype = 02`.
* Uses standard OAM allocation routines.
* Handles animation frames, x/y offsets, character data, properties, and sizes specific to the Vampire Bat's appearance.
## Attack Spawning (`Sprite_SpawnFireKeese`, `Sprite_SpawnIceKeese`):
* **`Sprite_SpawnFireKeese`:** Spawns a Keese sprite (`$11`) with `SprSubtype = $01` (Fire Keese).
* **`Sprite_SpawnIceKeese`:** Spawns a Keese sprite (`$11`) with `SprSubtype = $00` (Ice Keese).
## Interactions:
* **Damage:** Responds to damage from Link, including flashing and bouncing from tile collisions.
* **Attacks:** Can spawn Fire Keese and utilize `Sprite_Twinrova_FireAttack` (a shared attack function).
## Discrepancies/Notes:
* **Shared Sprite ID:** The Vampire Bat efficiently reuses the generic Keese sprite ID (`$11`), with `SprSubtype = 02` serving as the primary differentiator for its unique behavior.
* **Health Management:** Its health is configured within the generic `Sprite_Keese_Prep` routine based on its subtype.
* **Code Reuse:** It reuses `Sprite_Twinrova_FireAttack`, demonstrating efficient code sharing across different boss/mini-boss sprites.
* **Hardcoded Values:** Many numerical values for timers, speeds, and offsets are hardcoded. Replacing these with named constants would improve readability and maintainability.

View File

@@ -0,0 +1,62 @@
# Wolfos Mini-Boss Sprite Analysis
## Overview
The `wolfos` sprite (ID: `Sprite_Wolfos`, which is `$A9`) functions as a mini-boss or special enemy. It engages Link in combat with various movement and attack patterns. A key aspect of this sprite is its integration into a mask quest, where it can be subdued and, under specific conditions, grants Link the Wolf Mask.
## Key Properties:
* **Sprite ID:** `Sprite_Wolfos` (`$A9`)
* **Description:** A mini-boss/special enemy that fights Link and is part of a mask quest.
* **Number of Tiles:** 4
* **Health:** `30` (decimal)
* **Damage:** `00` (Damage dealt to Link is likely handled by its attacks.)
* **Special Properties:**
* `!Boss = 00` (Not marked as a boss, but functions as a mini-boss/special enemy.)
* `!ImperviousArrow = 01` (Impervious to arrows.)
## Custom Variables/Macros:
* `WolfosDialogue = SprMiscD`: Stores a flag to control Wolfos dialogue.
* `Wolfos_AnimateAction = SprMiscE`: Stores the current animation action.
* `AttackForward()`, `AttackBack()`, `WalkRight()`, `WalkLeft()`, `AttackRight()`, `AttackLeft()`, `Subdued()`, `GrantMask()`, `Dismiss()`: Macros for setting `SprAction` and `Wolfos_AnimateAction`, improving code clarity.
* `!NormalSpeed = $08`, `!AttackSpeed = $0F`: Constants for movement speeds.
## Main Logic Flow (`Sprite_Wolfos_Main`):
The Wolfos's behavior is governed by a state machine:
* **`Wolfos_AttackForward` (0x00), `Wolfos_AttackBack` (0x01), `Wolfos_WalkRight` (0x02), `Wolfos_WalkLeft` (0x03), `Wolfos_AttackRight` (0x04), `Wolfos_AttackLeft` (0x05):** These states manage the Wolfos's movement and attacks. They call `Wolfos_Move` and can randomly trigger attack actions with increased speed and temporary imperviousness.
* **`Wolfos_Subdued` (0x06):** In this state, the Wolfos stops moving, displays dialogue (`$23`), and waits for Link to play the Song of Healing (`SongFlag = $01`). If the song is played, it transitions to `Wolfos_GrantMask`.
* **`Wolfos_GrantMask` (0x07):** Displays the Wolfos mask graphic, shows a message (`$10F`), grants Link the `WolfMask` item, and transitions to `Wolfos_Dismiss`.
* **`Wolfos_Dismiss` (0x08):** Stops moving, kills the sprite, and clears Link's `BRANDISH` flag.
## Initialization (`Sprite_Wolfos_Prep`):
* Checks if Link is outdoors (`$1B`). If so, it further checks if the Wolfos has already been defeated (`$7EF303 = $01`). If defeated, the sprite is killed to prevent respawning.
* Sets initial timers (`SprTimerA = $40`, `SprTimerC = $40`).
* Configures deflection properties (`SprDefl = $82`, making it impervious to arrows).
* Sets `SprNbrOAM = $08` and initializes `SprMiscG, X` and `SprMiscE, X` to `0`.
## Defeat Check (`Sprite_Wolfos_CheckIfDefeated`):
* Checks if Link is outdoors. If `SprHealth, X` drops below `$04`, the Wolfos is considered "defeated."
* Upon defeat, it sets `SprAction = $06` (Wolfos_Subdued), `SprState = $09` (normal state, avoiding a full death animation), refills its health to `$40`, and clears `WolfosDialogue`. This indicates pacification rather than outright killing.
## Movement (`Wolfos_Move`, `Wolfos_DecideAction`, `Wolfos_MoveAction_Basic`, `Wolfos_MoveAction_CirclePlayer`, `Wolfos_MoveAction_Dodge`):
* **`Wolfos_Move`:** Handles damage flash, checks damage from player, prevents player from passing through, bounces from tile collision, checks for recoiling, moves the sprite, and calls `Wolfos_DecideAction`.
* **`Wolfos_DecideAction`:** Determines the Wolfos's next movement action based on timers and random chance. It uses a jump table to select between `Wolfos_MoveAction_Basic`, `Wolfos_MoveAction_CirclePlayer`, and `Wolfos_MoveAction_Dodge`.
* **`Wolfos_MoveAction_Basic`:** Basic movement towards or away from Link based on distance.
* **`Wolfos_MoveAction_CirclePlayer`:** Attempts to circle the player.
* **`Wolfos_MoveAction_Dodge`:** Dodges by applying speed towards the player.
## Animation (`Sprite_Wolfos_Animate`):
* This routine is called from `Sprite_Wolfos_Main`.
* It uses `Wolfos_AnimateAction` (stored in `SprMiscE, X`) to determine which animation to play.
* It has separate animation routines for `AttackForward`, `AttackBack`, `WalkRight`, `WalkLeft`, `AttackRight`, `AttackLeft`, and `Subdued`.
* It also spawns sparkle garnishes (`JSL Sprite_SpawnSparkleGarnish`).
## Drawing (`Sprite_Wolfos_Draw`):
* Uses standard OAM allocation routines.
* Handles animation frames, x/y offsets, character data, properties, and sizes for drawing the Wolfos.
* Includes a special frame for the Wolf Mask (`$CC`) when granting the item.
## Discrepancies/Notes:
* **Mask Quest Integration:** The Wolfos is directly integrated into a mask quest, where playing the Song of Healing subdues it and leads to receiving the Wolf Mask.
* **Health Refill on Defeat:** When defeated, its health is refilled to `$40`, and its state is set to `Wolfos_Subdued`, indicating it's not truly killed but rather pacified.
* **Hardcoded Values:** Many numerical values for timers, speeds, and offsets are hardcoded. Replacing these with named constants would improve readability and maintainability.
* **`JSL Link_ReceiveItem`:** This is a standard function for giving items to Link.
* **`JSL Sprite_SpawnSparkleGarnish`:** This is a generic garnish spawning function.

90
Docs/Sprites/NPCs.md Normal file
View File

@@ -0,0 +1,90 @@
# NPCs Analysis
This document provides an analysis of the Non-Player Character (NPC) sprites found in the `Sprites/NPCs/` directory.
## File Overview
| Filename | Sprite ID(s) | Description |
|---|---|---|
| `bean_vendor.asm` | `Sprite_BeanVendor` | Handles the logic for the bean vendor who sells Magic Beans to the player. |
| `bottle_vendor.asm` | (Vanilla Hook) | Modifies the vanilla bottle vendor to handle selling milk. |
| `bug_net_kid.asm` | (Vanilla Hook) | Modifies the Sick Kid to grant the Pegasus Boots after playing the Song of Healing. |
| `deku_scrub.asm` | `Sprite_DekuScrubNPCs` | Manages various Deku Scrub NPCs, including one who gives the Deku Mask. |
| `eon_owl.asm` | `Sprite_EonOwl` | The owl that guides Link. Includes logic for both the Eon Owl and Kaepora Gaebora. |
| `eon_zora.asm` | (Part of `zora.asm`) | A friendly Zora NPC found in the Eon Abyss. |
| `eon_zora_elder.asm`| (Part of `zora.asm`) | The elder Zora in the Eon Abyss. |
| `farore.asm` | `Sprite_Farore` | The Oracle Farore, who appears in cutscenes and guides the player. |
| `followers.asm` | (Vanilla Hooks) | Contains logic for various follower characters like the Zora Baby and the Old Man. |
| `fortune_teller.asm`| (Vanilla Hook) | Modifies the fortune teller's dialogue to provide hints relevant to the hack's progression. |
| `goron.asm` | `Sprite_Goron` | Handles both the Kalyxo Goron who opens the mines and the Eon Gorons. |
| `hyrule_dream.asm` | (Part of `farore.asm`) | Logic for NPCs appearing in Link's dream sequences (Zelda, King, Soldier). |
| `impa.asm` | (Vanilla Hook) | Modifies Impa's behavior, particularly in setting spawn points. |
| `korok.asm` | `Sprite_Korok` | A friendly Korok NPC. |
| `maku_tree.asm` | `Sprite_MakuTree` | The Maku Tree, a key story NPC who provides a Heart Container. |
| `maple.asm` | (Part of `mermaid.asm`)| Maple the witch, who can send Link to dream worlds. |
| `mask_salesman.asm` | `Sprite_MaskSalesman` | The Happy Mask Salesman, who sells the Bunny Hood and Stone Mask. |
| `mermaid.asm` | `Sprite_Mermaid` | A friendly mermaid NPC. Also contains logic for Maple and the Librarian. |
| `piratian.asm` | `$0E` | A friendly pirate-like NPC that becomes aggressive if attacked. |
| `ranch_girl.asm` | (Vanilla Hook) | Modifies the chicken lady at the ranch to give the Ocarina. |
| `tingle.asm` | `$22` | Tingle, who sells dungeon maps to the player. |
| `vasu.asm` | `Sprite_Vasu` | Vasu, the jeweler who appraises magic rings. Also includes logic for Error. |
| `village_dog.asm` | `Sprite_VillageDog` | A friendly dog that interacts with the player. Includes logic for the Eon Dog. |
| `village_elder.asm` | (Part of `bean_vendor.asm`)| The village elder NPC. |
| `zora_princess.asm` | `Sprite_ZoraPrincess` | The Zora Princess, who grants the Zora Mask. |
| `zora.asm` | `Sprite_Zora` | A friendly Zora NPC. Also contains logic for the Zora Princess and Eon Zoras. |
## Detailed NPC Analysis
### `bean_vendor.asm` / `village_elder.asm`
- **Sprite ID:** `Sprite_BeanVendor`
- **Summary:** This file contains the logic for two NPCs. The primary is the Bean Vendor, who sells Magic Beans for 100 rupees. The second is the Village Elder.
- **Key Logic:**
- **BeanVendor:** Initiates a dialogue on contact. If the player agrees to buy, it checks for sufficient rupees, deducts the cost, and spawns a collectible Magic Bean sprite.
- **VillageElder:** Engages in dialogue and sets a progress flag (`OOSPROG`) after the first interaction.
### `bug_net_kid.asm`
- **Sprite ID:** (Hooks `SpritePrep_SickKid`)
- **Summary:** This modifies the vanilla "Sick Kid" NPC. Instead of giving the Bug Net, he gives the player the Pegasus Boots.
- **Key Logic:** The `SickKid_CheckForSongOfHealing` routine checks if the `SongFlag` is set. If it is, the `BugNetKid_GrantBugNet` routine is called, which uses `Link_ReceiveItem` to give the boots (`ITEMGET` ID `$4B`).
### `deku_scrub.asm`
- **Sprite ID:** `Sprite_DekuScrubNPCs`
- **Summary:** Manages several Deku Scrub NPCs, including the Deku Butler and Deku Princess. A key interaction involves a withered Deku Scrub who, after being healed with the Song of Healing, gives the player the Deku Mask.
- **Key Logic:**
- The main state machine checks for the `SongFlag`.
- If the song is played, it transitions through a dialogue sequence (`QuiereCuracion`, `DarMascara`).
- Finally, in the `Regalo` state, it calls `Link_ReceiveItem` with item ID `$11` (Deku Mask) and sets a progress flag (`$7EF301`).
### `eon_owl.asm`
- **Sprite ID:** `Sprite_EonOwl`
- **Summary:** This is the guide owl, appearing in both the overworld (as Eon Owl) and the Hall of Secrets (as Kaepora Gaebora).
- **Key Logic:**
- **Eon Owl:** In the overworld, it triggers introductory dialogue when the player gets close and then flies away.
- **Kaepora Gaebora:** In the Hall of Secrets, it appears only after all 7 crystals are collected and before the player has the Song of Soaring. It offers to teach the player the song.
### `farore.asm` / `hyrule_dream.asm`
- **Sprite ID:** `Sprite_Farore`
- **Summary:** Handles the Oracle Farore and NPCs that appear in dream sequences.
- **Key Logic:**
- **Farore:** Manages the introductory cutscene where she follows Link, sets the main story state (`$B6`), and changes the game state to post-pendants (`$7EF3C5 = 2`).
- **Dream NPCs:** Contains simple display logic for Zelda, the King, and a soldier during the `MakuTree_HasMetLink` dream sequence.
### `followers.asm`
- **Sprite ID:** (Hooks vanilla follower system)
- **Summary:** Contains significant custom logic for follower characters, most notably the Zora Baby (Locksmith) and the Old Man.
- **Key Logic:**
- **Zora Baby:**
- Replaces the Locksmith sprite (`$39`).
- Can be picked up and carried by Link.
- When placed on a water gate switch, it triggers the switch.
- Transitions from a follower to a standard sprite when on a star tile in a dungeon.
- **Old Man:** Logic is modified to grant the Goldstar (Hookshot Lv2 upgrade) instead of the Magic Mirror.
### `vasu.asm`
- **Sprite ID:** `Sprite_Vasu`
- **Summary:** This is the ring jeweler, Vasu. He can appraise rings the player has found. The file also contains logic for the "I am Error" NPC.
- **Key Logic:**
- Vasu's main loop presents a choice: "Appraise" or "Explain".
- If "Appraise" is chosen, it checks if the player has any unappraised rings (`FOUNDRINGS`).
- It charges 20 rupees (the first one is free) and transfers the bits from `FOUNDRINGS` to `MAGICRINGS`, making them usable.
- The Error NPC appears as a subtype and gives the player a random ring when spoken to.

View File

@@ -0,0 +1,225 @@
# Bean Vendor
## Overview
The Bean Vendor is an NPC (Non-Player Character) sprite designed for player interaction, primarily through dialogue. It features a simple state machine to manage its idle and talking behaviors.
## Sprite Properties
* **`!SPRID`**: `$00` (Vanilla sprite ID, likely overridden)
* **`!NbrTiles`**: `08`
* **`!Harmless`**: `01` (Indicates the sprite is harmless to Link)
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Indicates the sprite is impervious to all attacks)
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `01`
* **`!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_BeanVendor_Long`)
This routine is the main entry point for the Bean Vendor, executed every frame. It handles drawing, shadow rendering, and dispatches to the main logic if the sprite is active.
```asm
Sprite_BeanVendor_Long:
{
PHB : PHK : PLB
JSR Sprite_BeanVendor_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_BeanVendor_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_BeanVendor_Prep`)
This routine runs once when the Bean Vendor is spawned. It initializes `SprDefl, X`, `SprTimerC, X`, `SprNbrOAM, X`, and `SprPrize, X`.
```asm
Sprite_BeanVendor_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.b #$30 : STA.w SprTimerC, X
LDA.b #$03 : STA.w SprNbrOAM, X
LDA.b #$03 : STA.w SprPrize, X
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_BeanVendor_Main`)
The Bean Vendor's core behavior is managed by a state machine with `BeanVendor_Idle` and `BeanVendor_Talk` states.
* **`BeanVendor_Idle`**: The vendor plays an idle animation. When Link is nearby (`GetDistance8bit_Long`), it transitions to the `BeanVendor_Talk` state.
* **`BeanVendor_Talk`**: The vendor plays a talking animation and displays a message using `JSL Interface_PrepAndDisplayMessage`. Once the message is dismissed, it transitions back to the `BeanVendor_Idle` state.
```asm
Sprite_BeanVendor_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw BeanVendor_Idle
dw BeanVendor_Talk
BeanVendor_Idle:
{
%PlayAnimation(0,1,15)
JSL GetDistance8bit_Long : CMP.b #$20 : BCS +
INC.w SprAction, X
+
RTS
}
BeanVendor_Talk:
{
%PlayAnimation(2,3,8)
JSL Interface_PrepAndDisplayMessage : BCC +
STZ.w SprAction, X
+
RTS
}
}
```
## Drawing (`Sprite_BeanVendor_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_BeanVendor_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, X : TAY ;Animation Frame
LDA .start_index, Y : STA $06
LDA.w SprFlash, X : STA $08
LDA.w SprMiscB, X : STA $09
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
; If SprMiscA != 0, then use 4th sheet
LDA.b $09 : BEQ +
LDA .chr_2, X : STA ($90), Y
JMP ++
+
LDA .chr, X : STA ($90), Y
++
INY
LDA .properties, X : ORA $08 : 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, $01, $03, $04, $06, $08
.nbr_of_tiles
db 0, 1, 0, 1, 1, 0
.x_offsets
dw 0
dw -4, 4
dw 0
dw -4, 4
dw -4, 4
dw 0
.y_offsets
dw 0
dw 0, 0
dw 0
dw 0, 0
dw 0, 0
dw 0
.chr
db $80
db $A2, $A2
db $82
db $84, $84
db $A4, $A4
db $A0
.chr_2
db $C0
db $E2, $E2
db $C2
db $C4, $C4
db $E4, $E4
db $E0
.properties
db $35
db $35, $75
db $35
db $35, $75
db $35, $75
db $35
.sizes
db $02
db $02, $02
db $02
db $02, $02
db $02, $02
db $02
}
```
## Design Patterns
* **NPC Interaction**: The sprite is designed to engage with the player through dialogue, triggered by proximity.
* **State Machine**: Employs a simple state machine to manage its `Idle` and `Talk` behaviors, ensuring appropriate animations and actions based on player interaction.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,225 @@
# Bottle Vendor
## Overview
The Bottle Vendor is an NPC (Non-Player Character) sprite designed for player interaction, primarily through dialogue. It features a simple state machine to manage its idle and talking behaviors, very similar in structure to the Bean Vendor.
## Sprite Properties
* **`!SPRID`**: `$00` (Vanilla sprite ID, likely overridden)
* **`!NbrTiles`**: `08`
* **`!Harmless`**: `01` (Indicates the sprite is harmless to Link)
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Indicates the sprite is impervious to all attacks)
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `01`
* **`!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_BottleVendor_Long`)
This routine is the main entry point for the Bottle Vendor, executed every frame. It handles drawing, shadow rendering, and dispatches to the main logic if the sprite is active.
```asm
Sprite_BottleVendor_Long:
{
PHB : PHK : PLB
JSR Sprite_BottleVendor_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_BottleVendor_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_BottleVendor_Prep`)
This routine runs once when the Bottle Vendor is spawned. It initializes `SprDefl, X`, `SprTimerC, X`, `SprNbrOAM, X`, and `SprPrize, X`.
```asm
Sprite_BottleVendor_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.b #$30 : STA.w SprTimerC, X
LDA.b #$03 : STA.w SprNbrOAM, X
LDA.b #$03 : STA.w SprPrize, X
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_BottleVendor_Main`)
The Bottle Vendor's core behavior is managed by a state machine with `BottleVendor_Idle` and `BottleVendor_Talk` states.
* **`BottleVendor_Idle`**: The vendor plays an idle animation. When Link is nearby (`GetDistance8bit_Long`), it transitions to the `BottleVendor_Talk` state.
* **`BottleVendor_Talk`**: The vendor plays a talking animation and displays a message using `JSL Interface_PrepAndDisplayMessage`. Once the message is dismissed, it transitions back to the `BottleVendor_Idle` state.
```asm
Sprite_BottleVendor_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw BottleVendor_Idle
dw BottleVendor_Talk
BottleVendor_Idle:
{
%PlayAnimation(0,1,15)
JSL GetDistance8bit_Long : CMP.b #$20 : BCS +
INC.w SprAction, X
+
RTS
}
BottleVendor_Talk:
{
%PlayAnimation(2,3,8)
JSL Interface_PrepAndDisplayMessage : BCC +
STZ.w SprAction, X
+
RTS
}
}
```
## Drawing (`Sprite_BottleVendor_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_BottleVendor_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, X : TAY ;Animation Frame
LDA .start_index, Y : STA $06
LDA.w SprFlash, X : STA $08
LDA.w SprMiscB, X : STA $09
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
; If SprMiscA != 0, then use 4th sheet
LDA.b $09 : BEQ +
LDA .chr_2, X : STA ($90), Y
JMP ++
+
LDA .chr, X : STA ($90), Y
++
INY
LDA .properties, X : ORA $08 : 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, $01, $03, $04, $06, $08
.nbr_of_tiles
db 0, 1, 0, 1, 1, 0
.x_offsets
dw 0
dw -4, 4
dw 0
dw -4, 4
dw -4, 4
dw 0
.y_offsets
dw 0
dw 0, 0
dw 0
dw 0, 0
dw 0, 0
dw 0
.chr
db $80
db $A2, $A2
db $82
db $84, $84
db $A4, $A4
db $A0
.chr_2
db $C0
db $E2, $E2
db $C2
db $C4, $C4
db $E4, $E4
db $E0
.properties
db $35
db $35, $75
db $35
db $35, $75
db $35, $75
db $35
.sizes
db $02
db $02, $02
db $02
db $02, $02
db $02, $02
db $02
}
```
## Design Patterns
* **NPC Interaction**: The sprite is designed to engage with the player through dialogue, triggered by proximity.
* **State Machine**: Employs a simple state machine to manage its `Idle` and `Talk` behaviors, ensuring appropriate animations and actions based on player interaction.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,86 @@
# Bug Net Kid (Sick Kid)
## Overview
The Bug Net Kid, also referred to as the Sick Kid, is an NPC sprite that plays a role in a quest involving the "Song of Healing" and the acquisition of the Boots (referred to as Bug Net in the comments). This sprite is implemented by overriding vanilla game code to introduce custom interactions and progression.
## Vanilla Overrides
This sprite extensively uses `pushpc`/`pullpc` blocks and `org` directives to inject custom logic into existing vanilla routines. This approach allows for modifying the behavior of a vanilla NPC without creating a new sprite ID.
* **`org $068D7F`**: Overrides the vanilla `SpritePrep_SickKid` routine.
* **`org $06B962`**: Overrides a routine related to the kid's resting state (`BugNetKid_Resting`).
* **`org $06B9C6`**: Overrides a routine responsible for granting the item (`BugNetKid_GrantBugNet`).
## `SickKid_CheckForSongOfHealing`
This routine is a core component of the Bug Net Kid's logic. It checks if the "Song of Healing" has been played by examining a `SongFlag` (likely a WRAM address like `$7E001F`). If the song has been played, it updates internal sprite state variables (`$0D80, X`, `$02E4`) and clears the `SongFlag`.
```asm
SickKid_CheckForSongOfHealing:
{
LDA.b SongFlag : CMP.b #$01 : BNE .no_song
INC $0D80, X
INC $02E4
STZ.b SongFlag
.no_song
RTL
}
```
## `SpritePrep_SickKid` (Initialization)
This routine is executed when the Sick Kid sprite is initialized. It checks an SRAM flag (`$7EF355`) to determine if Link has already obtained the Boots. If so, it sets `$0D80, X` to `$03`. It also increments `SprBulletproof, X`, making the kid invulnerable to attacks.
```asm
SpritePrep_SickKid:
{
LDA.l $7EF355 : BEQ .no_boots
LDA.b #$03 : STA $0D80, X
.no_boots
INC.w SprBulletproof, X
RTS
}
```
## `BugNetKid_Resting` (Main Logic)
This routine controls the kid's behavior when not actively granting an item. It checks for player preoccupation and damage, and crucially, calls `SickKid_CheckForSongOfHealing`. If Link has not yet received the Boots, it displays a solicited message to the player.
```asm
BugNetKid_Resting:
{
JSL Sprite_CheckIfPlayerPreoccupied : BCS .dont_awaken
JSR Sprite_CheckDamageToPlayer_same_layer : BCC .dont_awaken
JSL SickKid_CheckForSongOfHealing
LDA.l $7EF355
CMP.b #$01 : BCC .no_boots
.dont_awaken
RTS
.no_boots
LDA.b #$04
LDY.b #$01
JSL Sprite_ShowSolicitedMessageIfPlayerFacing
RTS
}
```
## `BugNetKid_GrantBugNet` (Item Granting)
This routine is responsible for giving Link the Boots. It sets the item ID (`LDY.b #$4B`), clears a flag (`$02E9`), calls `JSL Link_ReceiveItem` to add the item to Link's inventory, and updates internal sprite state variables (`$0D80, X`, `$02E4`).
```asm
BugNetKid_GrantBugNet:
{
; Give Link the Boots
LDY.b #$4B
STZ $02E9
PHX
JSL Link_ReceiveItem
PLX
INC $0D80, X
STZ $02E4
RTS
}
```
## Design Patterns
* **Vanilla Override**: This sprite is a prime example of overriding vanilla game code to introduce new NPC interactions and quest elements without creating entirely new sprite definitions.
* **Quest/Item Gating**: The sprite's behavior and the ability to receive the Boots are directly tied to specific game progression flags, such as the `SongFlag` and the SRAM flag for the Boots (`$7EF355`).
* **NPC Interaction**: The sprite interacts with the player by displaying messages and granting a key item, driving forward a specific questline.
* **Global Flags and SRAM Usage**: Utilizes global WRAM flags (`$02E4`, `SongFlag`) and SRAM (`$7EF355`) to maintain and track the state of the quest across game sessions.

View File

@@ -0,0 +1,307 @@
# Deku Scrub
## Overview
The Deku Scrub sprite is a highly versatile NPC implementation capable of representing multiple distinct characters, including a Withered Deku Scrub, Deku Butler, and Deku Princess. Its behavior is intricately tied to game progression, player actions, and specific in-game locations.
## Sprite Properties
* **`!SPRID`**: `Sprite_DekuScrubNPCs` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `06`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `03`
* **`!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_DekuScrub_Long`)
This routine is the main entry point for the Deku Scrub, executed every frame. It handles drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_DekuScrub_Long:
{
PHB : PHK : PLB
JSR Sprite_DekuScrub_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_DekuScrub_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_DekuScrub_Prep`)
This routine runs once when the Deku Scrub is spawned. It sets `SprDefl, X` and then determines the initial `SprAction, X` based on the current `AreaIndex`, `SprSubtype, X`, and whether the Deku Mask has been obtained (`$7EF301`). It also checks if Tail Palace is cleared (`Crystals`) to potentially set `SprState, X` to `0`.
```asm
Sprite_DekuScrub_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
; Peacetime Deku Scrub NPCs
LDA.b AreaIndex : CMP.b #$2E : BNE .check_next
; Deku Butler
LDA.b #$07 : STA.w SprAction, X
JMP +
.check_next
CMP.b #$2F : BNE .continue
LDA.b #$08 : STA.w SprAction, X
JMP +
.continue
LDA.w SprSubtype, X : CMP.b #$01 : BEQ .DekuButler
CMP.b #$02 : BEQ .DekuPrincess
LDA.l $7EF301 : BEQ +
LDA.b #$04 : STA.w SprAction, X
JMP +
.DekuButler
LDA.b #$05 : STA.w SprAction, X
JMP ++
.DekuPrincess
LDA.b #$06 : STA.w SprAction, X
++
; Check if tail palace is cleared
LDA.l Crystals : AND #$10 : BEQ +
STZ.w SprState, X
+
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_DekuScrub_Main`)
The Deku Scrub's core behavior is managed by a complex state machine with several states, many of which are named in Spanish:
* **`EstadoInactivo` (Inactive State)**: The scrub plays an idle animation, prevents player passage, and transitions to `QuiereCuracion` upon player interaction.
* **`QuiereCuracion` (Wants Healing)**: Plays an animation and checks if the "Song of Healing" (`SongFlag`) has been played. If so, it clears the flag, sets a timer, and transitions to `DarMascara`.
* **`DarMascara` (Give Mask)**: Plays an animation, displays a message after a timer, and then transitions to `Regalo`.
* **`Regalo` (Gift)**: After a timer, grants Link the Deku Mask (`$11`) and updates the Deku Mask flag (`$7EF301`), then transitions to `Withered`.
* **`Withered`**: Plays a withered animation.
* **`DekuButler`**: Plays a specific animation, prevents player passage, and displays a message.
* **`DekuPrincess`**: Plays a specific animation, prevents player passage, and displays a message.
* **`DekuButler_Peacetime`**: Plays a specific animation, prevents player passage, and displays a message. If the message is dismissed, it sets `MapIcon` to `$02`.
* **`DekuPrinces_Peacetime`**: Plays a specific animation, prevents player passage, and displays a message. If the message is dismissed, it sets `MapIcon` to `$02`.
```asm
Sprite_DekuScrub_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw EstadoInactivo
dw QuiereCuracion
dw DarMascara
dw Regalo
dw Withered
dw DekuButler
dw DekuPrincess
dw DekuButler_Peacetime
dw DekuPrinces_Peacetime
EstadoInactivo:
{
%PlayAnimation(0, 1, 16)
JSL Sprite_PlayerCantPassThrough
%ShowSolicitedMessage($140) : BCC .no_hablaba
%GotoAction(1)
.no_hablaba
RTS
}
QuiereCuracion:
{
%PlayAnimation(0, 1, 16)
LDA.b SongFlag : CMP.b #$01 : BNE .ninguna_cancion
STZ.b SongFlag
LDA.b #$C0 : STA.w SprTimerD, X
%GotoAction(2)
.ninguna_cancion
RTS
}
DarMascara:
{
%PlayAnimation(0, 1, 16)
LDA.w SprTimerD, X : BNE +
%ShowUnconditionalMessage($141)
LDA.b #$C0 : STA.w SprTimerD, X
%GotoAction(3)
+
RTS
}
Regalo:
{
LDA.w SprTimerD, X : BNE +
LDY #$11 : STZ $02E9 ; Give the Deku Mask
JSL Link_ReceiveItem
LDA.b #$01 : STA.l $7EF301
%GotoAction(4)
+
RTS
}
Withered:
{
%PlayAnimation(2, 2, 10)
RTS
}
DekuButler:
{
%PlayAnimation(3, 3, 10)
JSL Sprite_PlayerCantPassThrough
%ShowSolicitedMessage($080)
RTS
}
DekuPrincess:
{
%PlayAnimation(4, 4, 10)
JSL Sprite_PlayerCantPassThrough
%ShowSolicitedMessage($0C3)
RTS
}
DekuButler_Peacetime:
{
%StartOnFrame(3)
%PlayAnimation(3, 3, 10)
JSL Sprite_PlayerCantPassThrough
%ShowSolicitedMessage($1B9) : BCC +
LDA.b #$02 : STA.l MapIcon
+
RTS
}
DekuPrinces_Peacetime:
{
%StartOnFrame(4)
%PlayAnimation(4, 4, 10)
JSL Sprite_PlayerCantPassThrough
%ShowSolicitedMessage($1BA) : BCC +
LDA.b #$02 : STA.l MapIcon
+
RTS
}
}
```
## Drawing (`Sprite_DekuScrub_Draw`)
The drawing routine handles OAM allocation and animation. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_DekuScrub_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, $08, $0C, $10
.nbr_of_tiles
db 3, 3, 3, 3, 3
.x_offsets
dw 4, 4, -4, -4
dw 4, -4, -4, 4
dw -8, -8, 8, 8
dw -4, 4, -4, 4
dw -4, -4, 4, 4
.y_offsets
dw 4, -4, -4, 4
dw 4, 4, -4, -4
dw 4, -12, -12, 4
dw -12, -12, 4, 4
dw 4, -12, 4, -12
.chr
db $2E, $0E, $0E, $2E
db $2C, $2C, $0C, $0C
db $20, $00, $02, $22
db $04, $05, $24, $25
db $27, $07, $27, $07
.properties
db $3B, $7B, $3B, $7B
db $3B, $7B, $3B, $7B
db $3B, $3B, $3B, $3B
db $3B, $3B, $3B, $3B
db $3B, $3B, $7B, $7B
.sizes
db $02, $02, $02, $02
db $02, $02, $02, $02
db $02, $02, $02, $02
db $02, $02, $02, $02
db $02, $02, $02, $02
}
```
## Design Patterns
* **Multi-Character NPC**: A single sprite definition is used to represent multiple distinct NPC characters (Withered Deku Scrub, Deku Butler, Deku Princess), with their specific roles determined by `SprSubtype` and `AreaIndex`.
* **Quest Progression Integration**: The sprite's behavior is deeply integrated with various quest elements, checking for specific items (Deku Mask), songs (Song of Healing), and cleared dungeons (Tail Palace) to determine its current state and interactions.
* **Conditional Behavior**: Extensive use of conditional logic based on `AreaIndex`, `SprSubtype`, and global game state flags allows for dynamic changes in the NPC's role, dialogue, and actions.
* **NPC Interaction**: Provides rich interaction with the player through dialogue (`%ShowSolicitedMessage`, `%ShowUnconditionalMessage`) and the granting of key items (`Link_ReceiveItem`).
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make the NPC a solid object that Link cannot walk through.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, essential for accurate sprite rendering.

204
Docs/Sprites/NPCs/EonOwl.md Normal file
View File

@@ -0,0 +1,204 @@
# Eon Owl / Kaepora Gaebora
## Overview
This sprite is a sophisticated NPC implementation that serves as both the "Eon Owl" and "Kaepora Gaebora" (a character from The Legend of Zelda: Ocarina of Time). Its appearance, behavior, and interactions are highly conditional, depending on the player's location and various game progression flags.
## Sprite Properties
* **`!SPRID`**: `Sprite_EonOwl` (Custom symbol, likely a remapped vanilla ID)
* **`!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`**: `01` (Deflects all projectiles)
* **`!ImperviousArrow`**: `01` (Impervious to arrows)
* **`!ImpervSwordHammer`**: `01` (Impervious to sword and hammer attacks)
* **`!Boss`**: `00`
## Main Structure (`Sprite_EonOwl_Long`)
This routine serves as a dispatcher for the Eon Owl and Kaepora Gaebora, and includes logic for conditional despawning based on game state.
* **Kaepora Gaebora Logic**: If the `AreaIndex` is `$0E` (Hall of Secrets map) and certain conditions regarding collected crystals (`$7EF37A`) and the player's possession of the "Song of Soaring" (`$7EF34C`) are met, the sprite is identified as Kaepora Gaebora (`SprSubtype, X` set to `01`) and `Sprite_KaeporaGaebora_Draw` is called.
* **Eon Owl Logic**: Otherwise, `Sprite_EonOwl_Draw` is called.
* **Despawning**: If conditions for either character are not met, the sprite despawns (`STZ.w SprState, X`).
```asm
Sprite_EonOwl_Long:
{
PHB : PHK : PLB
; If it is not the Hall of Secrets map
LDA.b $8A : CMP.b #$0E : BNE .NotGaebora
; If the map doesn't have the 6 crystals
LDA.l $7EF37A : CMP.b #$77 : BNE .Despawn
; If the player has the Song of Soaring, despawn
LDA.l $7EF34C : CMP.b #$03 : BCS .Despawn
LDA.b #$01 : STA.w SprSubtype, X
JSR Sprite_KaeporaGaebora_Draw
JMP .HandleSprite
.NotGaebora
JSR Sprite_EonOwl_Draw
.HandleSprite
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_EonOwl_Main
.SpriteIsNotActive
PLB
RTL
.Despawn
STZ.w SprState, X
PLB
RTL
}
```
## Initialization (`Sprite_EonOwl_Prep`)
This routine initializes the sprite upon spawning, including setting its hitbox and handling conditional despawning for the intro sequence.
* **Hitbox**: `SprHitbox, X` is set to `0`.
* **Kaepora Gaebora Initialization**: If `AreaIndex` is `$0E`, `SprTimerA, X` is set to `$20` and `SprAction, X` to `$03`.
* **Intro Despawn**: If `AreaIndex` is `$50` (Intro Map) and Link already has the Sword, the sprite despawns.
```asm
Sprite_EonOwl_Prep:
{
PHB : PHK : PLB
STZ.w SprHitbox, X
LDA.b $8A : CMP.b #$0E : BNE .NotGaebora
LDA.b #$20 : STA.w SprTimerA, X
LDA.b #$03 : STA.w SprAction, X
.NotGaebora
LDA.w AreaIndex : CMP.b #$50 : BNE .not_intro
; If Map 0x50, don't spawn after getting sword
LDA.l Sword : CMP.b #$01 : BCC .continue
STZ.w SprState, X
.continue
.not_intro
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_EonOwl_Main`)
This routine manages the various states and behaviors of both the Eon Owl and Kaepora Gaebora.
* **`EonOwl_Idle`**: The Eon Owl plays an idle animation and transitions to `EonOwl_IntroDialogue` when Link is nearby.
* **`EonOwl_IntroDialogue`**: Displays an introductory message and then transitions to `EonOwl_FlyingAway`.
* **`EonOwl_FlyingAway`**: The Eon Owl plays a flying animation, moves upwards, and despawns after a timer.
* **`KaeporaGaebora`**: Kaepora Gaebora plays an idle animation and, if Link is at a certain distance and a timer allows, displays a message and transitions to `KaeporaGaebora_Respond`.
* **`KaeporaGaebora_Respond`**: Processes the player's dialogue choice. If the player declines, it transitions back to `KaeporaGaebora`. If the player accepts, it transitions to `KaeporaGaebora_FlyAway` and grants the "Song of Soaring" (`$7EF34C`).
* **`KaeporaGaebora_FlyAway`**: Kaepora Gaebora flies upwards and despawns after a timer.
```asm
Sprite_EonOwl_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw EonOwl_Idle
dw EonOwl_IntroDialogue
dw EonOwl_FlyingAway
dw KaeporaGaebora
dw KaeporaGaebora_Respond
dw KaeporaGaebora_FlyAway
EonOwl_Idle:
{
%PlayAnimation(0,1,16)
JSL GetDistance8bit_Long : CMP #$28 : BCS .not_too_close
%GotoAction(1)
.not_too_close
RTS
}
EonOwl_IntroDialogue:
{
%PlayAnimation(0,1,16)
%ShowUnconditionalMessage($00E6)
LDA.b #$C0 : STA.w SprTimerA, X
%GotoAction(2)
RTS
}
EonOwl_FlyingAway:
{
%PlayAnimation(2,3,10)
LDA.b #$F8 : STA.w SprYSpeed, X
JSL Sprite_Move
LDA.w SprTimerA, X : CMP.b #$80 : BNE +
LDA.b #$40 : STA.w SprXSpeed, X
+
LDA.w SprTimerA, X : BNE .not_done
STZ.w SprState, X
.not_done
RTS
}
; 0x03 - Kaepora Gaebora
KaeporaGaebora:
{
%PlayAnimation(0,0,1)
JSL GetDistance8bit_Long : CMP.b #$50 : BCC .not_ready
LDA.w SprTimerA, X : BNE .not_ready
%ShowUnconditionalMessage($146)
%GotoAction(4)
.not_ready
RTS
}
KaeporaGaebora_Respond:
{
LDA $1CE8 : BNE .player_said_no
%GotoAction(3)
RTS
.player_said_no
%GotoAction(5)
LDA.b #$60 : STA.w SprTimerA, X
LDA.b #$03 : STA.l $7EF34C
RTS
}
FlyAwaySpeed = 10
KaeporaGaebora_FlyAway:
{
LDA.b #-FlyAwaySpeed : STA.w SprYSpeed, X
JSL Sprite_Move
LDA.w SprTimerA, X : BNE .not_ready
STZ.w SprState, X
.not_ready
RTS
}
}
```
## Drawing (`Sprite_EonOwl_Draw` and `Sprite_KaeporaGaebora_Draw`)
Both drawing routines handle OAM allocation and animation, using `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each has its own specific OAM data for rendering the respective character.
## Design Patterns
* **Multi-Character NPC**: A single sprite definition dynamically represents two distinct NPCs (Eon Owl and Kaepora Gaebora) based on `AreaIndex` and game state, showcasing efficient sprite reuse.
* **Conditional Spawning/Despawning**: The sprite's visibility and existence are tightly controlled by game progression, including collected items (crystals, sword) and player inventory (Song of Soaring), making it appear only when relevant to the narrative.
* **Quest Progression Integration**: The sprite's dialogue and actions are directly linked to specific quest milestones, guiding the player through the game's story.
* **NPC Interaction with Dialogue Choices**: Kaepora Gaebora presents the player with dialogue options, and the player's choice influences game outcomes, such as receiving the "Song of Soaring."
* **Flying Behavior**: Implements realistic flying animations and movement, including flying away sequences with controlled speed and timers.
* **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 and positioning.

View File

@@ -0,0 +1,214 @@
# Eon Zora
## Overview
The Eon Zora is an NPC (Non-Player Character) sprite found in the Eon Abyss. Its behavior is characterized by random movement and context-sensitive dialogue that changes based on Link's current location within the game world.
## Sprite Properties
Explicit sprite properties (`!SPRID`, `!NbrTiles`, etc.) are not defined within this file. It is assumed that these properties are either inherited from a vanilla sprite ID or defined in a separate configuration file, as this file focuses on the sprite's behavior and drawing.
## Main Logic (`Sprite_EonZora_Main`)
This routine is the main entry point for the Eon Zora, executed every frame. It orchestrates the Zora's dialogue, movement, and animation.
* **Dialogue**: Calls `EonZora_HandleDialogue` to manage interactions with the player.
* **Movement**: Calls `EonZora_Walk` for random movement, followed by `JSL Sprite_Move` and `JSL Sprite_BounceFromTileCollision` for physical movement and collision handling.
* **Directional Animations**: Uses a jump table to play specific animations based on the Zora's current direction (Forward, Left, Right, Back).
```asm
Sprite_EonZora_Main:
{
JSR EonZora_HandleDialogue
JSR EonZora_Walk
JSL Sprite_Move
JSL Sprite_BounceFromTileCollision
LDA.w SprAction, X
JSL JumpTableLocal
dw EonZora_Forward
dw EonZora_Left
dw EonZora_Right
dw EonZora_Back
EonZora_Forward:
%PlayAnimation(0,1,10)
RTS
EonZora_Left:
%PlayAnimation(2,3,10)
RTS
EonZora_Right:
%PlayAnimation(4,5,10)
RTS
EonZora_Back:
%PlayAnimation(6,7,10)
RTS
}
```
## Movement Routine (`EonZora_Walk`)
This routine controls the Eon Zora's random walking behavior. It uses a timer (`SprTimerA, X`) to periodically select a new random direction and update the sprite's `SprXSpeed, X` and `SprYSpeed, X`.
```asm
EonZora_Walk:
{
LDA.w SprTimerA, X : BNE +
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X : TAY
LDA.w .speed_x, Y : STA.w SprXSpeed, X
LDA.w .speed_y, Y : STA.w SprYSpeed, X
LDA.b #$6A : STA.w SprTimerA, X
+
RTS
.speed_x
db 0, -4, 4, 0
.speed_y
db 4, 0, 0, -4
}
```
## Dialogue Handling (`EonZora_HandleDialogue`)
This routine manages the Eon Zora's dialogue, which is context-sensitive based on Link's current `AreaIndex`. It checks for specific `AreaIndex` values to display tailored messages. If no specific area matches, a default message is displayed, and interacting with it can randomly set the `FOUNDRINGS` global variable.
```asm
EonZora_HandleDialogue:
{
LDA.w AreaIndex : CMP.b #$63 : BNE .not_wisdom
%ShowSolicitedMessage($01AC)
JMP ++
.not_wisdom
CMP.b #$5B : BNE .not_power
%ShowSolicitedMessage($01AB)
JMP ++
.not_power
CMP.b #$40 : BNE .not_pyramid
%ShowSolicitedMessage($01AA)
JMP ++
.not_pyramid
CMP.b #$70 : BNE .not_underwater
%ShowSolicitedMessage($01AD)
JMP ++
.not_underwater
CMP.b #$42 : BNE .not_portal
%ShowSolicitedMessage($01AF)
JMP ++
.not_portal
%ShowSolicitedMessage($01AE) : BCC .no_talk
JSL GetRandomInt : AND.b #$06 : STA.l FOUNDRINGS
.no_talk
++
RTS
}
```
## Drawing (`Sprite_EonZora_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_EonZora_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, 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, $02, $04, $06, $08, $0A, $0C, $0D
.nbr_of_tiles
db 1, 1, 1, 1, 1, 1, 0, 0
.x_offsets
dw 0, 16
dw 0, -16
dw 0, 8
dw 0, 8
dw 0, -8
dw 0, -8
dw 0
dw 0
.y_offsets
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0
dw 0
.chr
db $60, $62
db $60, $62
db $40, $41
db $43, $44
db $40, $41
db $43, $44
db $64
db $64
.properties
db $39, $39
db $79, $79
db $39, $39
db $39, $39
db $79, $79
db $79, $79
db $39
db $79
.sizes
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02
db $02
}
```
## Design Patterns
* **Context-Sensitive Dialogue**: The NPC's dialogue dynamically changes based on Link's current `AreaIndex`, providing a rich and immersive storytelling experience tailored to the player's location.
* **Random Movement**: The Zora exhibits random walking behavior, contributing to the environmental ambiance and making the world feel more alive.
* **NPC Interaction**: Provides dialogue and has the potential to grant items (randomly setting `FOUNDRINGS`), adding an element of surprise and reward to player interactions.
* **Animation-Driven Movement**: The sprite's movement states are directly tied to specific animations for each direction, ensuring visual consistency between its actions and appearance.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,127 @@
# Eon Zora Elder
## Overview
The Eon Zora Elder is an NPC (Non-Player Character) sprite primarily characterized by its animation-driven states. Its main function is to visually convey different moods or actions through distinct animations, such as idle, surprised, or holding a rod.
## Sprite Properties
Explicit sprite properties (`!SPRID`, `!NbrTiles`, etc.) are not defined within this file. It is assumed that these properties are either inherited from a vanilla sprite ID or defined in a separate configuration file, as this file focuses on the sprite's behavior and drawing.
## Main Logic & State Machine (`Sprite_EonZoraElder_Main`)
The Eon Zora Elder's core behavior is managed by a simple state machine that primarily controls its animations:
* **`EonZoraElder_Idle`**: Plays an idle animation (`%PlayAnimation(0,1,10)`).
* **`EonZoraElder_Surprised`**: Plays a surprised animation (`%PlayAnimation(2,3,10)`).
* **`EonZoraElder_WithRod`**: Plays an animation depicting the elder holding a rod (`%PlayAnimation(4,4,10)`).
```asm
Sprite_EonZoraElder_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw EonZoraElder_Idle
dw EonZoraElder_Surprised
dw EonZoraElder_WithRod
EonZoraElder_Idle:
%PlayAnimation(0,1,10)
RTS
EonZoraElder_Surprised:
%PlayAnimation(2,3,10)
RTS
EonZoraElder_WithRod:
%PlayAnimation(4,4,10)
RTS
}
```
## Drawing (`Sprite_EonZoraElder_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_EonZoraElder_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, 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, $02, $04, $06
.nbr_of_tiles
db 1, 1, 1, 2
.x_offsets
dw 0, 8
dw 0, 8
dw 0, 8
dw 0, 8, -4
.y_offsets
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0, 0
.chr
db $46, $47
db $49, $4A
db $66, $67
db $69, $6A, $6C
.properties
db $39, $39
db $39, $39
db $39, $39
db $39, $39, $39
.sizes
db $02, $02
db $02, $02
db $02, $02
db $02, $02, $02
}
```
## Design Patterns
* **Animation-Driven States**: The sprite's states are primarily used to control which animation is currently playing, allowing for visual feedback to the player (e.g., idle, surprised, holding a rod).
* **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.

333
Docs/Sprites/NPCs/Farore.md Normal file
View File

@@ -0,0 +1,333 @@
# Farore
## Overview
Farore, the Oracle of Secrets, is a pivotal NPC sprite deeply integrated into the game's narrative and cutscene system. Her behavior is highly dynamic, adapting to the player's location (indoors/outdoors) and various game progression flags. She plays a crucial role in guiding the player and controlling cinematic sequences.
## Sprite Properties
* **`!SPRID`**: `Sprite_Farore` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `2`
* **`!Harmless`**: `00` (Unusual for an NPC, might indicate specific interaction or placeholder)
* **`!HVelocity`**: `00`
* **`!Health`**: `0`
* **`!Damage`**: `0`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `01`
* **`!Shadow`**: `01`
* **`!Palette`**: `0`
* **`!Hitbox`**: `0`
* **`!Persist`**: `00`
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `0`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_Farore_Long`)
This routine acts as a dispatcher, conditionally calling different drawing and main logic routines based on whether Link is `INDOORS`. This indicates that the `Farore` sprite ID is reused for a different entity (likely "Hyrule Dream") when indoors.
```asm
Sprite_Farore_Long:
{
PHB : PHK : PLB
LDA.b INDOORS : BEQ .outdoors
JSR Sprite_HyruleDream_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_HyruleDream_Main
JMP .SpriteIsNotActive
.outdoors
JSR Sprite_Farore_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Farore_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Farore_Prep`)
This routine initializes Farore upon spawning. It sets `SprDefl, X` to `$80` to prevent despawning off-screen. It also includes conditional initialization based on `INDOORS` and a check for `$7EF300` (likely a flag for Farore's presence) to potentially despawn the sprite.
```asm
Sprite_Farore_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X ; Don't kill Farore when she goes off screen
LDA.b INDOORS : BEQ .outdoors
JSR Sprite_HyruleDream_Prep
JMP .PlayIntro
.outdoors
LDA.l $7EF300 : BEQ .PlayIntro
STZ.w SprState, X ; Kill the sprite
.PlayIntro
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Farore_Main`)
Farore's core behavior is managed by a complex state machine heavily involved in cutscenes and quest progression:
* **`IntroStart`**: Initiates a cutscene (`InCutScene = 01`) and transitions to different states based on `STORY_STATE` (`$B6`).
* **`MoveUpTowardsFarore`**: Controls Link's movement during a cutscene, slowing him down and moving him north. Transitions to `MoveLeftTowardsFarore` when Link reaches a certain Y-position.
* **`MoveLeftTowardsFarore`**: Continues Link's controlled movement, moving him west. Stops auto-movement, sets a timer, and transitions to `WaitAndMessage`.
* **`WaitAndMessage`**: Displays a message after a timer, applies speed towards the player, and transitions to `Farore_ProceedWithCutscene`.
* **`Farore_ProceedWithCutscene`**: A transitional state that leads to `FaroreFollowPlayer` after a timer.
* **`FaroreFollowPlayer`**: Farore follows Link, controlling his movement and updating various game state flags (`GAMESTATE`, `STORY_STATE`, rain sound). Transitions to `MakuArea_FaroreFollowPlayer`.
* **`MakuArea_FaroreFollowPlayer`**: Farore continues to follow Link in the Maku Area.
* **`MakuArea_FaroreWaitForKydrog`**: Farore waits in the Maku Area.
```asm
Sprite_Farore_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw IntroStart
dw MoveUpTowardsFarore
dw MoveLeftTowardsFarore
dw WaitAndMessage
dw Farore_ProceedWithCutscene
dw FaroreFollowPlayer
dw MakuArea_FaroreFollowPlayer
dw MakuArea_FaroreWaitForKydrog
; 00
IntroStart:
{
LDA #$01 : STA InCutScene
LDA $B6 : CMP.b #$01 : BEQ .maku_area
CMP.b #$02 : BEQ .waiting
%GotoAction(1)
RTS
.maku_area
%GotoAction(6)
RTS
.waiting
%GotoAction(7)
RTS
}
; 01
MoveUpTowardsFarore:
{
LDA.w WALKSPEED : STA.b $57 ; Slow Link down for the cutscene
LDA.b #$08 : STA.b $49 ; Auto-movement north
; Link's Y Position - Y = 6C
LDA.b $20 : CMP.b #$9C : BCC .linkistoofar
%GotoAction(2)
.linkistoofar
%PlayAnimation(6, 6, 8) ; Farore look towards Link
RTS
}
; 02
MoveLeftTowardsFarore:
{
; Move Link Left
LDA.w WALKSPEED : STA.b $57 ; Slow Link down for the cutscene
LDA.b #$02 : STA.b $49
; Link's X position
LDA.b $22 : CMP.b #$1A : BCS .linkistoofar
STZ.b $49 ; kill automove
LDA.b #$20
STA.w SprTimerA, X ; set timer A to 0x10
%PlayAnimation(0, 0, 8)
%GotoAction(3)
.linkistoofar
RTS
}
; 03
WaitAndMessage:
{
%PlayAnimation(1, 2, 8)
LDA.b #$15
JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_MoveVert
LDA.w SprTimerA, X : BNE +
STZ $2F
LDA #$00 : STA InCutScene
; "I am Farore, the Oracle of Secrets."
%ShowUnconditionalMessage($0E)
%GotoAction(4)
+
RTS
}
; 04
Farore_ProceedWithCutscene:
{
LDA.w SprTimerA, X : BNE ++
%GotoAction(5)
++
RTS
}
; 05
FaroreFollowPlayer:
{
LDA #$01 : STA InCutScene
LDA.w WALKSPEED : STA.b $57 ; Slow Link down for the cutscene
LDA.b #$08 : STA.b $49 ; Auto-movement north
%PlayAnimation(3, 4, 8)
LDA.b #$15
JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_MoveVert
LDA #$02 : STA $7EF3C5 ; (0 - intro, 1 - pendants, 2 - crystals)
LDA #$05 : STA $012D ; turn off rain sound
LDA #$01 : STA $B6 ; Set Story State
JSL Sprite_LoadGfxProperties
%GotoAction(6)
RTS
}
; 06
MakuArea_FaroreFollowPlayer:
{
%PlayAnimation(3, 4, 8)
LDA.b #$15
JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_MoveVert
%GotoAction(6)
RTS
}
; 07
MakuArea_FaroreWaitForKydrog:
{
%PlayAnimation(5, 5, 8)
RTS
}
}
```
## Drawing (`Sprite_Farore_Draw`)
This routine handles OAM allocation and animation for Farore. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
```asm
Sprite_Farore_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, $02, $04, $06, $08, $0A, $0C
.nbr_of_tiles
db 1, 1, 1, 1, 1, 1, 1
.x_offsets
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, 0
dw 0, -1
.y_offsets
dw -8, 4
dw -8, 4
dw 4, -8
dw -8, 4
dw 4, -7
dw -8, 4
dw 4, -7
.chr
db $A8, $AA
db $A8, $88
db $AA, $A8
db $8A, $8C
db $8C, $8A
db $8A, $AC
db $AA, $86
.properties
db $3B, $3B
db $3B, $7B
db $3B, $3B
db $3B, $3B
db $7B, $3B
db $3B, $3B
db $3B, $7B
.sizes
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
db $02, $02
}
```
## Design Patterns
* **Multi-Character Sprite (Conditional Drawing/Logic)**: The sprite ID is reused for "Hyrule Dream" when indoors, demonstrating a powerful technique for resource optimization and context-sensitive character representation.
* **Cutscene Control**: Farore's logic is heavily integrated with cutscenes, controlling Link's movement, displaying messages, and managing game state transitions to create cinematic sequences.
* **Quest Progression Integration**: The sprite's appearance and behavior are tied to `STORY_STATE` and other game flags, indicating its crucial role in advancing the narrative.
* **Player Movement Manipulation**: During cutscenes, Farore's script directly controls Link's speed and auto-movement, ensuring precise choreography for story events.
* **Global State Management**: Modifies `InCutScene`, `GAMESTATE`, `STORY_STATE`, and other global variables to reflect and control the current game context.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,94 @@
# Followers
## Overview
The `followers.asm` file is a comprehensive collection of routines and data structures that implement a sophisticated follower system within Oracle of Secrets. It manages various NPC types, including the Zora Baby, Old Man, Kiki, and a Minecart, each with unique behaviors, interactions, and integration into the game world. This file heavily utilizes vanilla overrides to inject custom logic and expand upon existing game mechanics.
## Follower Data Memory Locations
This section defines various WRAM addresses used to store and cache follower-related data, enabling complex interactions and animations:
* **`FollowerYL`, `FollowerYH`, `FollowerXL`, `FollowerXH`, `FollowerZ`, `FollowerLayer`**: Stores position (Y, X, Z coordinates) and layer information for followers, with a cache for 20 steps of animation and movement.
* **`FollowerHeadOffset`, `FollowerHeadOffsetH`, `FollowerBodyOffset`, `FollowerBodyOffsetH`**: Stores offsets for follower head and body graphics, used to adjust their appearance based on direction (e.g., facing Link).
* **`Flwhgfxt`, `Flwhgfxth`, `Flwhgfxb`, `Flwhgfxbh`, `Flwbgfxt`, `Flwbgfxth`, `Flwbgfxb`, `Flwbgfxbh`**: Graphics data for follower head and body.
* **`Flwanimir`**: Index for reading follower animation steps.
* **`FollowerHook`**: Flag indicating when a follower is being used with the Hookshot.
* **`FollowerHookI`**: Caches `FLWANIMIW` when Hookshotting is finished.
* **`FLWGRABTIME`**: Countdown timer preventing followers from being immediately regrabbed after being dropped.
* **`FLWANIMIW`**: Index for writing follower animation steps.
* **`FollowCacheYL`, `FollowCacheYH`, `FollowCacheXL`, `FollowCacheXH`**: Cache of follower properties in SRAM.
## `Follower_WatchLink`
This routine adjusts a follower's head and body graphics offsets to make them turn and face Link, providing a more interactive and responsive NPC presence.
## Zora Baby Follower
The Zora Baby follower is a key NPC involved in specific puzzles and interactions, particularly with water switches.
* **`ZoraBaby_RevertToSprite`**: This routine spawns a `Sprite 0x39 Locksmith` (which is the Zora Baby sprite) and initializes its properties based on the follower's cached data. It sets `SprBulletproof`, `SprAction`, and `SprTimerB`, and clears relevant follower flags.
* **`CheckForZoraBabyTransitionToSprite`**: Checks if the Zora Baby is currently a follower (`$7EF3CC = $09`). If Link is standing on a star tile (`$0114 = $3B`), it calls `ZoraBaby_RevertToSprite` to transition the follower back into a regular sprite. If Link is outdoors, it clears the follower flag.
* **`CheckForZoraBabyFollower`**: A utility routine to check if the Zora Baby is currently a follower.
* **`UploadZoraBabyGraphicsPrep`**: Prepares the graphics for the Zora Baby, setting `$7EF3CC` to `$09` and calling `LoadFollowerGraphics`.
* **`ZoraBaby_CheckForWaterSwitchSprite`**: Checks for the presence of a `Sprite 0x21` (Water Gate Switch) and determines if the Zora Baby is positioned on top of it.
* **`ZoraBaby_CheckForWaterGateSwitch`**: Checks for a `Sprite 0x04` (Water Gate Switch) and performs a precise coordinate check to see if the Zora Baby is on top of it.
* **`ZoraBaby_GlobalBehavior`**: This is the main behavior routine for the Zora Baby. It makes the Zora Baby act as a barrier (`Sprite_BehaveAsBarrier`), makes it watch Link (`Follower_WatchLink`), and handles interactions like being lifted (`Sprite_CheckIfLifted`) and thrown (`ThrownSprite_TileAndSpriteInteraction_long`). Crucially, it detects if the Zora Baby is on a water switch and triggers the `ZoraBaby_PullSwitch` state.
### Zora Baby Vanilla Overrides
* **`org $09AA5E`**: Injects `JSL CheckForZoraBabyFollower` to enable the Zora Baby's swaying animation.
* **`org $09A19C`**: Injects `JSL CheckForZoraBabyTransitionToSprite` for follower basic movement.
* **`org $09A902`**: Sets the Zora Baby follower's palette to blue.
* **`org $09A8CF`**: Sets the Zora Baby character data offset.
* **`org $06BD9C`**: Defines the Zora Baby Sprite Idle OAM data.
* **`org $068D59` (`SpritePrep_Locksmith`)**: Overrides the `SpritePrep_Locksmith` routine. It makes the Zora Baby bulletproof, prevents spawning if already following, and calls `UploadZoraBabyGraphicsPrep`.
* **`org $06BCAC` (`Sprite_39_ZoraBaby`)**: Overrides `Sprite_39_Locksmith`. This is the main state machine for the Zora Baby, including states like `LockSmith_Chillin` (idle), `ZoraBaby_FollowLink`, `ZoraBaby_OfferService`, `ZoraBaby_RespondToAnswer`, `ZoraBaby_AgreeToWait`, `ZoraBaby_PullSwitch`, and `ZoraBaby_PostSwitch`. These states manage dialogue, following behavior, and interaction with switches.
## Old Man Follower
This section includes logic for the Old Man follower, particularly concerning his spawning conditions and item interactions.
* **`OldMan_ExpandedPrep`**: Prevents the Old Man sprite from spawning in his home room if Link already has him as a follower.
### Old Man Vanilla Overrides
* **`org $1EE9FF`**: Modifies the item given by the Old Man to be the Goldstar Hookshot upgrade.
* **`org $1BBD3C`**: Modifies `FindEntrance` for the Old Man.
* **`org $02D98B`**: Modifies `Underworld_LoadEntrance` for the Old Man.
* **`org $1EE8F1` (`SpritePrep_OldMan`)**: Overrides `SpritePrep_OldMan`. It makes the Old Man bulletproof, uses `OldMan_ExpandedPrep`, checks for the Lv2 Hookshot, and sets `$7EF3CC` to `$04` (Old Man follower) before calling `LoadFollowerGraphics`.
* **`org $09A4C8` (`Follower_HandleTriggerData`)**: This is a large data block defining trigger coordinates and messages for various followers, including the Old Man, Zelda, and Blind Maiden.
## Kiki Follower
This section contains logic for the Kiki follower, focusing on her reaction to Link's health.
* **`Kiki_CheckIfScared`**: If Link's health is low and Kiki is flashing, she will run away from him.
### Kiki Vanilla Overrides
* **`org $09A1C6`**: Injects `JSL Kiki_CheckIfScared` to implement Kiki's fear behavior.
* **`org $1EE2E9` (`Kiki_WalkOnRoof`)**: Defines speed data for Kiki walking on a roof.
* **`org $1EE576` (`Kiki_HopToSpot`)**: Defines target coordinates for Kiki to hop to a spot.
* **`org $1EE5E9` (`Kiki_WalkOnRoof_Ext`)**: Defines step and timer data for Kiki's extended roof walk.
## Minecart Follower
This section details the implementation of the Minecart follower, including its drawing, transition, and Link's interaction with it.
* **`FollowerDraw_CalculateOAMCoords`**: A helper routine to calculate OAM coordinates for followers.
* **`MinecartFollower_Top` / `MinecartFollower_Bottom`**: Drawing routines for the top and bottom halves of the Minecart follower.
* **`Minecart_AnimDirection`**: Data for Minecart animation direction.
* **`MinecartFollower_TransitionToSprite`**: Transitions the Minecart follower back into a regular sprite.
* **`DrawMinecartFollower`**: The main drawing routine for the Minecart follower, which also handles its transition to a sprite if Link is in the cart and not in a submodule.
* **`FollowerDraw_CachePosition`**: Caches the follower's position for drawing, adjusting coordinates relative to Link.
* **`CheckForMinecartFollowerDraw`**: Checks if the Minecart follower should be drawn.
* **`CheckForFollowerInterroomTransition` / `CheckForFollowerIntraroomTransition`**: Handles transitions for followers between rooms and within rooms.
* **`LinkState_Minecart`**: Defines Link's behavior when he is in a Minecart, including movement, collision, and animation.
* **`TileBehavior_TL_Long` / `TileBehavior_StopLeft_Long`**: Tile behaviors for Minecart tracks.
### Minecart Vanilla Overrides
* **`org $07A5F7`**: Injects `JSL LinkState_Minecart` to control Link's state when in a Minecart.
* **`org $07D938`**: Defines Minecart Track tile types.
* **`org $09A41F`**: Injects `JSL CheckForMinecartFollowerDraw`.
* **`org $028A5B`**: Injects `JSL CheckForFollowerInterroomTransition`.
* **`org $0289BF`**: Injects `JSL CheckForFollowerIntraroomTransition`.
## Design Patterns
* **Multi-Purpose File**: This file serves as a central repository for various follower-related logic, demonstrating how to manage diverse NPC behaviors within a single module.
* **Follower System**: Implements a robust and flexible follower system with features like position caching, animation, and complex interaction logic.
* **Vanilla Overrides**: Extensive use of `org` directives to modify vanilla sprite behaviors and integrate custom follower logic, showcasing advanced ROM hacking techniques.
* **Context-Sensitive Behavior**: Follower behavior dynamically changes based on game state, player actions, and environmental factors (e.g., Zora Baby on water switch, Old Man's spawning conditions, Kiki's fear of low-health Link).
* **Cutscene Integration**: Some followers (like the Zora Baby) are involved in cutscene-like sequences, demonstrating how to choreograph NPC actions within narrative events.
* **Item Gating/Progression**: The Old Man's appearance and item offerings are tied to the player's possession of specific items (e.g., Lv2 Hookshot), integrating followers into the game's progression system.
* **Player State Manipulation**: Routines like `LinkState_Minecart` directly control Link's movement and animation when interacting with followers, providing a seamless player experience.
* **16-bit OAM Calculations**: Explicitly uses `REP #$20` and `SEP #$20` for precise 16-bit OAM calculations in drawing routines, ensuring accurate sprite rendering.

View File

@@ -0,0 +1,88 @@
# Fortune Teller
## Overview
The `fortune_teller.asm` file is not a complete sprite definition but rather a set of routines and data that override and extend the behavior of the vanilla Fortune Teller NPC. Its primary function is to provide highly context-sensitive messages to Link, offering guidance or commentary based on his current inventory, collected items, and overall game progression.
## Vanilla Overrides
This file directly modifies existing vanilla code related to the Fortune Teller:
* **`org $0DC829`**: Overrides the `FortuneTellerMessage` data table, which contains the message IDs that the Fortune Teller can display.
* **`org $0DC849`**: Overrides the `FortuneTeller_PerformPseudoScience` routine, which is the core logic for determining and displaying messages.
## `FortuneTellerMessage` Data Table
This table stores a sequence of byte values, each representing a message ID. These IDs correspond to specific dialogue options that the Fortune Teller can present to Link.
```asm
org $0DC829
FortuneTellerMessage:
.low
#_0DC829: db $EA ; MESSAGE 00EA
#_0DC82A: db $EB ; MESSAGE 00EB
#_0DC82B: db $EC ; MESSAGE 00EC
#_0DC82C: db $ED ; MESSAGE 00ED
#_0DC82D: db $EE ; MESSAGE 00EE
#_0DC82E: db $EF ; MESSAGE 00EF
#_0DC82F: db $F0 ; MESSAGE 00F0
#_0DC830: db $F1 ; MESSAGE 00F1
#_0DC831: db $F6 ; MESSAGE 00F6
#_0DC832: db $F7 ; MESSAGE 00F7
#_0DC833: db $F8 ; MESSAGE 00F8
#_0DC834: db $F9 ; MESSAGE 00F9
#_0DC835: db $FA ; MESSAGE 00FA
#_0DC836: db $FB ; MESSAGE 00FB
#_0DC837: db $FC ; MESSAGE 00FC
#_0DC838: db $FD ; MESSAGE 00FD
.high
#_0DC839: db $00
; ... (rest of the table)
```
## `FortuneTeller_PerformPseudoScience` Routine
This routine is the central logic for the custom Fortune Teller. It dynamically selects which message to display to Link based on a series of checks against his inventory and various game progression flags stored in SRAM.
* **Initializations**: Performs some initial state manipulations (`STZ.w $0DC0,X`, `INC.w $0D80,X`, `STZ.b $03`).
* **Progression Check (`$7EF3D6`)**: Determines a base message category based on a custom progression flag.
* **Extensive Item and Game State Checks**: The routine then proceeds through a series of conditional checks, each corresponding to a specific item or game event. For example, it checks for:
* `$7EF344` (Mushroom/Powder)
* `$7EF37A` (Crystals, specifically if Tail Palace is beaten)
* `$7EF355` (Boots)
* `$7EF356` (Flippers)
* `$7EF345` (Fire Rod)
* `$7EF37B` (Magic Upgrade)
* `$7EF354` (Glove)
* `$7EF358` (Wolf Mask)
* `$7EF3C9` (Smithy Rescued flag)
* `$7EF352` (Cape)
* `$7EF354` (Titans Mitt)
* `$7EF359` (Sword level)
* **Message Display**: Based on these checks, it calls `FortuneTeller_PrepareNextMessage` with the appropriate message index and then `FortuneTeller_DisplayMessage` to show the message to the player.
```asm
FortuneTeller_PerformPseudoScience:
#_0DC849: STZ.w $0DC0,X
#_0DC84C: INC.w $0D80,X
#_0DC84F: STZ.b $03
#_0DC851: LDA.l $7EF3D6
#_0DC855: CMP.b #$02
#_0DC857: BCS .map_icon_past_pendants
#_0DC859: STZ.b $00
#_0DC85B: STZ.b $01
#_0DC85D: JMP.w FortuneTeller_DisplayMessage
.map_icon_past_pendants
#_0DC860: LDA.l $7EF344
#_0DC864: BNE .have_shroom_or_powder
#_0DC866: LDA.b #$02
#_0DC868: JSR FortuneTeller_PrepareNextMessage
#_0DC86B: BCC .have_shroom_or_powder
#_0DC86D: JMP.w FortuneTeller_DisplayMessage
; ... (rest of the conditional item checks)
```
## Design Patterns
* **Vanilla Override**: This file exemplifies how to directly modify and extend the behavior of existing vanilla NPCs through targeted code injection.
* **Context-Sensitive Dialogue**: The Fortune Teller's messages are highly dynamic and personalized, adapting to Link's current inventory and game progression. This creates a more engaging and responsive NPC interaction.
* **Quest Progression Tracking**: The routine extensively utilizes SRAM flags and item possession checks to track Link's progress through various quests and milestones, influencing the dialogue provided.
* **Modular Message System**: The use of `FortuneTeller_PrepareNextMessage` and `FortuneTeller_DisplayMessage` allows for a structured and modular approach to managing and displaying NPC dialogue.

179
Docs/Sprites/NPCs/Goron.md Normal file
View File

@@ -0,0 +1,179 @@
# Goron
## Overview
The Goron sprite (`!SPRID = $F2`) is a versatile NPC implementation that can represent two distinct Goron characters: the "Kalyxo Goron" and the "Eon Goron." Their specific behaviors and appearances are determined by the global `WORLDFLAG` and the current `AreaIndex`. These Gorons primarily serve as interactive NPCs, engaging Link through dialogue and potentially triggering game events.
## Sprite Properties
* **`!SPRID`**: `$F2` (Vanilla sprite ID, likely for a generic NPC)
* **`!NbrTiles`**: `04`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!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_Goron_Long`)
This routine acts as a dispatcher, selecting the appropriate drawing routine based on the `WORLDFLAG` (Kalyxo Goron if `0`, Eon Goron otherwise). It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_Goron_Long:
{
PHB : PHK : PLB
LDA.w WORLDFLAG : BEQ .kalyxo
JSR Sprite_EonGoron_Draw
JMP +
.kalyxo
JSR Sprite_KalyxoGoron_Draw
+
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Goron_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Goron_Prep`)
This routine initializes the Goron upon spawning. It sets `SprDefl, X` to `$80`. The initial `SprAction, X` is determined by `WORLDFLAG` and `AreaIndex`. For Eon Gorons, it can randomly set their initial action to `EonGoron_Main`, `EonGoron_Sing`, or `EonGoron_Punch`. For Kalyxo Gorons, it checks a specific flag (`$7EF280, X`) to set their initial action to `KalyxoGoron_Main` or `KalyxoGoron_MinesOpened`.
```asm
Sprite_Goron_Prep:
{
PHB : PHK : PLB
LDA.w WORLDFLAG : BEQ +
LDA.w AreaIndex : CMP.b #$55 : BNE .not_sing
LDA.b #$04 : STA.w SprAction, X
.not_sing
JSL GetRandomInt : AND.b #$01 : BEQ .rand
LDA.b #$05 : STA.w SprAction, X
JMP ++
.rand
LDA.b #$03 : STA.w SprAction, X
JMP ++
+
PHX
LDX $8A
LDA.l $7EF280, X : CMP.b #$20 : BEQ +++
PLX
STZ.w SprAction, X
++
PLB
RTL
+++
PLX
LDA.b #$02 : STA.w SprAction, X
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Goron_Main`)
This routine manages the various states and behaviors of both Kalyxo and Eon Gorons.
* **Player Collision**: Prevents Link from passing through the Goron (`Sprite_PlayerCantPassThrough`).
* **`KalyxoGoron_Main`**: Displays messages (`%ShowSolicitedMessage`) based on the `RockMeat` item count. Can transition to `KalyxoGoron_OpenMines` under certain conditions.
* **`KalyxoGoron_OpenMines`**: Plays an animation, sets a flag (`$04C6`) to open mines, and transitions to `KalyxoGoron_MinesOpened`.
* **`KalyxoGoron_MinesOpened`**: Plays an animation.
* **`EonGoron_Main`**: Plays an animation and displays a message.
* **`EonGoron_Sing`**: Plays a singing animation and displays a message.
* **`EonGoron_Punch`**: Plays a punching animation and displays a message.
```asm
Sprite_Goron_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw KalyxoGoron_Main
dw KalyxoGoron_OpenMines
dw KalyxoGoron_MinesOpened
dw EonGoron_Main
dw EonGoron_Sing
dw EonGoron_Punch
KalyxoGoron_Main:
{
LDA.l RockMeat : BEQ +
CMP.b #$05 : BCC ++
%ShowSolicitedMessage($01A9) : BCC +++
INC.w SprAction, X
+++
RTS
+
%ShowSolicitedMessage($01A7)
RTS
++
%ShowSolicitedMessage($01A8)
RTS
}
KalyxoGoron_OpenMines:
{
%PlayAnimation(1,1,10)
LDA.b #$04 : STA $04C6
INC.w SprAction, X
RTS
}
KalyxoGoron_MinesOpened:
{
%PlayAnimation(1,1,10)
RTS
}
EonGoron_Main:
{
%PlayAnimation(0, 1, 10)
%ShowSolicitedMessage($01B0)
RTS
}
EonGoron_Sing:
{
%PlayAnimation(2, 3, 10)
%ShowSolicitedMessage($01B2)
RTS
}
EonGoron_Punch:
{
%PlayAnimation(4, 5, 10)
%ShowSolicitedMessage($01B1)
RTS
}
}
```
## Drawing (`Sprite_KalyxoGoron_Draw` and `Sprite_EonGoron_Draw`)
Both drawing routines handle OAM allocation and animation for their respective Goron types. They explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
## Design Patterns
* **Multi-Character NPC (Conditional Drawing/Logic)**: A single sprite definition is used to represent two distinct Goron characters (Kalyxo and Eon) based on `WORLDFLAG`, demonstrating efficient resource utilization and context-sensitive character representation.
* **Quest Progression Integration**: The Gorons' dialogue and actions are tied to game state (e.g., `RockMeat` item count, `AreaIndex`), indicating their role in advancing the narrative and triggering specific events like opening mines.
* **Conditional Behavior**: Extensive use of conditional logic based on `WORLDFLAG` and `AreaIndex` allows for dynamic changes in the Goron's role, dialogue, and actions.
* **NPC Interaction**: Provides rich interaction with the player through dialogue (`%ShowSolicitedMessage`) and can trigger game events.
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make the NPC a solid object that Link cannot walk through.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,152 @@
# Hyrule Dream
## Overview
The Hyrule Dream sprite represents a special NPC that appears indoors and plays a role in a specific questline. It interacts with Link through dialogue and grants a unique "Dream" item, with its presence and actions tied to game progression.
## Sprite Properties
Explicit sprite properties (`!SPRID`, `!NbrTiles`, etc.) are not defined within this file. It is assumed that these properties are either inherited from a vanilla sprite ID or defined in a separate configuration file, as this file focuses on the sprite's behavior and drawing.
## Main Logic (`Sprite_HyruleDream_Main`)
This routine manages the Hyrule Dream's behavior through a state machine:
* **`HyruleDream_Idle`**: The sprite plays an idle animation. When Link is within a certain proximity (`GetDistance8bit_Long`), it transitions to the `HyruleDream_Talk` state.
* **`HyruleDream_Talk`**: The sprite continues its idle animation and displays a solicited message (`%ShowSolicitedMessage($01B3)`). Upon completion of the message, it transitions to `HyruleDream_GiveItem`.
* **`HyruleDream_GiveItem`**: The sprite maintains its idle animation. After a timer (`SprTimerA, X`) expires, it grants Link the "Dream" item (`LDY #$12`, `JSL Link_ReceiveItem`), sets a flag (`$7EF410`) indicating the Dream has been obtained, and then despawns itself (`STZ.w SprState, X`).
```asm
Sprite_HyruleDream_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw HyruleDream_Idle
dw HyruleDream_Talk
dw HyruleDream_GiveItem
HyruleDream_Idle:
{
%PlayAnimation(0,0,1)
JSL GetDistance8bit_Long : CMP.b #$20 : BCS +
INC.w SprAction, X
+
RTS
}
HyruleDream_Talk:
{
%PlayAnimation(0,0,1)
%ShowSolicitedMessage($01B3) : BCC +
INC.w SprAction, X
+
RTS
}
HyruleDream_GiveItem:
{
%PlayAnimation(0,0,1)
LDA.w SprTimerA, X : BNE +
LDY #$12 : JSL Link_ReceiveItem
LDA.b #$01 : STA.l $7EF410
STZ.w SprState, X
+
RTS
}
}
```
## Initialization (`Sprite_HyruleDream_Prep`)
This routine initializes the Hyrule Dream sprite upon spawning. It sets `SprDefl, X` to `$80` (preventing despawning off-screen). Crucially, it checks the "Dream" flag (`$7EF410`). If Link has already obtained the Dream, the sprite immediately despawns (`STZ.w SprState, X`), ensuring it only appears once.
```asm
Sprite_HyruleDream_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.l $7EF410 : BNE +
STZ.w SprState, X
+
PLB
RTL
}
```
## Drawing (`Sprite_HyruleDream_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_HyruleDream_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, 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
.nbr_of_tiles
db 3
.x_offsets
dw -8, 8, -8, 8
.y_offsets
dw -8, -8, 8, 8
.chr
db $C0, $C2, $E0, $E2
.properties
db $3B, $7B, $3B, $7B
.sizes
db $02, $02, $02, $02
}
```
## Design Patterns
* **NPC Interaction**: The sprite is designed to engage with the player through dialogue and the granting of a unique item, driving a specific questline.
* **Quest Progression Integration**: The sprite's appearance and item-granting are directly tied to a flag (`$7EF410`) for the "Dream" item, ensuring it appears only once per playthrough.
* **Conditional Spawning/Despawning**: The sprite dynamically despawns if Link has already obtained the "Dream" item, preventing redundant interactions.
* **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.

43
Docs/Sprites/NPCs/Impa.md Normal file
View File

@@ -0,0 +1,43 @@
# Impa (Zelda)
## Overview
The `impa.asm` file is not a complete sprite definition but rather a collection of routines and vanilla overrides that modify the behavior of the character Impa (or Zelda, as indicated by some labels). Its primary purpose is to manage spawn points and manipulate game state during critical events, likely cutscenes or key progression points within the game.
## Spawn Point Definitions
The file defines a WRAM address `SPAWNPT = $7EF3C8` for a spawn point flag, along with comments detailing various spawn point IDs:
* `0x00` - Link's house
* `0x01` - Sanctuary (Hall of Secrets)
* `0x02` - Castle Prison
* `0x03` - Castle Basement
* `0x04` - Throne
* `0x05` - Old man cave
* `0x06` - Old man home
## `Impa_SetSpawnPointFlag`
This routine is responsible for setting a specific spawn point flag. It stores a value into `$7EF372` (likely the actual spawn point ID) and then sets bit `$04` in `$7EF3D6` (a custom progression flag), indicating that a particular event has occurred.
```asm
Impa_SetSpawnPointFlag:
{
STA.l $7EF372
LDA.l $7EF3D6 : ORA.b #$04 : STA.l $7EF3D6
RTL
}
```
## Vanilla Overrides
This file extensively uses `pushpc`/`pullpc` blocks and `org` directives to inject custom code into specific vanilla routines, thereby altering the game's default behavior:
* **`org $05EE46` (`Zelda_AtSanctuary`)**: Injects `JSL Impa_SetSpawnPointFlag`. This modification ensures that when Zelda (or Impa) is at the Sanctuary, a specific spawn point flag is set, influencing where Link might respawn or transition.
* **`org $05EBCF`**: Modifies a comparison involving `$7EF359` (likely Link's Sword level) with `$05`. This is noted as a `TODO`, suggesting it's an incomplete or placeholder modification.
* **`org $029E2E` (`Module15_0C`)**: Modifies an overlay that Impa activates after the intro sequence. It sets bit `$20` in `$7EF2A3` (likely an overlay control flag), potentially changing the visual environment.
* **`org $05ED43` (`Zelda_BecomeFollower`)**: Prevents Impa from setting a spawn point by clearing `$02E4` and `$7EF3C8`. This suggests a custom handling of Impa's follower state.
* **`org $05ED63`**: NOPs out 5 bytes, effectively disabling some vanilla code at this address.
* **`org $05ED10` (`Zelda_ApproachHero`)**: NOPs out 5 bytes, preventing Impa from changing the game's background music or sound, indicating custom control over audio during this event.
## Design Patterns
* **Vanilla Override**: This file is a prime example of how to extensively override vanilla code to integrate custom NPC behaviors and progression systems into an existing game.
* **Quest Progression Tracking**: Utilizes custom progression flags (`$7EF3D6`) and spawn point flags (`$7EF372`, `$7EF3C8`) to meticulously track key events and the player's progress through the game's narrative.
* **Game State Manipulation**: Directly modifies WRAM addresses to control various aspects of the game, such as visual overlays and audio, providing fine-grained control over the player's experience.
* **Modular Code Injection**: The use of `org` directives allows for precise injection of custom routines into specific points of the vanilla codebase, enabling targeted modifications without disrupting unrelated functionality.

184
Docs/Sprites/NPCs/Korok.md Normal file
View File

@@ -0,0 +1,184 @@
# Korok
## Overview
The Korok sprite (`!SPRID = Sprite_Korok`) implements a multi-variant NPC system, allowing for different Korok characters (Makar, Hollo, Rown) to appear from a single sprite definition. These Koroks exhibit random walking behavior, engage in dialogue, and are liftable, contributing to environmental interactions and minor puzzles.
## Sprite Properties
* **`!SPRID`**: `Sprite_Korok` (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`**: `01`
* **`!Palette`**: `00`
* **`!Hitbox`**: `03`
* **`!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_Korok_Long`)
This routine acts as a dispatcher for drawing the correct Korok variant based on its `SprSubtype, X`. It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_Korok_Long:
{
PHB : PHK : PLB
LDA $0AA5 : BEQ .done
LDA.w SprSubtype, X : BEQ .draw_makar
CMP.b #$01 : BEQ .draw_hollo
CMP.b #$02 : BEQ .draw_rown
.draw_makar
JSR Sprite_Korok_DrawMakar
BRA .done
.draw_hollo
JSR Sprite_Korok_DrawHollo
BRA .done
.draw_rown
JSR Sprite_Korok_DrawRown
BRA .done
.done
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Korok_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Korok_Prep`)
This routine initializes the Korok upon spawning by randomly assigning a `SprSubtype, X` (0-3). This subtype determines which Korok variant (Makar, Hollo, or Rown) the sprite will represent.
```asm
Sprite_Korok_Prep:
{
PHB : PHK : PLB
JSL GetRandomInt : AND.b #$03 : STA.w SprSubtype, X
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Korok_Main`)
The Korok's core behavior is managed by a state machine that includes idle, walking, and liftable states.
* **`Sprite_Korok_Idle`**: The Korok plays an idle animation. Upon player interaction (`%ShowSolicitedMessage($001D)`), it randomly transitions to a walking state. It also prevents player passage (`Sprite_PlayerCantPassThrough`).
* **`Sprite_Korok_WalkingDown` / `Up` / `Left` / `Right`**: These states control the Korok's movement in different directions. Each state plays a specific walking animation, sets the appropriate speed (`KorokWalkSpeed`), moves the sprite (`Sprite_Move`), and after a timer (`SprTimerB, X`), randomly transitions to another walking state.
* **`Sprite_Korok_Liftable`**: This state handles the Korok's interaction when lifted (`Sprite_CheckIfLifted`) and thrown (`ThrownSprite_TileAndSpriteInteraction_long`).
```asm
Sprite_Korok_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw Sprite_Korok_Idle
dw Sprite_Korok_WalkingDown
dw Sprite_Korok_WalkingUp
dw Sprite_Korok_WalkingLeft
dw Sprite_Korok_WalkingRight
dw Sprite_Korok_Liftable
Sprite_Korok_Idle:
{
%PlayAnimation(0, 0, 10)
LDA $0AA5 : BNE +
PHX
JSL ApplyKorokSpriteSheets
PLX
LDA.b #$01 : STA.w $0AA5
+
%ShowSolicitedMessage($001D) : BCC .no_talk
JSL GetRandomInt : AND.b #$03
STA.w SprAction, X
RTS
.no_talk
JSL Sprite_PlayerCantPassThrough
RTS
}
Sprite_Korok_WalkingDown:
{
%PlayAnimation(0, 2, 10)
LDA.b #KorokWalkSpeed : STA.w SprYSpeed, X
JSL Sprite_Move
LDA.w SprTimerB, X : BNE +
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X
+
RTS
}
Sprite_Korok_WalkingUp:
{
%PlayAnimation(3, 5, 10)
LDA.b #-KorokWalkSpeed : STA.w SprYSpeed, X
JSL Sprite_Move
LDA.w SprTimerB, X : BNE +
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X
+
RTS
}
Sprite_Korok_WalkingLeft:
{
%PlayAnimation(6, 8, 10)
LDA.b #KorokWalkSpeed : STA.w SprXSpeed, X
JSL Sprite_Move
LDA.w SprTimerB, X : BNE +
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X
+
RTS
}
Sprite_Korok_WalkingRight:
{
%PlayAnimation(9, 11, 10)
LDA.b #-KorokWalkSpeed : STA.w SprXSpeed, X
JSL Sprite_Move
LDA.w SprTimerB, X : BNE +
JSL GetRandomInt : AND.b #$03 : STA.w SprAction, X
+
RTS
}
Sprite_Korok_Liftable:
{
JSL Sprite_Move
JSL Sprite_CheckIfLifted
JSL ThrownSprite_TileAndSpriteInteraction_long
RTS
}
}
```
## Drawing (`Sprite_Korok_DrawMakar`, `Sprite_Korok_DrawHollo`, `Sprite_Korok_DrawRown`)
Each Korok variant has its own dedicated drawing routine (`Sprite_Korok_DrawMakar`, `Sprite_Korok_DrawHollo`, `Sprite_Korok_DrawRown`). 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 Korok character.
## Design Patterns
* **Multi-Variant NPC**: A single sprite definition (`Sprite_Korok`) is used to represent multiple distinct Korok characters (Makar, Hollo, Rown) based on a randomly assigned `SprSubtype`, showcasing efficient resource utilization and varied visual appearances.
* **Randomized Behavior**: The Korok's initial variant and its walking directions are randomized, adding an element of unpredictability and variety to encounters.
* **NPC Interaction**: The Korok can be interacted with through dialogue (`%ShowSolicitedMessage`) and is liftable (`Sprite_CheckIfLifted`), allowing for environmental puzzles or simple interactions.
* **Conditional Drawing**: The drawing routine dispatches to different sub-routines based on the Korok's subtype, allowing for distinct visual appearances for each variant.
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make the NPC a solid object that Link cannot walk through.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,137 @@
# Maku Tree
## Overview
The Maku Tree sprite (`!SPRID = Sprite_MakuTree`) represents a significant NPC in the game, likely serving as a quest giver or a key character in the narrative. Its interactions are primarily dialogue-driven and tied to game progression, culminating in the granting of a Heart Container. Notably, its graphics are not handled by a conventional sprite drawing routine within this file, suggesting it might be a background element or drawn by a separate system.
## Sprite Properties
* **`!SPRID`**: `Sprite_MakuTree` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `00` (Indicates graphics are handled externally or as a background)
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `0`
* **`!Damage`**: `0`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `0`
* **`!Hitbox`**: `$0D`
* **`!Persist`**: `00`
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `0`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_MakuTree_Long`)
This routine primarily checks if the Maku Tree sprite is active and then dispatches to its main logic routine. The absence of a direct drawing call (`JSR Sprite_MakuTree_Draw`) here suggests its visual representation is managed outside of the standard sprite drawing pipeline.
```asm
Sprite_MakuTree_Long:
{
PHB : PHK : PLB
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_MakuTree_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_MakuTree_Prep`)
This routine initializes the Maku Tree upon spawning. It sets `SprDefl, X` and includes logic to potentially play the "Maku Song" by checking a custom progression flag (`OOSPROG2`).
```asm
Sprite_MakuTree_Prep:
{
PHB : PHK : PLB
; Play the Maku Song
LDA.l OOSPROG2 : AND.b #$04 : BEQ +
LDA.b #$03 : STA.w $012C
+
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_MakuTree_Main`)
The Maku Tree's core behavior is managed by a state machine that guides its interaction with Link and quest progression.
* **Player Collision**: Prevents Link from passing through the Maku Tree (`JSL Sprite_PlayerCantPassThrough`).
* **`MakuTree_Handler`**: Checks the `MakuTreeQuest` flag to determine if Link has already met the Maku Tree, transitioning to `MakuTree_MeetLink` or `MakuTree_HasMetLink` accordingly.
* **`MakuTree_MeetLink`**: When Link is close enough, the Maku Tree displays an unconditional message (`%ShowUnconditionalMessage($20)`), updates quest flags (`MakuTreeQuest`, `MapIcon`, `$7EF3D6`), and transitions to `MakuTree_SpawnHeartContainer`.
* **`MakuTree_SpawnHeartContainer`**: Grants Link a Heart Container (`LDY #$3E : JSL Link_ReceiveItem`) and then transitions to `MakuTree_HasMetLink`.
* **`MakuTree_HasMetLink`**: Displays a solicited message (`%ShowSolicitedMessage($22)`) and updates a progression flag (`$7EF3D6`) upon message dismissal.
```asm
Sprite_MakuTree_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw MakuTree_Handler
dw MakuTree_MeetLink
dw MakuTree_SpawnHeartContainer
dw MakuTree_HasMetLink
MakuTree_Handler:
{
; Check the progress flags
LDA.l MakuTreeQuest : AND.b #$01 : BNE .has_met_link
%GotoAction(1)
RTS
.has_met_link
%GotoAction(3)
RTS
}
MakuTree_MeetLink:
{
JSL GetDistance8bit_Long : CMP #$28 : BCS .not_too_close
%ShowUnconditionalMessage($20)
LDA.b #$01 : STA.l MakuTreeQuest
LDA.b #$01 : STA.l MapIcon ; Mushroom Grotto
LDA.l $7EF3D6 : ORA.b #$02 : STA.l $7EF3D6
%GotoAction(2)
.not_too_close
RTS
}
MakuTree_SpawnHeartContainer:
{
; Give Link a heart container
LDY #$3E : JSL Link_ReceiveItem
%GotoAction(3)
RTS
}
MakuTree_HasMetLink:
{
%ShowSolicitedMessage($22) : BCC .no_talk
LDA.l $7EF3D6 : ORA.b #$02 : STA.l $7EF3D6
.no_talk
RTS
}
}
```
## Drawing
There is no `Sprite_MakuTree_Draw` routine defined within this file. This strongly suggests that the Maku Tree's graphical representation is handled as a background element or by a separate drawing system, rather than as a conventional sprite with its own OAM data.
## Design Patterns
* **Background/Static NPC**: The Maku Tree functions as a static NPC whose visual representation is likely integrated into the background, as evidenced by the absence of a dedicated sprite drawing routine.
* **Quest Gating/Progression**: The Maku Tree's interactions are deeply tied to custom quest flags (`MakuTreeQuest`, `OOSPROG2`, `$7EF3D6`), controlling when Link can meet it, engage in dialogue, and receive rewards.
* **Item Granting**: The Maku Tree serves as a source for a significant reward, granting Link a Heart Container upon meeting specific quest conditions.
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make the Maku Tree a solid, impassable object in the game world.
* **Dialogue-Driven Progression**: Dialogue (`%ShowUnconditionalMessage`, `%ShowSolicitedMessage`) is strategically used to advance the quest narrative and provide context to the player's journey.

227
Docs/Sprites/NPCs/Maple.md Normal file
View File

@@ -0,0 +1,227 @@
# Maple
## Overview
The `maple.asm` file defines the behavior for the NPC "Maple," a significant character involved in a branching "Dream" questline. Maple interacts with Link through extensive dialogue, offers explanations about game mechanics, and possesses the unique ability to put Link to sleep, triggering special dream sequences that advance the narrative and potentially grant rewards.
## Main Logic (`MapleHandler`)
This routine orchestrates Maple's complex interactions with Link, managing dialogue, quest progression, and the initiation of dream sequences.
* **Player Collision**: Prevents Link from passing through Maple (`JSL Sprite_PlayerCantPassThrough`).
* **`Maple_Idle`**: Displays a solicited message (`%ShowSolicitedMessage($01B3)`). Upon dismissal, it transitions to `Maple_HandleFirstResponse`. It also includes logic to set a flag (`$7EF351`) and a timer (`$012F`) for a specific event.
* **`Maple_HandleFirstResponse`**: Processes Link's initial dialogue response (`$1CE8`), leading to different branches: `Maple_Idle`, `Maple_ExplainHut`, or `Maple_DreamOrExplain`.
* **`Maple_DreamOrExplain`**: Displays an unconditional message (`%ShowUnconditionalMessage($01B4)`) and, based on Link's response, transitions to `Maple_ExplainPendants`, `Maple_CheckForPendant`, or back to `Maple_Idle`.
* **`Maple_ExplainHut`**: Displays an unconditional message (`%ShowUnconditionalMessage($01B5)`) and returns to `Maple_Idle`.
* **`Maple_ExplainPendants`**: Displays an unconditional message (`%ShowUnconditionalMessage($01B8)`) and returns to `Maple_Idle`.
* **`Maple_CheckForPendant`**: Checks Link's collected Pendants (`Pendants` SRAM flag) and Dreams (`Dreams` SRAM flag) to determine if a new Dream is available. If so, it sets `CurrentDream`, displays a message (`%ShowUnconditionalMessage($01B6)`), and transitions to `Maple_PutLinkToSleep`. Otherwise, it transitions to `Maple_NoNewPendant`.
* **`Maple_NoNewPendant`**: Displays an unconditional message (`%ShowUnconditionalMessage($01B7)`) and returns to `Maple_Idle`.
* **`Maple_PutLinkToSleep`**: Calls `Sprite_PutLinkToSleep` to initiate the sleep sequence and then transitions to `Maple_HandleDreams`.
* **`Maple_HandleDreams`**: After a timer (`SprTimerA, X`), calls `Link_HandleDreams` to process the dream sequence.
```asm
MapleHandler:
{
%PlayAnimation(0,1,16)
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw Maple_Idle
dw Maple_HandleFirstResponse
dw Maple_DreamOrExplain
dw Maple_ExplainHut
dw Maple_ExplainPendants
dw Maple_CheckForPendant
dw Maple_NoNewPendant
dw Maple_PutLinkToSleep
dw Maple_HandleDreams
Maple_Idle:
{
%ShowSolicitedMessage($01B3) : BCC +
INC.w SprAction, X
+
LDA.l $7EF351 : BEQ +
LDA.b #$02 : STA.l $7EF351
LDA.b #$1B : STA.w $012F
+
RTS
}
Maple_HandleFirstResponse:
{
LDA.w $1CE8 : CMP.b #$02 : BNE +
STZ.w SprAction, X
RTS
+
CMP.b #$01 : BNE .next_response
LDA.b #$03 : STA.w SprAction, X
RTS
.next_response
INC.w SprAction, X
RTS
}
Maple_DreamOrExplain:
{
%ShowUnconditionalMessage($01B4)
LDA.w $1CE8 : BEQ .check_for_pendant
CMP.b #$01 : BNE .another_time
LDA.b #$04 : STA.w SprAction, X
RTS
.check_for_pendant
LDA.b #$05 : STA.w SprAction, X
RTS
.another_time
STZ.w SprAction, X
RTS
}
Maple_ExplainHut:
{
%ShowUnconditionalMessage($01B5)
STZ.w SprAction, X
RTS
}
Maple_ExplainPendants:
{
%ShowUnconditionalMessage($01B8)
STZ.w SprAction, X
RTS
}
Maple_CheckForPendant:
{
; Check for pendant
LDA.l Pendants : AND.b #$04 : BNE .courage
LDA.l Pendants : AND.b #$02 : BNE .power
LDA.l Pendants : AND.b #$01 : BNE .wisdom
JMP .none
.courage
LDA.l Dreams : AND.b #$04 : BNE .power
LDA.b #$02 : STA.w CurrentDream : BRA +
.power
LDA.l Dreams : AND.b #$02 : BNE .wisdom
LDA.b #$01 : STA.w CurrentDream : BRA +
.wisdom
LDA.l Dreams : AND.b #$01 : BNE .none
STZ.w CurrentDream
+
%ShowUnconditionalMessage($01B6)
LDA.b #$07 : STA.w SprAction, X
LDA.b #$40 : STA.w SprTimerA, X
RTS
.none
INC.w SprAction, X
RTS
}
Maple_NoNewPendant:
{
%ShowUnconditionalMessage($01B7)
STZ.w SprAction, X
RTS
}
Maple_PutLinkToSleep:
{
JSR Sprite_PutLinkToSleep
INC.w SprAction, X
RTS
}
Maple_HandleDreams:
{
LDA.w SprTimerA, X : BNE +
JSR Link_HandleDreams
+
RTS
}
}
```
## `Sprite_PutLinkToSleep`
This routine initiates a cinematic sleep sequence for Link. It adjusts Link's coordinates, sets his state to sleeping (`$5D = $16`), spawns a blanket ancilla, adjusts Link's OAM coordinates, and applies a blinding white palette filter to transition into a dream sequence.
```asm
Sprite_PutLinkToSleep:
{
PHX
LDA.b $20 : SEC : SBC.b #$14 : STA.b $20
LDA.b $22 : CLC : ADC.b #$18 : STA.b $22
LDA.b #$16 : STA.b $5D ; Set Link to sleeping
LDA.b #$20 : JSL AncillaAdd_Blanket
LDA.b $20 : CLC : ADC.b #$04 : STA.w $0BFA,X
LDA.b $21 : STA.w $0C0E,X
LDA.b $22 : SEC : SBC.b #$08 : STA.w $0C04,X
LDA.b $23 : STA.w $0C18,X
JSL PaletteFilter_StartBlindingWhite
JSL ApplyPaletteFilter
PLX
RTS
}
```
## `Link_HandleDreams`
This routine manages the different dream sequences based on the `CurrentDream` variable. It sets specific bits in the `Dreams` SRAM flag and warps Link to a designated room using `Link_WarpToRoom`.
* **`Dream_Wisdom`**: Sets bit `0` in `Dreams`, warps Link to a room, and sets `$EE` to `$01`.
* **`Dream_Power`**: Sets bit `1` in `Dreams` and warps Link to a room.
* **`Dream_Courage`**: Sets bit `2` in `Dreams` and warps Link to a room.
```asm
Link_HandleDreams:
{
LDA.w CurrentDream
JSL JumpTableLocal
dw Dream_Wisdom
dw Dream_Power
dw Dream_Courage
Dream_Wisdom:
{
LDA.l Dreams : ORA.b #%00000001 : STA.l Dreams
LDX.b #$00
JSR Link_WarpToRoom
LDA.b #$01 : STA.b $EE
RTS
}
Dream_Power:
{
LDA.l Dreams : ORA.b #%00000010 : STA.l Dreams
LDX.b #$01
JSR Link_WarpToRoom
RTS
}
Dream_Courage:
{
LDA.l Dreams : ORA.b #%00000100 : STA.l Dreams
LDX.b #$02
JSR Link_WarpToRoom
RTS
}
}
```
## `Link_WarpToRoom`
This routine sets Link's state for warping, including setting his `LinkState` and room coordinates, and uses a `.room` data table to determine the target room for the warp.
## `Link_FallIntoDungeon`
This routine sets Link's state for falling into a dungeon, including setting the entrance ID and various state flags. It uses an `.entrance` data table to determine the target entrance.
## Vanilla Override
* **`org $068C9C`**: Sets a byte to `$0F`, likely a minor adjustment to vanilla code.
## Design Patterns
* **Complex Dialogue System**: Maple's interactions feature a highly branching and conditional dialogue system, where player choices and game progression influence the conversation flow and subsequent events.
* **Quest Gating/Progression**: Maple is a central figure in a "Dream" questline, with her dialogue and actions dynamically adapting based on Link's collected Pendants and Dreams, guiding the player through a significant narrative arc.
* **Player State Manipulation**: Maple possesses the unique ability to put Link to sleep, which triggers special dream sequences and warps him to different locations, creating immersive and story-rich transitions.
* **Cinematic Sequences**: The `Sprite_PutLinkToSleep` routine orchestrates a cinematic effect, incorporating screen transitions, visual filters, and the spawning of ancillae to enhance the player's experience during dream initiations.
* **Global State Management**: The sprite extensively modifies `Pendants`, `Dreams`, `CurrentDream`, and other global variables to meticulously track and influence quest progress, ensuring a consistent and evolving game world.

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

View File

@@ -0,0 +1,119 @@
# Mermaid / Maple / Librarian
## Overview
The `mermaid.asm` file is a highly versatile sprite definition that implements three distinct NPC characters: the "Mermaid," "Maple," and "Librarian." This multi-purpose sprite leverages `SprSubtype` and `SprMiscE, X` to dispatch to different behaviors and drawing routines, allowing for efficient resource reuse and complex, context-sensitive interactions within the game world.
## Sprite Properties
* **`!SPRID`**: `Sprite_Mermaid` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `02`
* **`!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_Mermaid_Long`)
This routine acts as a central dispatcher, selecting the appropriate drawing routine based on `SprMiscE, X` (0 for Mermaid, 1 for Maple, 2 for Librarian). It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_Mermaid_Long:
{
PHB : PHK : PLB
LDA.w SprMiscE, X : BEQ .MermaidDraw
CMP.b #$02 : BEQ .LibrarianDraw
JSR Sprite_Maple_Draw
JMP .Continue
.LibrarianDraw
JSR Sprite_Librarian_Draw
JMP .Continue
.MermaidDraw
JSR Sprite_Mermaid_Draw
.Continue
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Mermaid_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Mermaid_Prep`)
This routine initializes the sprite upon spawning. It sets `SprDefl, X`, `SprTimerA, X`, and `SprHitbox, X`. Crucially, it sets `SprMiscE, X` based on `SprSubtype, X` to determine which character the sprite will represent (0 for Mermaid, 1 for Maple, 2 for Librarian).
```asm
Sprite_Mermaid_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.b #$40 : STA.w SprTimerA, X
LDA.b #$07 : STA.w SprHitbox, X
; Mermaid Sprite
STZ.w SprMiscE, X
; Maple Sprite
LDA.w SprSubtype, X : CMP.b #$01 : BNE +
LDA.b #$01 : STA.w SprMiscE, X
+
; Librarian Sprite
CMP.b #$02 : BNE ++
LDA.b #$02 : STA.w SprMiscE, X
++
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Mermaid_Main`)
This routine acts as a dispatcher for the main logic, calling the appropriate handler (`MermaidHandler`, `MapleHandler`, or `LibrarianHandler`) based on `SprMiscE, X`.
### `MermaidHandler`
Manages the Mermaid's behavior through a state machine:
* **`MermaidWait`**: Plays an idle animation, prevents player passage, and displays a message on contact. Upon message dismissal, it transitions to `MermaidDive`.
* **`MermaidDive`**: Plays a diving animation, moves horizontally, and transitions to `MermaidSwim` after a timer.
* **`MermaidSwim`**: Plays a swimming animation, moves, sets `SprXSpeed, X`, spawns a splash effect, and can despawn or change direction after a timer.
### `LibrarianHandler`
Manages the Librarian's behavior, primarily focused on a map and scroll translation quest:
* **`LibrarianIdle`**: Plays an animation, prevents player passage, and displays messages based on whether Link has no maps, all maps, or new scrolls. Transitions to `Librarian_CheckResponse` if new scrolls are available.
* **`Librarian_CheckResponse`**: Processes Link's response to the translation offer, transitioning to `Librarian_OfferTranslation` or back to `LibrarianIdle`.
* **`Librarian_OfferTranslation`**: Displays a message, prevents player passage, and checks `Scrolls` and `DNGMAP1`/`DNGMAP2` to identify new scrolls. If found, it updates `Scrolls`, sets `SprMiscG, X` to the scroll ID, and transitions to `Librarian_TranslateScroll`.
* **`Librarian_TranslateScroll`**: Displays a message based on the scroll ID and transitions to `Librarian_FinishTranslation`.
* **`Librarian_FinishTranslation`**: Displays a final message and returns to `LibrarianIdle`.
## `Librarian_CheckForAllMaps` and `Librarian_CheckForNoMaps`
These helper routines check `DNGMAP1` and `DNGMAP2` (SRAM flags for dungeon maps) to determine Link's map collection status.
## Drawing (`Sprite_Mermaid_Draw`, `Sprite_Maple_Draw`, `Sprite_Librarian_Draw`)
Each character 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 character.
## Design Patterns
* **Multi-Character Sprite (Conditional Drawing/Logic)**: A single sprite definition (`Sprite_Mermaid`) is used to represent three distinct NPCs (Mermaid, Maple, Librarian) based on `SprSubtype` and `SprMiscE`, showcasing efficient resource utilization and context-sensitive character representation.
* **Quest Progression Integration**: The Librarian's dialogue and actions are tied to collected dungeon maps and scrolls, indicating its role in a translation quest, driving narrative progression.
* **Context-Sensitive Dialogue**: The Librarian's messages dynamically change based on whether Link has maps, all maps, or new scrolls, providing a personalized and evolving interaction.
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make NPCs solid objects that Link cannot walk through.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,181 @@
# Piratian
## Overview
The Piratian sprite (`!SPRID = $0E`) represents an NPC that initially behaves in a friendly manner, engaging Link through dialogue and moving randomly. However, it possesses an "aggro" system, becoming hostile and attacking Link if provoked. A unique aspect of this sprite is its dynamic health scaling, which adjusts based on Link's current Sword level.
## Sprite Properties
* **`!SPRID`**: `$0E` (Vanilla sprite ID, likely for a generic NPC)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `00` (Initially harmful, but `Sprite_Piratian_Friendly` suggests otherwise)
* **`!HVelocity`**: `00`
* **`!Health`**: `00` (Dynamically set in `_Prep`)
* **`!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_Piratian_Long`)
This routine handles the Piratian's drawing, shadow rendering, and dispatches to its main logic if the sprite is active.
```asm
Sprite_Piratian_Long:
{
PHB : PHK : PLB
JSR Sprite_Piratian_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Piratian_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Piratian_Prep`)
This routine initializes the Piratian upon spawning. Its health (`SprHealth, X`) is dynamically set based on Link's current Sword level (`$7EF359`), providing a form of difficulty scaling. `SprMiscA, X` is initialized to `0` (likely an aggro flag), and a specific bit of `SprNbrOAM, X` is set, potentially for drawing behavior.
```asm
Sprite_Piratian_Prep:
{
PHB : PHK : PLB
LDA.l $7EF359 : TAY
LDA.w .health, Y : STA.w SprHealth, X
STZ.w SprMiscA, X
LDA.w SprNbrOAM, X : ORA.b #$80 : STA.w SprNbrOAM, X
PLB
RTL
.health
db $08, $0A, $0C, $0F
}
```
## Main Logic & State Machine (`Sprite_Piratian_Main`)
This routine orchestrates the Piratian's movement and animation, calling `Sprite_Piratian_Move` and then dispatching to various animation states based on `SprAction, X`.
* **`Piratian_MoveDown` / `Up` / `Left` / `Right`**: Each state plays a specific walking animation.
* **`SkullHead`**: Plays a specific animation, likely when the Piratian is defeated or in a particular state.
```asm
Sprite_Piratian_Main:
{
JSR Sprite_Piratian_Move
LDA.w SprAction, X
JSL JumpTableLocal
dw Piratian_MoveDown
dw Piratian_MoveUp
dw Piratian_MoveLeft
dw Piratian_MoveRight
dw SkullHead
Piratian_MoveDown:
{
%PlayAnimation(0,1,16)
RTS
}
Piratian_MoveUp:
{
%PlayAnimation(2,3,16)
RTS
}
Piratian_MoveLeft:
{
%PlayAnimation(4,5,16)
RTS
}
Piratian_MoveRight:
{
%PlayAnimation(6,7,16)
RTS
}
SkullHead:
{
%PlayAnimation(8,9,16)
RTS
}
}
```
## Movement and Interaction (`Sprite_Piratian_Move`)
This routine handles the Piratian's movement, collision, damage reactions, and implements its "aggro" system.
* **Random Movement**: If `SprTimerA, X` is `0`, the Piratian randomly selects a new direction (`Sprite_SelectNewDirection`) and updates its animation state (`SprAction, X`).
* **Physics & Collision**: Moves the sprite (`JSL Sprite_MoveXyz`), handles tile collision (`JSL Sprite_BounceFromTileCollision`), damage flash (`JSL Sprite_DamageFlash_Long`), and interaction with thrown sprites (`JSL ThrownSprite_TileAndSpriteInteraction_long`).
* **Aggro Logic**: If the Piratian takes damage from Link (`JSL Sprite_CheckDamageFromPlayer`), it sets `SprMiscA, X` to `01` (aggro flag), changes its drawing behavior (`SprNbrOAM, X`), sets timers, and begins to attack Link (`Sprite_ProjectSpeedTowardsPlayer`, `Sprite_CheckDamageToPlayer`).
* **Friendly Behavior**: If not in an aggro state, it calls `Sprite_Piratian_Friendly` for dialogue interaction.
```asm
Sprite_Piratian_Move:
{
LDA.w SprTimerA, X : BNE +
JSL Sprite_SelectNewDirection
TYA
CMP.b #$03 : BCC ++
SEC : SBC.b #$03
++
STA.w SprAction, X
+
JSL Sprite_MoveXyz
JSL Sprite_BounceFromTileCollision
JSL Sprite_DamageFlash_Long
JSL ThrownSprite_TileAndSpriteInteraction_long
JSL Sprite_CheckDamageFromPlayer : BCC .no_dano
LDA.b #$01 : STA.w SprMiscA, X
LDA.w SprNbrOAM, X : AND.b #$7F : STA.w SprNbrOAM, X
%SetTimerA($60)
%SetTimerF($20)
.no_dano
LDA.w SprMiscA, X : BEQ .no_aggro
LDA.b #$10 : STA.w SprTimerA, X
LDA.b #$08
JSL Sprite_ProjectSpeedTowardsPlayer
JSL Sprite_CheckDamageToPlayer
JMP .return
.no_aggro
JSR Sprite_Piratian_Friendly
.return
RTS
}
```
## Friendly Interaction (`Sprite_Piratian_Friendly`)
This routine handles the Piratian's friendly dialogue. If `SprTimerD, X` is `0`, it displays a message on contact (`%ShowMessageOnContact($01BB)`). Upon message dismissal, it sets `SprTimerD, X` to `$FF`.
## Drawing (`Sprite_Piratian_Draw`)
The drawing routine uses the `%DrawSprite()` macro to render the Piratian's graphics based on defined OAM data tables.
## Design Patterns
* **Dynamic Health Scaling**: The Piratian's health is dynamically adjusted based on Link's current Sword level, providing a form of adaptive difficulty.
* **Aggro System**: The Piratian features an "aggro" system where it transitions from a friendly, dialogue-based NPC to a hostile enemy if Link attacks it, adding depth to interactions.
* **Random Movement**: The Piratian moves randomly, contributing to its NPC-like behavior and making its movements less predictable.
* **NPC Interaction**: The Piratian can be interacted with through dialogue when in its friendly state, offering context or hints.
* **Conditional Behavior**: The sprite's behavior changes significantly based on its "friendly" or "aggro" state, demonstrating complex state management.
* **16-bit OAM Calculations**: Although `%DrawSprite()` is used, the underlying drawing routines likely utilize `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,95 @@
# Ranch Girl
## Overview
The `ranch_girl.asm` file defines the behavior for the "Ranch Girl" NPC, who is involved in a "Chicken Easter Egg" quest. This character plays a crucial role in granting Link the Ocarina and teaching him a song. Her behavior is implemented as a vanilla override, modifying the existing `ChickenLady` routine.
## Vanilla Overrides
This file directly modifies the vanilla `ChickenLady` routine at `org $01AFECF` to inject custom logic for the Ranch Girl's interactions.
## `RanchGirl_Message`
This routine handles the dialogue displayed by the Ranch Girl. It checks Link's Ocarina status (`$7EF34C`).
* If Link already possesses the Ocarina, it displays message `$010E`.
* Otherwise, it displays message `$017D` and sets `SprMiscD, X` to `$01`, indicating the start of the Ocarina quest.
```asm
RanchGirl_Message:
{
LDA $7EF34C : CMP.b #$01 : BCS .has_ocarina
%ShowUnconditionalMessage($017D)
LDA #$01 : STA.w SprMiscD, X
RTL
.has_ocarina
%ShowUnconditionalMessage($010E)
RTL
}
```
## `RanchGirl_TeachSong`
This routine is responsible for teaching Link a song (specifically the "Song of Storms") and granting him the Ocarina. It checks the Ocarina quest flag (`SprMiscD, X`) and Link's current Ocarina status (`$7EF34C`).
* If the conditions are met, it plays the "Song of Storms" sound, gives Link the Ocarina (`LDY #$14`, `JSL Link_ReceiveItem`), and sets `$7EF34C` to `$01`.
```asm
RanchGirl_TeachSong:
{
LDA.w SprMiscD, X : CMP.b #$01 : BNE .not_started
LDA $10 : CMP.b #$0E : BEQ .running_dialog
LDA $7EF34C : CMP.b #$01 : BCS .has_song
; Play the song of storms
LDA.b #$2F
STA.w $0CF8
JSL $0DBB67 ; Link_CalculateSFXPan
ORA.w $0CF8
STA $012E ; Play the song learned sound
; Give Link the Ocarina
LDY #$14
; Clear the item receipt ID
STZ $02E9
PHX
JSL Link_ReceiveItem
PLX
LDA #$01 : STA $7EF34C ; The item gives 02 by default, so decrement that for now
.not_started
.running_dialog
.has_song
LDA.b $1A : LSR #4 : AND.b #$01 : STA.w $0DC0,X
RTL
}
```
## `ChickenLady` (Vanilla Override)
This is the main entry point for the Ranch Girl's custom behavior, overriding the vanilla `ChickenLady` routine. It sets `SprMiscC, X` to `$01`, calls vanilla drawing and activity check routines, and then executes `RanchGirl_Message` and `RanchGirl_TeachSong`.
```asm
org $01AFECF
ChickenLady:
{
JSR .main
RTL
.main
LDA.b #$01 : STA.w SprMiscC, X
JSL SpriteDraw_RaceGameLady
JSR Sprite_CheckIfActive_Bank1A
LDA.w SprTimerA, X : CMP.b #$01 : BNE .no_message
JSL RanchGirl_Message
.no_message
JSL RanchGirl_TeachSong
.return
RTS
}
```
## Design Patterns
* **Vanilla Override**: This file directly modifies a vanilla routine (`ChickenLady`) to implement custom NPC behavior, demonstrating a common ROM hacking technique.
* **Quest Gating/Progression**: The Ranch Girl's dialogue and the granting of the Ocarina are tied to Link's possession of the Ocarina and the state of the Ocarina quest, integrating her into the game's progression system.
* **Item Granting**: The Ranch Girl serves as a source for the Ocarina, a key item in the game.
* **Game State Manipulation**: Directly modifies `$7EF34C` (Ocarina flag) and `SprMiscD, X` (quest flag) to track and influence game state.

250
Docs/Sprites/NPCs/Tingle.md Normal file
View File

@@ -0,0 +1,250 @@
# Tingle
## Overview
The Tingle sprite (`!SPRID = Sprite_Tingle`) implements the iconic map salesman character. Tingle's primary function is to sell a map to Link, with his interactions and dialogue flow being conditional on Link's rupee count and whether the map has already been purchased.
## Sprite Properties
* **`!SPRID`**: `Sprite_Tingle` (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`**: `00`
* **`!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_Tingle_Long`)
This routine handles Tingle's drawing, shadow rendering, and dispatches to his main logic if the sprite is active.
```asm
Sprite_Tingle_Long:
{
PHB : PHK : PLB
JSR Sprite_Tingle_Draw
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Tingle_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Tingle_Prep`)
This routine is empty, indicating that Tingle requires no custom initialization upon spawning.
## Main Logic & State Machine (`Sprite_Tingle_Main`)
Tingle's core behavior is managed by a state machine that facilitates a dialogue and transaction flow for selling a map.
* **Player Collision**: Prevents Link from passing through Tingle (`JSL Sprite_PlayerCantPassThrough`).
* **`Tingle_Idle`**: Plays an animation. Checks if Link has already bought the map (`$7EF3D6` bit `$01`). If so, it transitions to `Tingle_AlreadyBoughtMap`. Otherwise, it displays a solicited message (`%ShowSolicitedMessage($01A4)`) asking if Link wants to buy a map. Based on Link's response (`$1CE8`), it transitions to `Tingle_BuyMap` or `Tingle_PlayerSaidNo`.
* **`Tingle_BuyMap`**: Plays an animation. Checks if Link has enough rupees (`$7EF360`). If sufficient, it deducts the rupees, sets the map bought flag (`$7EF3D6` bit `$01`), displays a confirmation message (`%ShowUnconditionalMessage($01A5)`), and transitions to `Tingle_AlreadyBoughtMap`. If rupees are insufficient, it transitions to `Tingle_NotEnoughMoney`.
* **`Tingle_PlayerSaidNo`**: Plays an animation, displays a message (`%ShowUnconditionalMessage($01A6)`), and returns to `Tingle_Idle`.
* **`Tingle_AlreadyBoughtMap`**: Plays an animation, displays a message (`%ShowUnconditionalMessage($01A3)`) confirming the map has been bought, and returns to `Tingle_Idle`.
* **`Tingle_NotEnoughMoney`**: Plays an animation, displays a message (`%ShowUnconditionalMessage($029)`) about insufficient funds, and returns to `Tingle_Idle`.
```asm
Sprite_Tingle_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw Tingle_Idle
dw Tingle_BuyMap
dw Tingle_PlayerSaidNo
dw Tingle_AlreadyBoughtMap
dw Tingle_NotEnoughMoney
; 0x00
Tingle_Idle:
{
%PlayAnimation(0, 1, 16)
; Player has already bought the map
LDA.l $7EF3D6 : AND.b #$01 : BNE .already_bought_map
%ShowSolicitedMessage($01A4) : BCC .didnt_converse
LDA $1CE8 : BEQ .buy_map
; Player said no
%GotoAction(2)
RTS
.buy_map
%GotoAction(1)
RTS
.already_bought_map
%GotoAction(3)
RTS
.didnt_converse
RTS
}
; 0x01
Tingle_BuyMap:
{
%PlayAnimation(0, 1, 16)
REP #$20
LDA.l $7EF360 : CMP.w #$0064 ; 100 rupees
SEP #$30
BCC .not_enough_money
; Deduct rupees
REP #$20
LDA.l $7EF360
SEC : SBC.w #$0064
STA.l $7EF360
SEP #$30
; Set map bought flag
LDA.l $7EF3D6 : ORA.b #$01 : STA.l $7EF3D6
%ShowUnconditionalMessage($01A5)
%GotoAction(3)
RTS
.not_enough_money
%GotoAction(4)
RTS
}
; 0x02
Tingle_PlayerSaidNo:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($01A6)
%GotoAction(0)
RTS
}
; 0x03
Tingle_AlreadyBoughtMap:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($01A3)
%GotoAction(0)
RTS
}
; 0x04
Tingle_NotEnoughMoney:
{
%PlayAnimation(0, 1, 16)
%ShowUnconditionalMessage($029)
%GotoAction(0)
RTS
}
}
```
## Drawing (`Sprite_Tingle_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_Tingle_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
* **Shop System**: Tingle implements a basic shop system for selling a map, including price checks and rupee deduction, providing a functional in-game vendor.
* **Quest Gating/Progression**: The availability of the map and Tingle's dialogue are conditional on whether Link has already purchased the map (`$7EF3D6` bit `$01`), ensuring a logical progression of events.
* **Conditional Transactions**: The process of buying the map involves checking Link's rupee count and deducting the cost upon a successful purchase, simulating a real in-game economy.
* **Player Choice and Branching Dialogue**: Link's responses (`$1CE8`) to Tingle's inquiries directly influence the flow of conversation and the available options, leading to a personalized interaction.
* **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.

210
Docs/Sprites/NPCs/Vasu.md Normal file
View File

@@ -0,0 +1,210 @@
# Vasu / Error
## Overview
The `vasu.asm` file defines the behavior for a multi-character NPC sprite that can represent either "Vasu," the ring appraiser, or a special "Error" sprite. The specific character displayed and its interactions are determined by `SprSubtype, X`. Vasu offers a service to appraise rings, with conditional dialogue and transactions based on Link's inventory and rupee count.
## Sprite Properties
* **`!SPRID`**: `Sprite_Vasu` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `03`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!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`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_Vasu_Long`)
This routine acts as a dispatcher for drawing, selecting `Sprite_Vasu_Draw` for Vasu (`SprSubtype, X = 0`) or `Sprite_Error_Draw` for the Error sprite. It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_Vasu_Long:
{
PHB : PHK : PLB
LDA.w SprSubtype, X : BNE +
JSR Sprite_Vasu_Draw
JMP ++
+
JSR Sprite_Error_Draw
++
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Vasu_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Vasu_Prep`)
This routine initializes the sprite upon spawning. It sets `SprDefl, X` to `$80`. If the sprite is Vasu (`SprSubtype, X = 0`), it sets `SprAction, X` to `$04`.
```asm
Sprite_Vasu_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
LDA.w SprSubtype, X : BEQ +
LDA.b #$04 : STA.w SprAction, X
+
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Vasu_Main`)
This routine manages the various states and interactions for both Vasu and the Error sprite.
* **Player Collision**: Prevents Link from passing through (`JSL Sprite_PlayerCantPassThrough`).
* **`Vasu_Idle`**: Plays an animation and displays a solicited message (`%ShowSolicitedMessage($00A9)`). Upon message dismissal, it transitions to `Vasu_MessageHandler`.
* **`Vasu_MessageHandler`**: Plays an animation and processes Link's choice (`MsgChoice`). It can lead to `Vasu_AppraiseRing`, `Vasu_ExplainRings` (displays message `$00AA` and returns to `Vasu_Idle`), or return to `Vasu_Idle` if Link chooses "nevermind."
* **`Vasu_AppraiseRing`**: Plays an animation. Checks `FOUNDRINGS` (SRAM flag for found rings). If no rings are found, it displays a message (`%ShowUnconditionalMessage($00AD)`) and returns to `Vasu_Idle`. If rings are found, it checks `MAGICRINGS` (SRAM flag for owned rings). If Link has no rings yet, it offers the first appraisal for free (`%ShowUnconditionalMessage($00AB)`). Otherwise, it checks for 20 rupees (`$7EF360`). If Link has enough, it deducts the rupees, updates `MAGICRINGS` by ORing with `FOUNDRINGS`, and transitions to `Vasu_RingAppraised`. If not enough rupees, it displays a message (`%ShowUnconditionalMessage($0189)`) and returns to `Vasu_Idle`.
* **`Vasu_RingAppraised`**: Plays an animation, displays a message (`%ShowUnconditionalMessage($00AC)`), and returns to `Vasu_Idle`.
* **`Error_Idle`**: Plays an animation and displays a solicited message (`%ShowSolicitedMessage($0121)`) "I am Error." Upon message dismissal, it randomly sets `FOUNDRINGS`.
```asm
Sprite_Vasu_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw Vasu_Idle
dw Vasu_MessageHandler
dw Vasu_AppraiseRing
dw Vasu_RingAppraised
dw Error_Idle
Vasu_Idle:
{
%PlayAnimation(0,1,20)
%ShowSolicitedMessage($00A9) : BCC .didnt_talk
%GotoAction(1)
.didnt_talk
RTS
}
Vasu_MessageHandler:
{
%PlayAnimation(0,1,20)
LDA.w MsgChoice : BEQ .appraise_rings
CMP.b #$01 : BEQ .explain_rings
; Player said nevermind.
%GotoAction(0)
RTS
.explain_rings
%ShowUnconditionalMessage($00AA)
%GotoAction(0)
RTS
.appraise_rings
LDA.b #$40 : STA.w SprTimerB, X
%GotoAction(2)
RTS
}
Vasu_AppraiseRing:
{
%PlayAnimation(0,1,20)
; Check if the player has found any rings to appraise
REP #$30
LDA.l FOUNDRINGS
AND.w #$00FF
SEP #$30
BEQ .no_rings
; Check if the player has any rings, if not give them one for free
LDA.l MAGICRINGS : BEQ .no_rings_yet
REP #$20
LDA.l $7EF360
CMP.w #$14 ; 20 rupees
SEP #$30
BCC .not_enough_rupees
REP #$20
LDA.l $7EF360
SEC
SBC.w #$14 ; Subtract 20 rupees
STA.l $7EF360
SEP #$30
JMP .appraise_me
.not_enough_rupees
%ShowUnconditionalMessage($0189) ; 'You don't have enough rupees!'
%GotoAction(0)
RTS
.no_rings_yet
%ShowUnconditionalMessage($00AB) ; 'First one is free!'
JMP .appraise_me
.no_rings
%ShowUnconditionalMessage($00AD) ; 'You don't have any rings!'
%GotoAction(0)
RTS
.appraise_me
; Check the found rings and set the saved rings
; Get the bit from found rings and set it in MAGICRINGS
LDA.l FOUNDRINGS
ORA.l MAGICRINGS
STA.l MAGICRINGS
%GotoAction(3)
RTS
}
Vasu_RingAppraised:
{
%PlayAnimation(0,1,20)
%ShowUnconditionalMessage($00AC) ; 'Come back later for more appraisals!'
%GotoAction(0)
RTS
}
Error_Idle:
{
%PlayAnimation(0,1,24)
; "I am Error"
%ShowSolicitedMessage($0121) : BCC +
JSL GetRandomInt : AND.b #$06 : STA.l FOUNDRINGS
+
RTS
}
}
```
## Drawing (`Sprite_Vasu_Draw` and `Sprite_Error_Draw`)
Both drawing routines handle OAM allocation and animation for their respective characters. They explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each routine contains its own specific OAM data for rendering the character.
## Design Patterns
* **Multi-Character Sprite (Conditional Drawing/Logic)**: A single sprite definition (`Sprite_Vasu`) is used to represent two distinct characters (Vasu and the "Error" sprite) based on `SprSubtype`, showcasing efficient resource utilization and varied visual appearances.
* **Shop/Service System**: Vasu implements a service where he appraises rings for a fee (or for free the first time), integrating a transactional element into NPC interactions.
* **Quest Gating/Progression**: Vasu's interactions are conditional on Link having found rings (`FOUNDRINGS`) and his rupee count, ensuring that the appraisal service is available only when relevant.
* **Conditional Transactions**: The appraisal process involves checking Link's rupee count and deducting the cost, simulating a real in-game economy.
* **Player Choice and Branching Dialogue**: Link's choices (`MsgChoice`) directly influence the flow of conversation, leading to different outcomes and information from Vasu.
* **Item Management**: Vasu interacts with `FOUNDRINGS` and `MAGICRINGS` (SRAM flags) to manage Link's ring collection, updating his inventory based on appraisals.
* **Easter Egg/Hidden Content**: The "Error" sprite, with its unique dialogue, likely serves as an Easter egg or a placeholder for debugging, adding a touch of humor or mystery.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,263 @@
# Village Dog / Eon Dog
## Overview
The `village_dog.asm` file defines the behavior for two distinct dog NPCs: the "Village Dog" and the "Eon Dog." Their appearance and behavior are dynamically determined by the global `WORLDFLAG`. These dogs exhibit random movement, react to Link's proximity, can be lifted and thrown, and offer context-sensitive dialogue based on Link's current form.
## Sprite Properties
* **`!SPRID`**: `Sprite_VillageDog` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `08`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `01`
* **`!Palette`**: `00`
* **`!Hitbox`**: `09`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `01` (Checks both layers for collision)
* **`!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_VillageDog_Long`)
This routine acts as a dispatcher for drawing, selecting `Sprite_VillageDog_Draw` for the Village Dog (`WORLDFLAG = 0`) or `Sprite_EonDog_Draw` for the Eon Dog. It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_VillageDog_Long:
{
PHB : PHK : PLB
LDA.w WORLDFLAG : BEQ .village
JSR Sprite_EonDog_Draw
JMP +
.village
JSR Sprite_VillageDog_Draw
+
JSL Sprite_DrawShadow
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_VillageDog_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_VillageDog_Prep`)
This routine initializes the dog upon spawning. If it's an Eon Dog (`WORLDFLAG` is not `0`), it sets `SprAction, X` to `$07` and `SprTimerA, X` to `$40`.
```asm
Sprite_VillageDog_Prep:
{
PHB : PHK : PLB
LDA.w WORLDFLAG : BEQ .village
LDA.b #$07 : STA.w SprAction, X
LDA.b #$40 : STA.w SprTimerA, X
.village
PLB
RTL
}
```
## `HandleTossedDog`
This routine manages the vertical movement of a dog that has been tossed. If `SprHeight, X` is not `0`, it decrements it, simulating gravity.
## `LiftOrTalk`
This routine determines whether Link can lift the dog or engage in dialogue. It checks Link's current form (`$02B2`). If Link is in Wolf or Minish form, it calls `ShowMessageIfMinish`. Otherwise, it checks if the dog is lifted (`JSL Sprite_CheckIfLifted`) and handles interactions with thrown sprites (`JSL ThrownSprite_TileAndSpriteInteraction_long`).
## Main Logic & State Machine (`Sprite_VillageDog_Main`)
This routine manages the various states and behaviors of both Village Dogs and Eon Dogs.
* **`Dog_Handler`**: Plays a sitting animation, calls `HandleTossedDog`, and if Link is nearby, sets a timer and transitions to `Dog_LookLeftAtLink` or `Dog_LookRightAtLink`. It also calls `LiftOrTalk`.
* **`Dog_LookLeftAtLink` / `Dog_LookRightAtLink`**: Plays an animation of the dog looking towards Link. After a timer, it transitions to `Dog_MoveLeftTowardsLink` or `Dog_MoveRightTowardsLink`.
* **`Dog_MoveLeftTowardsLink` / `Dog_MoveRightTowardsLink`**: Plays a walking animation, calls `HandleTossedDog`, and if Link is nearby, transitions to `Dog_WagTailLeft` or `Dog_WagTailRight`. It handles tile collisions, applies speed towards Link, and calls `LiftOrTalk`. After a timer, it returns to `Dog_Handler`.
* **`Dog_WagTailLeft` / `Dog_WagTailRight`**: Plays a wagging tail animation, calls `LiftOrTalk` and `HandleTossedDog`. After a timer, it returns to `Dog_Handler`.
* **`EonDog_Handler` / `EonDog_Right`**: These states are specific to the Eon Dog, playing animations and calling `EonDog_Walk`.
```asm
Sprite_VillageDog_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw Dog_Handler ; 00
dw Dog_LookLeftAtLink ; 01
dw Dog_LookRightAtLink ; 02
dw Dog_MoveLeftTowardsLink ; 03
dw Dog_MoveRightTowardsLink ; 04
dw Dog_WagTailLeft ; 05
dw Dog_WagTailRight ; 06
dw EonDog_Handler ; 07
dw EonDog_Right ; 08
; 0
Dog_Handler:
{
%PlayAnimation(8,8,8) ; Sitting
JSR HandleTossedDog
LDA $0309 : AND #$03 : BNE .lifting
LDA #$20 : STA.w SprTimerD, X
JSL Sprite_IsToRightOfPlayer : TYA : BEQ .walk_right
%GotoAction(1)
JMP .lifting
.walk_right
%GotoAction(2)
.lifting
JSR LiftOrTalk
JSL Sprite_Move
RTS
}
; 01
Dog_LookLeftAtLink:
{
%PlayAnimation(9,9,8)
JSR HandleTossedDog
LDA.w SprTimerD, X : BNE +
; Load the timer for the run
LDA.b #$60 : STA.w SprTimerD, X
%GotoAction(3)
+
RTS
}
; 02
Dog_LookRightAtLink:
{
%PlayAnimation(10,10,8)
JSR HandleTossedDog
LDA.w SprTimerD, X : BNE +
; Load the timer for the run
LDA.b #$60 : STA.w SprTimerD, X
%GotoAction(4)
+
RTS
}
; 03
Dog_MoveLeftTowardsLink:
{
%PlayAnimation(2,4,6)
JSR HandleTossedDog
; Check if the dog is near link, then wag the tail
JSR CheckIfPlayerIsNearby : BCC +
%GotoAction(5)
+
; Check for collision
JSL Sprite_CheckTileCollision
LDA $0E70, X : BEQ .no_collision
%GotoAction(0)
.no_collision
LDA.b #$0A
JSL Sprite_ApplySpeedTowardsPlayer
STZ $06 : STZ $07
JSL Sprite_MoveLong
JSR LiftOrTalk
LDA.w SprTimerD, X : BNE +
%GotoAction(0)
+
RTS
}
; 04
Dog_MoveRightTowardsLink:
{
%PlayAnimation(5,7,6)
JSR HandleTossedDog
JSR CheckIfPlayerIsNearby : BCC +
%GotoAction(6)
+
; Check for collision
JSL Sprite_CheckTileCollision
LDA $0E70, X : BEQ .no_collision
%GotoAction(0)
.no_collision
LDA.b #$0A
JSL Sprite_ApplySpeedTowardsPlayer
STZ $06 : STZ $07
JSL Sprite_MoveLong
JSR LiftOrTalk
LDA.w SprTimerD, X : BNE ++
%GotoAction(0)
++
RTS
}
; 05
Dog_WagTailLeft:
{
%PlayAnimation(0,1, 8)
JSR LiftOrTalk
JSR HandleTossedDog
LDA.w SprTimerD, X : BNE +
%GotoAction(0)
+
RTS
}
; 06
Dog_WagTailRight:
{
%PlayAnimation(11,12,8)
JSR LiftOrTalk
JSR HandleTossedDog
LDA.w SprTimerD, X : BNE +
%GotoAction(0)
+
RTS
}
EonDog_Handler:
{
%PlayAnimation(0,1,8)
JSR EonDog_Walk
RTS
}
EonDog_Right:
{
%PlayAnimation(2,3,8)
JSR EonDog_Walk
RTS
}
}
```
## `EonDog_Walk`
This routine handles the Eon Dog's random movement. It moves the sprite (`JSL Sprite_MoveLong`), handles tile collision (`JSL Sprite_BounceFromTileCollision`), and after a timer, randomly selects a new direction and updates its speed and action.
## `CheckIfPlayerIsNearby`
This routine checks if Link is within a specific rectangular area around the dog, returning with the carry flag set if true.
## `ShowMessageIfMinish`
This routine displays a context-sensitive message based on Link's current form (`$02B2`). If Link is in Minish form, it displays message `$18`; otherwise, it displays message `$1B`.
## Drawing (`Sprite_VillageDog_Draw` and `Sprite_EonDog_Draw`)
Both drawing routines handle OAM allocation and animation for their respective dog types. They explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each routine contains its own specific OAM data for rendering the character.
## Design Patterns
* **Multi-Character NPC (Conditional Drawing/Logic)**: A single sprite definition (`Sprite_VillageDog`) is used to represent two distinct dog characters (Village Dog and Eon Dog) based on `WORLDFLAG`, showcasing efficient resource utilization and varied visual appearances.
* **Random Movement**: The dogs exhibit random movement patterns, contributing to the environmental ambiance and making their movements less predictable.
* **Player Interaction**: The dogs can be lifted and thrown (`LiftOrTalk`), and they react to Link's presence by looking at him and wagging their tails, adding to the interactive elements of the game world.
* **Conditional Dialogue**: The `ShowMessageIfMinish` routine provides context-sensitive dialogue based on Link's current form, enhancing the narrative and player experience.
* **Player Collision**: Implements `Sprite_PlayerCantPassThrough` to make the dogs solid objects that Link cannot walk through.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,38 @@
# Village Elder
## Overview
The `village_elder.asm` file defines the behavior for the "Village Elder" NPC. This is a straightforward NPC whose primary function is to interact with Link through dialogue. The content of the dialogue is conditional, changing based on whether Link has previously met the elder, as tracked by a custom progression flag (`OOSPROG`).
## Main Logic (`Sprite_VillageElder_Main`)
This routine manages the Village Elder's interactions and dialogue flow:
* **Animation**: Plays a specific animation (`%PlayAnimation(2,3,16)`).
* **Player Collision**: Prevents Link from passing through the elder (`JSL Sprite_PlayerCantPassThrough`).
* **Progression Check (`OOSPROG`)**: It checks the `OOSPROG` (Oracle of Secrets Progression) flag. Specifically, it checks if bit `$10` is set, which indicates that Link has already met the elder.
* **First Meeting**: If Link has not yet met the elder, it displays a solicited message (`%ShowSolicitedMessage($143)`). Upon dismissal of this message, it sets bit `$10` in `OOSPROG` to mark that Link has now met the elder.
* **Subsequent Meetings**: If Link has already met the elder, a different solicited message (`%ShowSolicitedMessage($019)`) is displayed.
```asm
Sprite_VillageElder_Main:
{
%PlayAnimation(2,3,16)
JSL Sprite_PlayerCantPassThrough
REP #$30
LDA.l OOSPROG : AND.w #$00FF
SEP #$30
AND.b #$10 : BNE .already_met
%ShowSolicitedMessage($143) : BCC .no_message
LDA.l OOSPROG : ORA.b #$10 : STA.l OOSPROG
.no_message
RTS
.already_met
%ShowSolicitedMessage($019)
RTS
}
```
## Design Patterns
* **Simple NPC Interaction**: The Village Elder provides basic dialogue interaction with Link.
* **Quest Progression Tracking**: Utilizes a custom progression flag (`OOSPROG`) to track whether Link has met the elder, allowing for dynamic changes in dialogue based on game state.
* **Player Collision**: Implements `JSL Sprite_PlayerCantPassThrough` to make the elder a solid object that Link cannot walk through.

218
Docs/Sprites/NPCs/Zora.md Normal file
View File

@@ -0,0 +1,218 @@
# Zora (Generic Sea Zora Handler)
## Overview
The `zora.asm` file serves as a centralized handler for various Zora NPCs within the game. It acts as a dispatcher, directing execution to specific drawing and main logic routines for the Zora Princess, Eon Zora, Eon Zora Elder, and a generic Sea Zora. This dynamic dispatch is based on the current `ROOM`, `WORLDFLAG`, and `SprSubtype`, allowing for a single sprite definition to manage a diverse cast of Zora characters.
## Main Structure (`Sprite_Zora_Long`)
This routine is a complex dispatcher that determines which Zora variant to draw and process based on several game state variables:
* **Zora Princess**: If the current `ROOM` is `$0105`, it calls `Sprite_ZoraPrincess_Draw` and sets `SprMiscG, X` to `$01`.
* **Eon Zora**: If `WORLDFLAG` is not `0`, it calls `Sprite_EonZora_Draw`, `Sprite_DrawShadow`, and sets `SprMiscG, X` to `$02`.
* **Eon Zora Elder**: If `SprSubtype, X` is not `0` (and not the Princess or Eon Zora), it calls `Sprite_EonZoraElder_Draw` and sets `SprMiscG, X` to `$03`.
* **Generic Sea Zora**: Otherwise, it calls `Sprite_Zora_Draw`, `Sprite_DrawShadow`, and sets `SprMiscG, X` to `0`.
* After drawing, it calls `Sprite_CheckActive` and then `Sprite_Zora_Handler` if the sprite is active.
```asm
Sprite_Zora_Long:
{
PHB : PHK : PLB
; Check what Zora we are drawing
REP #$30
LDA.w ROOM : CMP.w #$0105 : BNE .not_princess
SEP #$30
JSR Sprite_ZoraPrincess_Draw
LDA.b #$01 : STA.w SprMiscG, X
JMP +
.not_princess
LDA.w WORLDFLAG : AND.w #$00FF : BEQ .eon_draw
SEP #$30
JSR Sprite_EonZora_Draw
JSL Sprite_DrawShadow
LDA.b #$02 : STA.w SprMiscG, X
JMP +
.eon_draw
SEP #$30
LDA.w SprSubtype, X : BNE .special_zora
JSR Sprite_Zora_Draw
JSL Sprite_DrawShadow
STZ.w SprMiscG, X
JMP +
.special_zora
JSR Sprite_EonZoraElder_Draw
LDA.b #$03 : STA.w SprMiscG, X
+
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Zora_Handler
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Zora_Prep`)
This routine is empty, indicating that custom initialization for the Zora handler is minimal or handled by the individual Zora sprite routines.
## Main Logic Dispatcher (`Sprite_Zora_Handler`)
This routine dispatches to the appropriate main logic routine for the specific Zora variant based on the value of `SprMiscG, X`:
* `$01`: Calls `Sprite_ZoraPrincess_Main`
* `$00`: Calls `Sprite_Zora_Main` (Generic Sea Zora)
* `$02`: Calls `Sprite_EonZora_Main`
* `$03`: Calls `Sprite_EonZoraElder_Main`
```asm
Sprite_Zora_Handler:
{
LDA.w SprMiscG, X
CMP.b #$01 : BNE .not_princess
JSR Sprite_ZoraPrincess_Main
RTS
.not_princess
JSL JumpTableLocal
dw Sprite_Zora_Main
dw Sprite_ZoraPrincess_Main
dw Sprite_EonZora_Main
dw Sprite_EonZoraElder_Main
}
```
## `Sprite_Zora_Main` (Generic Sea Zora)
This routine defines the behavior for a generic Sea Zora NPC.
* **Head Tracking**: Calls `Zora_TrackHeadToPlayer` to make the Zora face Link.
* **Player Collision**: Prevents Link from passing through the Zora (`JSL Sprite_PlayerCantPassThrough`).
* **Dialogue**: Calls `Zora_HandleDialogue` for context-sensitive dialogue interactions.
* **Animation**: Uses a jump table for animation states: `Zora_Forward`, `Zora_Right`, `Zora_Left`, each playing a specific animation.
```asm
Sprite_Zora_Main:
{
JSR Zora_TrackHeadToPlayer
JSL Sprite_PlayerCantPassThrough
JSR Zora_HandleDialogue
LDA.w SprAction, X
JSL JumpTableLocal
dw Zora_Forward
dw Zora_Right
dw Zora_Left
Zora_Forward:
{
%PlayAnimation(0,0,10)
RTS
}
Zora_Right:
{
%PlayAnimation(1,1,10)
RTS
}
Zora_Left:
{
%PlayAnimation(1,1,10)
RTS
}
}
```
## `Zora_TrackHeadToPlayer`
This routine makes the Zora face Link by setting `SprAction, X` to `0` (forward) or `1` (right/left) based on Link's horizontal position relative to the Zora.
## `Zora_HandleDialogue`
This routine handles context-sensitive dialogue for the generic Sea Zora. It checks the `Crystals` SRAM flag (specifically bit `$20`) to determine if a certain crystal has been collected. Based on this and the Zora's `SprAction, X`, it displays different solicited messages (`$01A6`, `$01A5`, or `$01A4`).
## Drawing (`Sprite_Zora_Draw`)
This routine handles OAM allocation and animation for the generic Sea Zora. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
```asm
Sprite_Zora_Draw:
{
JSL Sprite_PrepOamCoord
JSL Sprite_OAM_AllocateDeferToPlayer
LDA.w SprFrame, 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, $02, $04
.nbr_of_tiles
db 1, 1, 1
.x_offsets
dw 0, 0
dw 0, 0
dw 0, 0
.y_offsets
dw -8, 0
dw -8, 0
dw -8, 0
.chr
db $DE, $EE
db $DC, $EC
db $DC, $EC
.properties
db $35, $35
db $35, $35
db $75, $75
.sizes
db $02, $02
db $02, $02
db $02, $02
}
```
## Design Patterns
* **Centralized NPC Handler**: This file acts as a central dispatcher for multiple Zora-type NPCs (Zora Princess, Eon Zora, Eon Zora Elder, and generic Sea Zora), demonstrating efficient management of diverse character behaviors from a single entry point.
* **Multi-Character Sprite (Conditional Drawing/Logic)**: A single sprite ID is used to represent various Zora characters, with their specific drawing and main logic routines dynamically selected based on game state variables like `ROOM`, `WORLDFLAG`, and `SprSubtype`.
* **Context-Sensitive Dialogue**: The generic Sea Zora's dialogue changes based on collected crystals and its current `SprAction`, providing dynamic and responsive interactions with the player.
* **Player Collision**: Implements `JSL Sprite_PlayerCantPassThrough` to make the Zora NPCs solid objects that Link cannot walk through.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,219 @@
# Zora Princess
## Overview
The Zora Princess sprite (`!SPRID = Sprite_ZoraPrincess`) is a key NPC involved in a quest that culminates in Link receiving the Zora Mask. Her interactions are conditional, primarily triggered by Link playing the "Song of Healing," and her presence is tied to whether Link has already obtained the Zora Mask.
## Sprite Properties
* **`!SPRID`**: `Sprite_ZoraPrincess` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `9`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!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_ZoraPrincess_Long`)
This routine handles the Zora Princess's drawing and dispatches to her main logic if the sprite is active.
```asm
Sprite_ZoraPrincess_Long:
{
PHB : PHK : PLB
JSR Sprite_ZoraPrincess_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_ZoraPrincess_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_ZoraPrincess_Prep`)
This routine initializes the Zora Princess upon spawning. It checks the Zora Mask flag (`$7EF302`). If Link already possesses the Zora Mask, the sprite immediately despawns (`STZ.w SprState, X`), ensuring the quest is a one-time event. It also sets `SprDefl, X` and `SprTileDie, X` to `0`.
```asm
Sprite_ZoraPrincess_Prep:
{
PHB : PHK : PLB
LDA.l $7EF302 : BEQ .doesnt_have_mask
STZ.w SprState, X ; Kill the sprite
.doesnt_have_mask
LDA #$00 : STA.w SprDefl, X
LDA #$00 : STA.w SprTileDie, X
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_ZoraPrincess_Main`)
This routine manages the Zora Princess's interactions and the process of granting the Zora Mask.
* **Player Collision**: Prevents Link from passing through the Zora Princess (`JSL Sprite_PlayerCantPassThrough`).
* **`WaitForLink`**: Plays an animation and displays a solicited message (`%ShowSolicitedMessage($0C5)`). Upon message dismissal, it transitions to `CheckForSongOfHealing`.
* **`CheckForSongOfHealing`**: Plays an animation and checks the `SongFlag`. If the "Song of Healing" has been played, it clears the `SongFlag`, sets a timer (`SprTimerD, X`), and transitions to `ThanksMessage`.
* **`ThanksMessage`**: Plays an animation. After a timer (`SprTimerD, X`), it displays an unconditional message (`%ShowUnconditionalMessage($0C6)`) and transitions to `GiveZoraMask`.
* **`GiveZoraMask`**: After a timer (`SprTimerD, X`), it grants Link the Zora Mask (`LDY #$0F`, `JSL Link_ReceiveItem`), sets the Zora Mask obtained flag (`$7EF302` to `$01`), and despawns the sprite (`STZ.w SprState, X`).
```asm
Sprite_ZoraPrincess_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL JumpTableLocal
dw WaitForLink
dw CheckForSongOfHealing
dw ThanksMessage
dw GiveZoraMask
WaitForLink:
{
%PlayAnimation(0, 1, 10)
%ShowSolicitedMessage($0C5) : BCC .no_hablaba
%GotoAction(1)
.no_hablaba
RTS
}
CheckForSongOfHealing:
{
%PlayAnimation(0, 1, 10)
LDA.b SongFlag : BEQ .ninguna_cancion
STZ.b SongFlag
LDA.b #$C0 : STA.w SprTimerD, X
%GotoAction(2)
.ninguna_cancion
RTS
}
ThanksMessage:
{
%PlayAnimation(0, 1, 10)
LDA.w SprTimerD, X : BNE +
%ShowUnconditionalMessage($0C6)
LDA.b #$C0 : STA.w SprTimerD, X
%GotoAction(3)
+
RTS
}
GiveZoraMask:
{
LDA.w SprTimerD, X : BNE +
LDY #$0F : STZ $02E9 ; Give the Zora Mask
JSL Link_ReceiveItem
LDA #$01 : STA.l $7EF302
LDA.b #$00 : STA.w SprState, X
+
RTS
}
}
```
## Drawing (`Sprite_ZoraPrincess_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_ZoraPrincess_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, 11
.x_offsets
dw -4, 4, -4, 4
dw 4, 4, 4, 4, -4, -4, -4, -4, 12, 12, 12, 12
.y_offsets
dw -8, -8, 8, 8
dw -8, 0, 8, 16, -8, 0, 8, 16, -8, 0, 8, 16
.chr
db $C0, $C1, $E0, $E1
db $C1, $D1, $E1, $F1, $C3, $D3, $E3, $F3, $C3, $D3, $E3, $F3
.properties
db $33, $33, $33, $33
db $33, $33, $33, $33, $33, $33, $33, $33, $73, $73, $73, $73
.sizes
db $02, $02, $02, $02
db $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00, $00
}
```
## Design Patterns
* **Quest Gating/Progression**: The Zora Princess's appearance and the granting of the Zora Mask are conditional on Link not already possessing the mask and playing the "Song of Healing," integrating her into a specific questline.
* **NPC Interaction**: The Zora Princess interacts with the player through dialogue and grants a key item (the Zora Mask), which is essential for progression.
* **Conditional Spawning/Despawning**: The sprite dynamically despawns if Link has already obtained the Zora Mask, ensuring that the quest is a one-time event and preventing redundant interactions.
* **Item Granting**: The Zora Princess serves as the source for the Zora Mask, a significant item that likely grants new abilities or access to new areas.
* **Player Collision**: Implements `JSL Sprite_PlayerCantPassThrough` to make the Zora Princess a solid object that Link cannot walk through.
* **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.

56
Docs/Sprites/Objects.md Normal file
View File

@@ -0,0 +1,56 @@
# Objects Analysis
This document provides an analysis of the object sprites found in the `Sprites/Objects/` directory. These sprites are typically interactive elements of the environment rather than enemies.
## File Overview
| Filename | Sprite ID(s) | Description |
|---|---|---|
| `collectible.asm` | `$52` | A generic collectible sprite that can represent a Pineapple, Seashell, Sword/Shield, or Rock Sirloin. |
| `deku_leaf.asm` | `Sprite_DekuLeaf` | A Deku Leaf platform that Link can stand on. Also used for whirlpools. |
| `ice_block.asm` | `$D5` | A pushable ice block used in puzzles. |
| `minecart.asm` | `Sprite_Minecart` (`$A3`) | A rideable minecart used for transportation puzzles in dungeons. |
| `mineswitch.asm` | `Sprite_Mineswitch` | A lever switch that can be hit to change the state of other objects, like minecart tracks. |
| `pedestal.asm` | (Hooks `$1EE05F`) | Logic for the magic pedestals where Link can read text with the Book of Mudora. |
| `portal_sprite.asm` | `Sprite_Portal` | The blue and orange portals created by the Portal Rod. |
| `switch_track.asm` | `Sprite_SwitchTrack` (`$B0`) | The visual component of a switchable minecart track, which rotates when a lever is hit. |
## Detailed Object Analysis
### `collectible.asm`
- **Sprite ID:** `$52`
- **Summary:** This is a versatile sprite that acts as a world collectible. Its behavior and appearance are determined by its `SprAction` state, which is set during prep.
- **Key Logic:**
- **`Sprite_Collectible_Prep`:** Checks the current map (`$8A`) to determine what the sprite should be. For example, on map `$58` (intro), it becomes the sword and shield.
- **`Sprite_Collectible_Main`:** The main state machine. On contact with the player (`Sprite_CheckDamageToPlayer`), it increments the corresponding counter in SRAM (e.g., `Pineapples`, `Seashells`) or grants an item (`Link_ReceiveItem`) and then despawns itself.
### `ice_block.asm`
- **Sprite ID:** `$D5`
- **Summary:** A block that slides on icy floors when pushed by Link. It is used for puzzles.
- **Key Logic:**
- **Pushing:** When Link is in contact (`Sprite_CheckDamageToPlayerSameLayer`), the `Sprite_ApplyPush` routine gives the block velocity based on the direction Link is facing.
- **Sliding:** The block continues to move in that direction (`Sprite_Move`) until it hits a wall (`Sprite_CheckTileCollision`) or another object.
- **Switch Interaction:** `Sprite_IceBlock_CheckForSwitch` checks if the block is on top of a floor switch tile. If so, it sets a flag (`$0642`) to activate the switch and stops moving.
### `minecart.asm` / `switch_track.asm` / `mineswitch.asm`
- **Sprite IDs:** `Sprite_Minecart` (`$A3`), `Sprite_SwitchTrack` (`$B0`), `Sprite_Mineswitch`
- **Summary:** This is a complex system of three interconnected sprites that create minecart puzzles.
- **Key Logic:**
- **`Sprite_Minecart`:**
- The player can press B to enter the cart (`Minecart_WaitHoriz`/`Vert`). This sets the `!LinkInCart` flag and attaches the player.
- The cart moves along a path defined by custom tile types (`$B0`-`$BE`).
- At intersections (`$B6`, etc.), it reads player input (`$F0`) to determine which way to turn (`CheckForPlayerInput`).
- At corners (`$B2`-`$B5`), it automatically turns (`CheckForCornerTiles`).
- At dynamic switch tracks (`$D0`-`$D3`), it checks the state of the corresponding `Sprite_Mineswitch` to determine its path (`HandleDynamicSwitchTileDirections`).
- At dungeon transitions, it converts into a follower sprite (`MinecartFollower_TransitionToSprite`) to persist between rooms.
- **`Sprite_SwitchTrack`:** This is a purely visual sprite. Its frame is set based on the on/off state of its corresponding `Mineswitch`, which is stored in `SwitchRam`.
- **`Sprite_Mineswitch`:** This is a lever. When hit by the player, it toggles its state in the `SwitchRam` array, which is then read by the `SwitchTrack` and `Minecart` sprites.
### `portal_sprite.asm`
- **Sprite ID:** `Sprite_Portal`
- **Summary:** This sprite handles the logic for the portals created by the Portal Rod.
- **Key Logic:**
- **Spawning:** Two portals can exist at once: one blue and one orange. The `StateHandler` determines which type to create based on the `$7E0FA6` flag.
- **Warping:** When Link overlaps with a portal (`CheckIfHitBoxesOverlap`), it triggers a warp. The destination is the location of the *other* portal, whose coordinates are stored in RAM (`BluePortal_X/Y`, `OrangePortal_X/Y`).
- **Dismissal:** The `CheckForDismissPortal` routine despawns the oldest portal when a third one is created.
- **Invalid Placement:** `RejectOnTileCollision` prevents portals from being placed on invalid surfaces like walls and despawns the sprite if they are.

View File

@@ -0,0 +1,172 @@
# Collectible Sprites (Pineapple, Seashell, Sword/Shield, Rock Sirloin)
## Overview
The Collectible sprite (`!SPRID = $52`) is a versatile implementation designed to represent various collectible items within the game, including Pineapples, Seashells, the starting Sword/Shield, and Rock Sirloin. Its specific appearance and behavior are dynamically determined by the `SprAction, X` state and the current `AreaIndex`, allowing for context-sensitive item placement and interaction.
## Sprite Properties
* **`!SPRID`**: `$52` (Vanilla sprite ID, likely for a generic collectible)
* **`!NbrTiles`**: `03`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `00`
* **`!Persist`**: `00`
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `00`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_Collectible_Long`)
This routine acts as a dispatcher for drawing, selecting the appropriate drawing routine based on the `AreaIndex`:
* If `AreaIndex` is `$58` (Intro Sword area), it calls `Sprite_SwordShield_Draw`.
* If `AreaIndex` is `$4B` (Lupo Mountain area), it calls `Sprite_RockSirloin_Draw`.
* Otherwise, it calls `Sprite_Pineapple_Draw`.
* It also handles shadow drawing and dispatches to the main logic if the sprite is active.
```asm
Sprite_Collectible_Long:
{
PHB : PHK : PLB
LDA.b $8A : CMP.b #$58 : BNE .not_intro_sword
JSR Sprite_SwordShield_Draw
BRA +
.not_intro_sword
LDA.b $8A : CMP.b #$4B : BNE .not_lupo_mountain
JSR Sprite_RockSirloin_Draw
BRA +
.not_lupo_mountain
JSR Sprite_Pineapple_Draw
+
JSL Sprite_DrawShadow
JSL Sprite_CheckActive
BCC .SpriteIsNotActive
JSR Sprite_Collectible_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Collectible_Prep`)
This routine initializes the collectible sprite upon spawning, with conditional logic based on the `AreaIndex`:
* **Intro Sword**: If `AreaIndex` is `$58`, it checks Link's Sword flag (`$7EF359`). If Link already has the Sword, the sprite despawns. It also sets `SprAction, X` to `$02` (SwordShield).
* **Rock Sirloin**: If `AreaIndex` is `$4B`, it sets `SprAction, X` to `$03` (RockSirloin).
```asm
Sprite_Collectible_Prep:
{
PHB : PHK : PLB
; Don't spawn the sword if we have it.
LDA.b $8A : CMP.b #$58 : BNE .not_intro_sword
LDA.l $7EF359 : BEQ +
STZ.w SprState, X
+
LDA.b #$02 : STA.w SprAction, X
.not_intro_sword
LDA.b $8A : CMP.b #$4B : BNE .not_lupo_mountain
LDA.b #$03 : STA.w SprAction, X
.not_lupo_mountain
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_Collectible_Main`)
This routine manages the behavior of various collectible items through a jump table based on `SprAction, X`:
* **`Pineapple`**: Moves the sprite (`JSL Sprite_Move`). If Link touches it (`JSL Sprite_CheckDamageToPlayer`), it increments the `Pineapples` custom item count and despawns the sprite.
* **`Seashell`**: Similar to Pineapple, but increments the `Seashells` custom item count.
* **`SwordShield`**: Plays an animation, moves the sprite. If Link touches it, it grants Link the Sword (`LDY.b #$00`, `JSL Link_ReceiveItem`) and despawns the sprite.
* **`RockSirloin`**: Moves the sprite. It checks Link's Glove flag (`$7EF354`). If Link has the Glove, it checks for player contact. If touched, it handles interaction with thrown sprites (`JSL ThrownSprite_TileAndSpriteInteraction_long`), increments the `RockMeat` custom item count, and despawns the sprite.
```asm
Sprite_Collectible_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw Pineapple
dw Seashell
dw SwordShield
dw RockSirloin
Pineapple:
{
JSL Sprite_Move
JSL Sprite_CheckDamageToPlayer : BCC +
LDA.l Pineapples : INC A : STA.l Pineapples
STZ.w SprState, X
+
RTS
}
Seashell:
{
JSL Sprite_Move
JSL Sprite_CheckDamageToPlayer : BCC +
LDA.l Seashells : INC A : STA.l Seashells
STZ.w SprState, X
+
RTS
}
SwordShield:
{
%PlayAnimation(0,0,1)
JSL Sprite_Move
JSL Sprite_CheckDamageToPlayer : BCC +
LDY.b #$00 : STZ $02E9
JSL Link_ReceiveItem
STZ.w SprState, X
+
RTS
}
RockSirloin:
{
JSL Sprite_Move
LDA.l $7EF354 : BEQ .do_you_even_lift_bro
JSL Sprite_CheckDamageToPlayer : BCC +
JSL ThrownSprite_TileAndSpriteInteraction_long
LDA.l RockMeat : INC A : STA.l RockMeat
STZ.w SprState, X
+
.do_you_even_lift_bro
RTS
}
}
```
## Drawing (`Sprite_Pineapple_Draw`, `Sprite_SwordShield_Draw`, `Sprite_RockSirloin_Draw`)
Each collectible type has its own dedicated drawing routine. These routines handle OAM allocation and animation, and explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each routine contains its own specific OAM data for rendering the respective item.
## Design Patterns
* **Multi-Item Collectible**: A single sprite definition (`!SPRID = $52`) is used to represent multiple distinct collectible items, with their specific appearance and behavior determined by `SprAction, X` and `AreaIndex`. This allows for efficient reuse of sprite slots for various in-game items.
* **Context-Sensitive Spawning/Drawing**: The sprite's initial appearance and drawing routine are dynamically selected based on the `AreaIndex`, enabling specific items to appear in designated locations within the game world.
* **Item Granting**: Collectibles grant items to Link upon contact, directly influencing his inventory and progression.
* **Quest Progression Integration**: The Sword/Shield collectible despawns if Link already possesses the Sword, and the Rock Sirloin requires Link to have the Glove to interact with it, integrating these items into the game's quest and progression systems.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,147 @@
# Deku Leaf / Whirlpool
## Overview
The `deku_leaf.asm` file defines a versatile sprite (`!SPRID = Sprite_DekuLeaf`) that can function as two distinct in-game objects: the "Deku Leaf" and a "Whirlpool." Its specific behavior and visual representation are dynamically determined by the current `AreaIndex`, allowing it to serve different purposes in various locations.
## Sprite Properties
* **`!SPRID`**: `Sprite_DekuLeaf` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `00` (Graphics are handled externally or as a background)
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `$0D`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `00`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_DekuLeaf_Long`)
This routine acts as a dispatcher for drawing, selecting `Sprite_Whirlpool_Draw` if `AreaIndex` is `$3D` (Whirlpool area), and `Sprite_DekuLeaf_Draw` otherwise. It also dispatches to the main logic if the sprite is active.
```asm
Sprite_DekuLeaf_Long:
{
PHB : PHK : PLB
LDA $8A : CMP.b #$3D : BEQ .whirlpool
JSR Sprite_DekuLeaf_Draw
JMP +
.whirlpool
JSR Sprite_Whirlpool_Draw
+
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_DekuLeaf_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_DekuLeaf_Prep`)
This routine initializes the sprite upon spawning. If `AreaIndex` is `$3D` (Whirlpool area), it sets `SprAction, X` to `$01` (`Whirlpool_Main`), indicating its role as a whirlpool.
```asm
Sprite_DekuLeaf_Prep:
{
PHB : PHK : PLB
LDA $8A : CMP.b #$3D : BNE .not_whirlpool
LDA.b #$01 : STA.w SprAction, X
.not_whirlpool
PLB
RTL
}
```
## Main Logic & State Machine (`Sprite_DekuLeaf_Main`)
This routine manages the behavior of both the Deku Leaf and the Whirlpool through a jump table based on `SprAction, X`:
* **`WaitForPlayer` (Deku Leaf)**: Plays an idle animation. It checks if Link is on the leaf (`JSR CheckIfPlayerIsOn`). If so, it sets a flag (`$71`) and, if Link is in Minish form, spawns a poof garnish. Otherwise, it clears the flag.
* **`Whirlpool_Main`**: Plays an animation. If Link is on the whirlpool and the underwater flag (`$0AAB`) is set, it resets various Link state flags (`$55`, `$0AAB`, `$0351`, `$037B`, `$02B2`). If Link's state is not `$0B` (Mirror), it saves Link's coordinates and sets `GameMode` to `$23` to initiate a warp, similar to the Mirror effect.
```asm
Sprite_DekuLeaf_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw WaitForPlayer
dw Whirlpool_Main
WaitForPlayer:
{
%StartOnFrame(0)
%PlayAnimation(0, 0, 10)
JSR CheckIfPlayerIsOn : BCC +
LDA.b #$01 : STA.b $71
LDA.w $02B2 : CMP.b #$01 : BNE ++
JSL Sprite_SpawnPoofGarnish
++
RTS
+
STZ.b $71
RTS
}
Whirlpool_Main:
{
%PlayAnimation(0, 2, 10)
JSR CheckIfPlayerIsOn : BCC .not_on
LDA $0AAB : BEQ .not_on
STZ $55 ; Reset cape flag
STZ $0AAB ; Reset underwater flag
STZ $0351 ; Reset ripple flag
STZ $037B ; Reset invincibility flag
STZ $02B2 ; Reset mask flag
LDA.b $10 : CMP.b #$0B : BEQ .exit
LDA.b $8A : AND.b #$40 : STA.b $7B : BEQ .no_mirror_portal
LDA.b $20 : STA.w $1ADF
LDA.b $21 : STA.w $1AEF
LDA.b $22 : STA.w $1ABF
LDA.b $23 : STA.w $1ACF
.no_mirror_portal
LDA.b #$23
#SetGameModeLikeMirror:
STA.b $11
STZ.w $03F8
LDA.b #$01 : STA.w $02DB
STZ.b $B0
STZ.b $27 : STZ.b $28
LDA.b #$14 : STA.b $5D
.not_on
.exit
RTS
}
}
```
## Drawing (`Sprite_DekuLeaf_Draw` and `Sprite_Whirlpool_Draw`)
Each object type has its own dedicated drawing routine. These routines handle OAM allocation and animation, and explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations. Each routine contains its own specific OAM data for rendering the respective object.
## Design Patterns
* **Multi-Object Sprite (Conditional Drawing/Logic)**: A single sprite definition (`Sprite_DekuLeaf`) is used to represent two distinct objects (Deku Leaf and Whirlpool) based on `AreaIndex`, showcasing efficient resource utilization and varied functionality.
* **Context-Sensitive Behavior**: The sprite's behavior changes entirely based on the `AreaIndex`, allowing it to function as a traversal item (Deku Leaf) or a warp point (Whirlpool), adapting to different game contexts.
* **Player Interaction**: The Deku Leaf allows Link to stand on it for traversal, while the Whirlpool provides a warp mechanism, both offering unique forms of player interaction.
* **Game State Manipulation**: The Whirlpool modifies various Link state flags to initiate a warp, demonstrating direct control over the player's game state during transitions.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,208 @@
# Ice Block (Pushable)
## Overview
The Ice Block sprite (`!SPRID = $D5`) is an interactive object designed as a puzzle element that Link can push. It features complex logic for detecting Link's push, applying movement with momentum, and interacting with switch tiles. This sprite is impervious to most attacks and behaves like a solid statue.
## Sprite Properties
* **`!SPRID`**: `$D5` (Vanilla sprite ID, likely for a pushable block)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Impervious to all attacks)
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `09`
* **`!Persist`**: `00`
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `00`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `01` (Behaves like a solid statue)
* **`!DeflectProjectiles`**: `01` (Deflects all projectiles)
* **`!ImperviousArrow`**: `01` (Impervious to arrows)
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Constants
* **`!ICE_BLOCK_SPEED`**: `16` (Speed at which the ice block moves when pushed)
* **`!PUSH_CONFIRM_FRAMES`**: `10` (Number of frames Link must maintain a push for it to be confirmed)
* **`!ALIGN_TOLERANCE`**: `4` (Pixel tolerance for Link's alignment with the block)
* **`!WRAM_FLAG_0642`**: `$0642` (WRAM address for a flag related to switch activation)
* **`!WRAM_TILE_ATTR`**: `$0FA5` (WRAM address for tile attributes)
* **`!SPRITE_LOOP_MAX`**: `$0F` (Max index for sprite loops)
* **`!SPRITE_TYPE_STATUE`**: `$1C` (Sprite ID for a generic statue)
* **`!SPRITE_STATE_ACTIVE`**: `$09` (Sprite state for active sprites)
* **`!TILE_ATTR_ICE`**: `$0E` (Tile attribute for ice, currently unused)
* **`!SWITCH_TILE_ID_1` to `!SWITCH_TILE_ID_4`**: IDs for various switch tiles.
* **`!SWITCH_TILE_COUNT_MINUS_1`**: `$03`
## Main Structure (`Sprite_IceBlock_Long`)
This routine handles the Ice Block's drawing and dispatches to its main logic if active. It also manages Link's interaction with the block, setting Link's speed and actions when pushing.
```asm
Sprite_IceBlock_Long:
{
PHB : PHK : PLB
LDA.w SprMiscC, X : BEQ .not_being_pushed
STZ.w SprMiscC, X
STZ.b LinkSpeedTbl
STZ.b $48 ; Clear push actions bitfield
.not_being_pushed
LDA.w SprTimerA, X : BEQ .retain_momentum
LDA.b #$01 : STA.w SprMiscC, X
LDA.b #$84 : STA.b $48 ; Set statue and push block actions
LDA.b #$04 : STA.b LinkSpeedTbl ; Slipping into pit speed
.retain_momentum
JSR Sprite_IceBlock_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_IceBlock_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_IceBlock_Prep`)
This routine initializes the Ice Block upon spawning. It caches the sprite's initial position in `SprMiscD, X` through `SprMiscG, X`. It sets `SprDefl, X` to `$04` (designating it as a pushable statue) and initializes `SprMiscB, X` to `0` (movement state).
```asm
Sprite_IceBlock_Prep:
{
PHB : PHK : PLB
; Cache Sprite position
LDA.w SprX, X : STA.w SprMiscD, X
LDA.w SprY, X : STA.w SprMiscE, X
LDA.w SprXH, X : STA.w SprMiscF, X
LDA.w SprYH, X : STA.w SprMiscG, X
LDA.b #$04 : STA.w SprDefl, X ; Set as pushable statue
LDA.w SprHitbox, X : ORA.b #$09 : STA.w SprHitbox, X
; Initialize movement state tracking
STZ.w SprMiscB, X ; Clear movement state
PLB
RTL
}
```
## Main Logic (`Sprite_IceBlock_Main`)
This routine manages the Ice Block's behavior, including push detection, movement, and interaction with switches.
* **Animation**: Plays a static animation (`%PlayAnimation(0, 0, 1)`).
* **Sprite-to-Sprite Collision**: Calls `IceBlock_HandleSpriteToSpriteCollision` to manage interactions with other sprites.
* **Damage Reaction**: If the block takes damage, its position, speed, and movement state are reset.
* **Switch Detection**: Calls `Sprite_IceBlock_CheckForSwitch`. If the block is on a switch, it stops movement, sets `!WRAM_FLAG_0642` to `01`, and resets its movement state.
* **Push Logic**: This is a core part of the routine. If the block is not moving, it checks if Link is in contact and correctly aligned (`IceBlock_CheckLinkPushAlignment`). If so, a push timer (`SprTimerA, X`) is initiated. If the timer expires while Link is still pushing, the block snaps to the grid, applies push speed (`Sprite_ApplyPush`), and begins moving. If the block is already moving, it continues to move (`JSL Sprite_Move`) and checks for tile collisions, stopping if an obstacle is encountered.
```asm
Sprite_IceBlock_Main:
{
%PlayAnimation(0, 0, 1)
JSR IceBlock_HandleSpriteToSpriteCollision ; Renamed from Statue_BlockSprites
JSL Sprite_CheckDamageFromPlayer : BCC .no_damage
LDA.w SprMiscD, X : STA.w SprX, X
LDA.w SprY, X : STA.w SprY, X
LDA.w SprXH, X : STA.w SprXH, X
LDA.w SprYH, X : STA.w SprYH, X
STZ.w SprXSpeed, X : STZ.w SprYSpeed, X
STZ.w SprTimerA, X : STZ.w SprMiscA, X
STZ.w SprMiscB, X ; Reset movement state when hit
.no_damage
STZ.w !WRAM_FLAG_0642
JSR Sprite_IceBlock_CheckForSwitch : BCC .no_switch
STZ.w SprXSpeed, X : STZ.w SprYSpeed, X
LDA.b #$01 : STA.w !WRAM_FLAG_0642
STZ.w SprMiscB, X ; Reset movement state when hitting switch
.no_switch
; If the block is currently moving, apply movement and check for collisions
LDA.w SprMiscB, X
BNE .block_is_moving
; --- Block is NOT moving, check for push initiation ---
JSL Sprite_CheckDamageToPlayerSameLayer : BCC .NotInContact
; Link is in contact. Now check if he's properly aligned and facing the block.
JSR IceBlock_CheckLinkPushAlignment
BCC .NotInContact ; Link is not aligned or facing correctly.
; Link is aligned and facing the block. Start or continue the push timer.
LDA.w SprTimerA, X
BNE .timer_is_running ; Timer already started, let it count down.
; Start the timer for the first time.
LDA.b #!PUSH_CONFIRM_FRAMES
STA.w SprTimerA, X
RTS ; Wait for next frame
.timer_is_running
; Timer is running. Has it reached zero? (SprTimerA is decremented by engine)
LDA.w SprTimerA, X
BNE .NotInContact ; Not zero yet, keep waiting.
; --- PUSH CONFIRMED ---
; Timer reached zero while still in contact and aligned.
; Snap to grid before setting speed for clean movement.
LDA.w SprX, X : AND.b #$F8 : STA.w SprX, X
LDA.w SprY, X : AND.b #$F8 : STA.w SprY, X
JSR Sprite_ApplyPush ; Set speed based on Link's direction.
LDA.b #$01 : STA.w SprMiscB, X ; Set "is moving" flag.
RTS
.NotInContact
; No contact or improper alignment, reset push timer.
STZ.w SprTimerA, X
RTS
.block_is_moving
JSL Sprite_Move
JSL Sprite_Get_16_bit_Coords
JSL Sprite_CheckTileCollision
; ----udlr , u = up, d = down, l = left, r = right
LDA.w SprCollision, X : AND.b #$0F : BEQ + ; If no collision, continue moving
STZ.w SprXSpeed, X : STZ.w SprYSpeed, X ; Stop movement
STZ.w SprMiscB, X ; Reset movement state
+
RTS
}
```
## `IceBlock_CheckLinkPushAlignment`
This complex routine precisely determines if Link is correctly aligned and facing the ice block to initiate a push. It calculates the relative positions of Link and the block, considers Link's facing direction, and uses `!ALIGN_TOLERANCE` to allow for slight pixel variations. It returns with the carry flag set for success or clear for failure.
## `Sprite_ApplyPush`
This routine sets the Ice Block's `SprXSpeed, X` or `SprYSpeed, X` based on Link's facing direction (`SprMiscA, X`) and the predefined `!ICE_BLOCK_SPEED`.
## `IceBlock_CheckForGround`
This routine is currently unused but was intended to check if the tile beneath the sprite was a sliding ice tile.
## `Sprite_IceBlock_CheckForSwitch`
This routine checks if any of the four corners of the Ice Block are currently positioned on a switch tile (identified by `!SWITCH_TILE_ID_1` to `!SWITCH_TILE_ID_4`). It returns with the carry flag set if any corner is on a switch tile.
## `IceBlock_HandleSpriteToSpriteCollision`
This routine (renamed from `Statue_BlockSprites`) manages collisions between the Ice Block and other active sprites. It iterates through other sprites, checks for collision, and applies recoil or other effects to them.
## Drawing (`Sprite_IceBlock_Draw`)
This routine handles OAM allocation and animation for the Ice Block. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
## Design Patterns
* **Interactive Puzzle Element**: The Ice Block is a core interactive puzzle element that Link can manipulate by pushing, requiring precise player input and environmental interaction.
* **Precise Collision and Alignment Detection**: Implements detailed logic to ensure Link is correctly positioned and facing the block before a push is registered, providing a robust and fair interaction mechanism.
* **Movement with Momentum**: The block retains momentum after being pushed, sliding across the terrain until it encounters an obstacle, adding a realistic physics element.
* **Switch Activation**: The block can activate switch tiles upon contact, integrating it into environmental puzzles and triggering game events.
* **Sprite-to-Sprite Collision**: Handles interactions with other sprites, applying recoil effects to them, demonstrating complex inter-sprite dynamics.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,166 @@
# Mine Switch
## Overview
The Mine Switch sprite (`!SPRID = Sprite_Mineswitch`) is an interactive puzzle element, typically found in the Goron Mines. It functions as a lever-style switch that Link can activate by attacking it, altering the state of minecart tracks or other game elements. This sprite supports both a regular on/off switch and a speed-controlling switch, with its behavior and appearance changing based on its current state.
## Sprite Properties
* **`!SPRID`**: `Sprite_Mineswitch` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `01`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `00`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `01`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `00`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_LeverSwitch_Long`)
This routine handles the Mine Switch's drawing and dispatches to its main logic if the sprite is active.
```asm
Sprite_LeverSwitch_Long:
{
PHB : PHK : PLB
JSR Sprite_LeverSwitch_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_LeverSwitch_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_LeverSwitch_Prep`)
This routine initializes the Mine Switch upon spawning. It sets `SprDefl, X` to `0`. It retrieves the switch's on/off state from `SwitchRam`, indexed by `SprSubtype, X`, and sets `SprAction, X` and `SprFrame, X` accordingly. It also sets `SprTileDie, X` to `0` and `SprBulletproof, X` to `0`.
```asm
Sprite_LeverSwitch_Prep:
{
PHB : PHK : PLB
LDA.b #$00 : STA.w SprDefl, X
; Get the subtype of the switch so that we can get its on/off state.
LDA.w SprSubtype, X : TAY
LDA.w SwitchRam, Y : STA.w SprAction, X : STA.w SprFrame, X
LDA.b #$00 : STA.w SprTileDie, X
STZ.w SprBulletproof, X
PLB
RTL
}
```
## Constants
* **`SwitchRam = $0230`**: A WRAM address that stores the state (on/off) of each individual switch, indexed by its `SprSubtype`.
## Main Logic & State Machine (`Sprite_LeverSwitch_Main`)
This routine manages the Mine Switch's behavior through a jump table, supporting different types of switches:
* **Player Collision**: Prevents Link from passing through the switch (`JSL Sprite_PlayerCantPassThrough`).
* **`SwitchOff`**: Plays an animation. If Link attacks it (`JSL Sprite_CheckDamageFromPlayer`) and a timer (`SprTimerA, X`) allows, it plays a sound (`$25`), turns the switch on (`STA.w SwitchRam, Y` to `01`), sets a timer, and transitions to `SwitchOn`.
* **`SwitchOn`**: Plays an animation. If Link attacks it and a timer allows, it plays a sound (`$25`), turns the switch off (`STA.w SwitchRam, Y` to `00`), sets a timer, and transitions to `SwitchOff`.
* **`SpeedSwitchOff`**: Plays an animation. If Link attacks it, it plays a sound (`$25`), sets `$36` to `01` (likely a global speed flag for minecarts), and transitions to `SpeedSwitchOn`.
* **`SpeedSwitchOn`**: Plays an animation. If Link attacks it, it plays a sound (`$25`), clears `$36`, and transitions to `SpeedSwitchOff`.
```asm
Sprite_LeverSwitch_Main:
{
JSL Sprite_PlayerCantPassThrough
LDA.w SprAction, X
JSL UseImplicitRegIndexedLocalJumpTable
dw SwitchOff
dw SwitchOn
dw SpeedSwitchOff
dw SpeedSwitchOn
SwitchOff:
{
%PlayAnimation(0,0,4)
LDA.w SprTimerA, X : BNE .NoDamage
JSL Sprite_CheckDamageFromPlayer : BCC .NoDamage
LDA #$25 : STA $012F
; Get the subtype of the switch so that we can get its on/off state.
LDA.w SprSubtype, X : TAY
; Turn the switch on.
LDA #$01 : STA.w SwitchRam, Y
LDA #$10 : STA.w SprTimerA, X
%GotoAction(1)
.NoDamage
RTS
}
SwitchOn:
{
%PlayAnimation(1,1,4)
LDA.w SprTimerA, X : BNE .NoDamage
JSL Sprite_CheckDamageFromPlayer : BCC .NoDamage
LDA #$25 : STA $012F
; Get the subtype of the switch so that we can get its on/off state.
LDA.w SprSubtype, X : TAY
; Turn the switch off.
LDA #$00 : STA.w SwitchRam, Y
LDA #$10 : STA.w SprTimerA, X
%GotoAction(0)
.NoDamage
RTS
}
SpeedSwitchOff:
{
%PlayAnimation(0,0,4)
JSL Sprite_CheckDamageFromPlayer : BCC .NoDamage
LDA.b #$25 : STA $012F
LDA.b #$01 : STA $36
%GotoAction(3)
.NoDamage
RTS
}
SpeedSwitchOn:
{
%PlayAnimation(1,1,4)
JSL Sprite_CheckDamageFromPlayer : BCC .NoDamage
LDA #$25 : STA $012F
STZ.w $36
%GotoAction(2)
.NoDamage
RTS
}
}
```
## Drawing (`Sprite_LeverSwitch_Draw`)
This routine handles OAM allocation and animation for the Mine Switch. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
## Design Patterns
* **Interactive Puzzle Element**: The Mine Switch is a key interactive puzzle element that Link can activate by attacking it, triggering changes in the game environment.
* **State-Based Behavior**: The switch has distinct "on" and "off" states, with different animations and effects, providing clear visual feedback to the player.
* **Subtype-Driven State**: The `SprSubtype` is used to index into `SwitchRam`, allowing each individual switch to maintain its own independent state, enabling complex puzzle designs with multiple switches.
* **Speed Control**: The "Speed Switch" variant directly controls a global speed flag (`$36`), likely affecting the speed of minecarts or other moving objects, adding another layer of interaction to the minecart system.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,113 @@
# Minecart
## Overview
The Minecart sprite (`!SPRID = Sprite_Minecart`) is a highly complex and interactive object primarily used in the Goron Mines. It allows Link to ride it through a network of tracks, with its movement dictated by various track tile types, player input, and seamless dungeon transitions. The Minecart system features persistent state across rooms and intricate collision detection.
## Sprite Properties
* **`!SPRID`**: `Sprite_Minecart` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `08`
* **`!Harmless`**: `01`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `01` (Impervious to all attacks)
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `14`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `00`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Constants
* **`!LinkInCart`**: `$35` (Flag indicating Link is currently riding the Minecart)
* **`!MinecartSpeed`**: `20` (Normal movement speed)
* **`!DoubleSpeed`**: `30` (Faster movement speed, possibly for boosts)
* **Directions**: `North`, `East`, `South`, `West` (Used for `!MinecartDirection` and `SprMiscB`)
* **Sprite Facing Directions**: `Up`, `Down`, `Left`, `Right` (Used for `!SpriteDirection`)
* **`!MinecartDirection`**: `$0DE0` (Maps to `SprMiscC`, stores the current movement direction)
* **`!SpriteDirection`**: `$0DE0` (Stores the sprite's visual facing direction)
* **Track Persistence**: A system for saving and loading minecart state across rooms:
* **`!MinecartTrackRoom`**: `$0728` (Stores the room ID where a specific track was left)
* **`!MinecartTrackX`**: `$0768` (Stores the X position of a track)
* **`!MinecartTrackY`**: `$07A8` (Stores the Y position of a track)
* **Active Cart Tracking**: Variables to manage the currently active minecart:
* **`!MinecartTrackCache`**: `$07E8` (Stores the ID of the track Link is currently on)
* **`!MinecartDirectionCache`**: `$07E9` (Stores the direction during room transitions)
* **`!MinecartCurrent`**: `$07EA` (Stores the sprite slot index of the current minecart)
## Collision Setup (Tile Types)
Defines various tile types that represent different parts of the minecart track, including straight sections, corners, intersections, stop tiles, and dynamic switch tiles. These are crucial for guiding the minecart's movement and interaction with the environment.
## Main Structure (`Sprite_Minecart_Long`)
This routine handles the Minecart's multi-layered drawing (top and bottom portions) and dispatches to its main logic if the sprite is active.
```asm
Sprite_Minecart_Long:
{
PHB : PHK : PLB
JSR Sprite_Minecart_DrawTop ; Draw behind Link
JSR Sprite_Minecart_DrawBottom ; Draw in front of Link
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Minecart_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Minecart_Prep`)
This routine initializes the Minecart upon spawning. It updates cached coordinates, manages track persistence (initializing track data if not already set), and handles despawning if the cart is not in its designated room or its coordinates don't match. It sets various sprite properties and determines the initial movement direction based on the tile the minecart is placed on.
## Main Logic & State Machine (`Sprite_Minecart_Main`)
This routine manages the Minecart's complex behavior through a state machine:
* **`Minecart_WaitHoriz` / `Minecart_WaitVert`**: The cart waits in a horizontal or vertical orientation. If Link is on the cart (`CheckIfPlayerIsOn`) and presses the B button, it saves the track ID, cancels Link's dash, sets `LinkSomaria` and `!LinkInCart`, adjusts Link's position, and transitions to a movement state (`Minecart_MoveEast`, `Minecart_MoveWest`, `Minecart_MoveNorth`, `Minecart_MoveSouth`).
* **`Minecart_MoveNorth` / `MoveEast` / `MoveSouth` / `MoveWest`**: The cart moves in the specified direction. It plays animations, sets speed (`!MinecartSpeed` or `!DoubleSpeed`), moves the sprite, drags Link along (`JSL DragPlayer`), handles the player camera, and processes track tiles (`HandleTileDirections`).
* **`Minecart_Release`**: Stops the cart, releases Link, and transitions back to a `Minecart_Wait` state.
## Helper Routines
* **`HandlePlayerCameraAndMoveCart`**: Manages Link's animation, camera, and plays cart sound effects.
* **`StopCart`**: Stops the cart, releases Link, rounds its coordinates, and saves its position to track variables for persistence.
* **`InitMovement`**: Caches Link's coordinates for movement calculations.
* **`Minecart_SetDirectionNorth` / `East` / `South` / `West`**: Set the cart's direction, animation, and update track caches.
* **`HandleTileDirections`**: A crucial routine that checks the tile the minecart is currently on and determines its next action, handling out-of-bounds, stop tiles, player input at intersections, corner tiles, and dynamic switch tiles.
* **`CheckForOutOfBounds`**: Determines if the cart is on an out-of-bounds tile.
* **`CheckForStopTiles`**: Checks for stop tiles and sets the cart's next direction.
* **`CheckForPlayerInput`**: Detects player input on intersection tiles to allow Link to choose the cart's direction.
* **`CheckForCornerTiles`**: Handles direction changes when the cart encounters corner tiles.
* **`HandleDynamicSwitchTileDirections`**: Manages movement on dynamic switch tiles, which can alter the cart's path.
* **`CheckTrackSpritePresence`**: Checks for the presence and collision of a `Sprite $B0` (Switch Track) with the minecart.
* **`CheckIfPlayerIsOn`**: Determines if Link is overlapping the minecart.
* **`ResetTrackVars`**: Resets all minecart track-related variables.
* **`Minecart_HandleToss` / `Minecart_HandleTossedCart` / `Minecart_HandleLiftAndToss`**: Routines for handling the minecart being tossed or lifted by Link.
## Drawing (`Sprite_Minecart_DrawTop` and `Sprite_Minecart_DrawBottom`)
These routines draw the Minecart in two separate portions (top and bottom) to create the illusion of Link riding inside it. They utilize `JSL Sprite_PrepOamCoord` and `OAM_AllocateFromRegionB`/`C` for OAM allocation, and explicitly use `REP #$20` and `SEP #$20` for 16-bit coordinate calculations.
## Vanilla Overrides
* **`RoomTag_ShutterDoorRequiresCart`**: Modifies a room tag to require Link to be in a cart to open a shutter door, integrating the Minecart into dungeon puzzles.
* **`org $028260`**: Injects `JSL ResetTrackVars` to ensure minecart track variables are reset at specific points.
## Design Patterns
* **Complex Interactive Object**: The Minecart is a highly interactive object with intricate movement, collision, and state management, providing a unique traversal mechanic.
* **Track-Based Movement**: Movement is precisely governed by specific track tile types (stops, corners, intersections), requiring careful design of the minecart routes.
* **Player Input for Direction**: Link can influence the minecart's direction at intersections, adding an element of player control to the ride.
* **Persistent State**: Minecart position and direction are saved across room transitions, ensuring continuity and allowing for complex multi-room puzzles.
* **Multi-Part Drawing**: The minecart is drawn in two separate parts to allow Link to appear "inside" it, enhancing visual immersion.
* **Player State Manipulation**: The minecart directly controls Link's state (`!LinkInCart`, `LinkSomaria`, `LinkState`), seamlessly integrating the ride into Link's overall actions.
* **Dynamic Room Transitions**: Handles seamless transitions between rooms while Link is in the minecart, maintaining the flow of gameplay.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,76 @@
# Pedestal (Magic Pedestal Plaque)
## Overview
The `pedestal.asm` file defines the custom behavior for a "Magic Pedestal" sprite, which is an interactive object that responds to Link's actions. This implementation overrides a vanilla pedestal plaque sprite (`Sprite_B3_PedestalPlaque`) to trigger specific game events based on Link's inventory, input, and the current `AreaIndex`.
## Vanilla Overrides
* **`org $1EE05F`**: Injects `JSL CheckForBook` into the `Sprite_B3_PedestalPlaque` routine. This means the custom logic defined in `CheckForBook` will execute when the vanilla pedestal plaque sprite is processed.
## `CheckForBook`
This routine is the primary entry point for the custom pedestal logic. It checks several conditions to determine if Link is interacting with the pedestal in a specific way:
* **Link's Action**: Checks `$2F` (Link's current action/state).
* **Player Contact**: Checks for damage to player (`JSL Sprite_CheckDamageToPlayer`), which in this context likely means Link is in contact with the pedestal.
* **Item Held**: Checks if Link is holding a specific item (`$0202` compared to `$0F`, which likely corresponds to a book item).
* **Player Input**: Checks if Link is pressing the Y button (`BIT.b $F4`).
* **State Manipulation**: If Link is holding the book and pressing Y, it sets `$0300` to `0`, `$037A` to `$20`, and `$012E` to `0` (these are likely related to Link's animation or state changes).
* **Event Trigger**: Calls `JSR PedestalPlaque` to execute area-specific logic.
```asm
CheckForBook:
{
LDA.b $2F : BNE .exit
JSL Sprite_CheckDamageToPlayer : BCC .exit
LDA.w $0202 : CMP.b #$0F : BNE .not_holding_book
LDY.b #$01 : BIT.b $F4 : BVS .not_pressing_y
.not_holding_book
LDY.b #$00
.not_pressing_y
CPY.b #$01 : BNE .no_book_pose
STZ.w $0300
LDA.b #$20
STA.w $037A
STZ.w $012E
.no_book_pose
JSR PedestalPlaque
.exit
LDA.b AreaIndex : CMP.b #$30
RTL
}
```
## `PedestalPlaque`
This routine contains the area-specific logic for the pedestal, triggering different events based on the current `AreaIndex`:
* **Zora Temple (`AreaIndex = $1E`)**: Checks a flag (`$7EF29E` bit `$20`) and `SongFlag` (`$03`). If specific conditions are met (e.g., a certain event has not occurred and a particular song has been played), it sets `$04C6` to `$01` (likely a flag to open a gate or trigger an event) and clears `SongFlag`.
* **Goron Desert (`AreaIndex = $36`)**: No specific logic defined in this file.
* **Fortress Secrets (`AreaIndex = $5E`)**: No specific logic defined in this file.
```asm
PedestalPlaque:
{
LDA.b AreaIndex : CMP.b #$1E : BEQ .zora_temple
CMP.b #$36 : BEQ .goron_desert
CMP.b #$5E : BEQ .fortress_secrets
JMP .return
.zora_temple
LDA.l $7EF29E : AND.b #$20 : BNE .return
LDA.b SongFlag : CMP.b #$03 : BNE .return
LDA.b #$01 : STA $04C6
STZ.b SongFlag
JMP .return
.goron_desert
.fortress_secrets
.return
RTS
}
```
## Design Patterns
* **Vanilla Override**: This file directly modifies the vanilla pedestal plaque sprite to implement custom interactive behavior, demonstrating how to integrate new puzzle mechanics into existing game elements.
* **Context-Sensitive Interaction**: The pedestal responds specifically when Link is holding a particular item (a book) and pressing a button, creating a unique and logical interaction for puzzle solving.
* **Quest Progression Integration**: The pedestal triggers events based on the `AreaIndex` and various game state flags (e.g., `SongFlag`, `$7EF29E`), indicating its role in advancing specific quests and unlocking new areas.
* **Game State Manipulation**: Directly modifies WRAM addresses (`$04C6`, `SongFlag`) to trigger game events, such as opening gates or clearing flags, which are crucial for puzzle resolution and progression.

View File

@@ -0,0 +1,281 @@
# Portal Sprite
## Overview
The Portal sprite (`!SPRID = Sprite_Portal`) implements a sophisticated two-way warping system within the game. It allows Link to instantly travel between designated Blue and Orange portals, which can be placed in both dungeons and the overworld. This sprite features complex logic for portal activation, collision detection with Link, and seamless management of Link's state during warps.
## Sprite Properties
* **`!SPRID`**: `Sprite_Portal` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `01`
* **`!Harmless`**: `00`
* **`!HVelocity`**: `00`
* **`!Health`**: `00`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `00`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `00`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_Portal_Long`)
This routine handles the Portal's drawing and dispatches to its main logic if the sprite is active.
```asm
Sprite_Portal_Long:
{
PHB : PHK : PLB
JSR Sprite_Portal_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_Portal_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_Portal_Prep`)
This routine initializes the Portal upon spawning. It sets `SprDefl, X` to `0` (ensuring persistence outside the camera view), modifies `SprHitbox, X` properties, sets `SprTileDie, X` to `0`, and makes the portal bulletproof (`SprBulletproof, X` to `$FF`).
```asm
Sprite_Portal_Prep:
{
PHB : PHK : PLB
; Persist outside of camera
LDA #$00 : STA.w SprDefl, X
LDA.w SprHitbox, X : AND.b #$C0 : STA.w SprHitbox, X
STZ.w SprTileDie, X
LDA.b #$FF : STA.w SprBulletproof, X
PLB
RTL
}
```
## Portal Data Memory Locations
* **`BluePortal_X`, `BluePortal_Y`, `OrangePortal_X`, `OrangePortal_Y`**: WRAM addresses storing the X and Y coordinates of the Blue and Orange portals, respectively.
* **`BlueActive`, `OrangeActive`**: Flags indicating whether the Blue and Orange portals are currently active.
* **`OrangeSpriteIndex`, `BlueSpriteIndex`**: Store the sprite indices of the Orange and Blue portals.
## Main Logic & State Machine (`Sprite_Portal_Main`)
This routine manages the various states and behaviors of the portals, including their creation, activation, and warping functionality.
* **`StateHandler`**: Calls `CheckForDismissPortal` and `RejectOnTileCollision`. It then checks `$7E0FA6` (likely a flag indicating which portal is being spawned). If `$7E0FA6` is `0`, it sets up an Orange Portal (stores coordinates, sets `SprSubtype, X` to `01`, and transitions to `OrangePortal`). Otherwise, it sets up a Blue Portal (stores coordinates, sets `SprSubtype, X` to `02`, and transitions to `BluePortal`).
* **`BluePortal` / `OrangePortal`**: Plays an animation. It checks if Link has been warped (`$11` compared to `$2A`). It then checks for overlap with Link's hitbox (`CheckIfHitBoxesOverlap`). If Link overlaps, it determines if Link is in a dungeon or overworld (`$1B`) and transitions to the appropriate warp state (`BluePortal_WarpDungeon`, `OrangePortal_WarpDungeon`, `BluePortal_WarpOverworld`, `OrangePortal_WarpOverworld`).
* **`BluePortal_WarpDungeon` / `OrangePortal_WarpDungeon`**: Warps Link's coordinates (`$20`, `$22`), sets camera scroll boundaries, stores the other portal's coordinates, sets its `SprTimerD, X`, sets `$11` to `$14`, and returns to the respective portal state.
* **`BluePortal_WarpOverworld` / `OrangePortal_WarpOverworld`**: Warps Link's coordinates (`$20`, `$22`), sets camera scroll boundaries, applies Link's movement to the camera (`JSL ApplyLinksMovementToCamera`), stores the other portal's coordinates, sets its `SprTimerD, X`, sets `$5D` to `$01`, and returns to the respective portal state.
```asm
Sprite_Portal_Main:
{
LDA.w SprAction, X
JSL JumpTableLocal
dw StateHandler
dw BluePortal
dw OrangePortal
dw BluePortal_WarpDungeon
dw OrangePortal_WarpDungeon
dw BluePortal_WarpOverworld
dw OrangePortal_WarpOverworld
StateHandler:
{
JSR CheckForDismissPortal
JSR RejectOnTileCollision
LDA $7E0FA6 : BNE .BluePortal
LDA #$01 : STA $0307
TXA : STA.w OrangeSpriteIndex
LDA.w SprY, X : STA.w OrangePortal_X
LDA.w SprX, X : STA.w OrangePortal_Y
LDA.b #$01 : STA.w SprSubtype, X
%GotoAction(2)
RTS
.BluePortal
LDA #$02 : STA $0307
TXA : STA.w BlueSpriteIndex
LDA.w SprY, X : STA.w BluePortal_X
LDA.w SprX, X : STA.w BluePortal_Y
LDA.b #$02 : STA.w SprSubtype, X
%GotoAction(1)
RTS
}
BluePortal:
{
%StartOnFrame(0)
%PlayAnimation(0,1,8)
LDA $11 : CMP.b #$2A : BNE .not_warped_yet
STZ $11
.not_warped_yet
CLC
LDA.w SprTimerD, X : BNE .NoOverlap
JSL Link_SetupHitBox
JSL $0683EA ; Sprite_SetupHitbox_long
JSL CheckIfHitBoxesOverlap : BCC .NoOverlap
CLC
LDA $1B : BEQ .outdoors
%GotoAction(3) ; BluePortal_WarpDungeon
.NoOverlap
RTS
.outdoors
%GotoAction(5) ; BluePortal_WarpOverworld
RTS
}
OrangePortal:
{
%StartOnFrame(2)
%PlayAnimation(2,3,8)
LDA $11 : CMP.b #$2A : BNE .not_warped_yet
STZ $11
.not_warped_yet
CLC
LDA.w SprTimerD, X : BNE .NoOverlap
JSL Link_SetupHitBox
JSL $0683EA ; Sprite_SetupHitbox_long
JSL CheckIfHitBoxesOverlap : BCC .NoOverlap
CLC
; JSL $01FF28 ; Player_CacheStatePriorToHandler
LDA $1B : BEQ .outdoors
%GotoAction(4) ; OrangePortal_WarpDungeon
.NoOverlap
RTS
.outdoors
%GotoAction(6) ; OrangePortal_WarpOverworld
RTS
}
BluePortal_WarpDungeon:
{
LDA $7EC184 : STA $20
LDA $7EC186 : STA $22
LDA $7EC188 : STA $0600
LDA $7EC18A : STA $0604
LDA $7EC18C : STA $0608
LDA $7EC18E : STA $060C
PHX
LDA.w OrangeSpriteIndex : TAX
LDA #$40 : STA.w SprTimerD, X
LDA.w SprY, X : STA $7EC184
STA.w BluePortal_Y
LDA.w SprX, X : STA $7EC186
STA.w BluePortal_X
PLX
LDA #$14 : STA $11
%GotoAction(1) ; Return to BluePortal
RTS
}
OrangePortal_WarpDungeon:
{
LDA $7EC184 : STA $20
LDA $7EC186 : STA $22
; Camera Scroll Boundaries
LDA $7EC188 : STA $0600 ; Small Room North
LDA $7EC18A : STA $0604 ; Small Room South
LDA $7EC18C : STA $0608 ; Small Room West
LDA $7EC18E : STA $060C ; Small Room South
PHX
LDA.w BlueSpriteIndex : TAX
LDA #$40 : STA.w SprTimerD, X
LDA.w SprY, X : STA $7EC184
STA.w OrangePortal_Y
LDA.w SprX, X : STA $7EC186
STA.w OrangePortal_X
PLX
LDA #$14 : STA $11
%GotoAction(2) ; Return to OrangePortal
RTS
}
BluePortal_WarpOverworld:
{
LDA.w OrangePortal_X : STA $20
LDA.w OrangePortal_Y : STA $22
LDA $7EC190 : STA $0610
LDA $7EC192 : STA $0612
LDA $7EC194 : STA $0614
LDA $7EC196 : STA $0616
JSL ApplyLinksMovementToCamera
PHX ; Infinite loop prevention protocol
LDA.w OrangeSpriteIndex : TAX
LDA #$40 : STA.w SprTimerD, X
PLX
LDA #$01 : STA $5D
;LDA #$2A : STA $11
%GotoAction(1) ; Return to BluePortal
RTS
}
OrangePortal_WarpOverworld:
{
LDA.w BluePortal_X : STA $20
LDA.w BluePortal_Y : STA $22
LDA $7EC190 : STA $0610
LDA $7EC192 : STA $0612
LDA $7EC194 : STA $0614
LDA $7EC196 : STA $0616
JSL ApplyLinksMovementToCamera
PHX
LDA.w BlueSpriteIndex : TAX
LDA #$40 : STA.w SprTimerD, X
PLX
LDA #$01 : STA $5D
;LDA #$2A : STA $11
%GotoAction(2) ; Return to BluePortal
RTS
}
}
```
## Helper Routines
* **`CheckForDismissPortal`**: Checks a ticker (`$06FE`). If it exceeds `02`, it despawns the active portals (Blue and Orange) and decrements the ticker. Otherwise, it increments the ticker. This ticker needs to be reset during room and map transitions.
* **`RejectOnTileCollision`**: Checks for tile collision. If a portal is placed on an invalid tile (tile attribute `0` or `48`), it despawns the portal, plays an error sound (`SFX2.3C`), and decrements the ticker (`$06FE`).
## Drawing (`Sprite_Portal_Draw`)
This routine handles OAM allocation and animation for the Portal. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
## Design Patterns
* **Two-Way Warping System**: Implements a complex two-way portal system that allows Link to instantly travel between two designated points, enhancing exploration and puzzle design.
* **Context-Sensitive Warping**: Portals can intelligently warp Link between dungeons and the overworld, adapting to the current game context and providing seamless transitions.
* **Persistent Portal Locations**: Portal coordinates are stored in WRAM, allowing them to be placed and remembered across game sessions, enabling dynamic puzzle setups.
* **Link State Management**: Modifies Link's coordinates, camera boundaries, and game mode during warps, ensuring a smooth and consistent player experience during transitions.
* **Collision Detection**: Utilizes `CheckIfHitBoxesOverlap` to accurately detect when Link enters a portal, triggering the warp sequence.
* **Error Handling**: Includes logic to dismiss portals if they are placed on invalid tiles, preventing game-breaking scenarios and providing feedback to the player.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

View File

@@ -0,0 +1,232 @@
# Switch Track
## Overview
The Switch Track sprite (`!SPRID = Sprite_SwitchTrack`) is an interactive object designed to function as a rotating segment of a minecart track. Its visual appearance and implied path change dynamically based on its `SprAction` (which represents its mode of rotation) and the on/off state of a corresponding switch, stored in `SwitchRam`.
## Sprite Properties
* **`!SPRID`**: `Sprite_SwitchTrack` (Custom symbol, likely a remapped vanilla ID)
* **`!NbrTiles`**: `02`
* **`!Harmless`**: `00`
* **`!HVelocity`**: `00`
* **`!Health`**: `01`
* **`!Damage`**: `00`
* **`!DeathAnimation`**: `00`
* **`!ImperviousAll`**: `00`
* **`!SmallShadow`**: `00`
* **`!Shadow`**: `00`
* **`!Palette`**: `00`
* **`!Hitbox`**: `00`
* **`!Persist`**: `01` (Continues to live off-screen)
* **`!Statis`**: `00`
* **`!CollisionLayer`**: `00`
* **`!CanFall`**: `00`
* **`!DeflectArrow`**: `00`
* **`!WaterSprite`**: `00`
* **`!Blockable`**: `00`
* **`!Prize`**: `00`
* **`!Sound`**: `00`
* **`!Interaction`**: `00`
* **`!Statue`**: `00`
* **`!DeflectProjectiles`**: `00`
* **`!ImperviousArrow`**: `00`
* **`!ImpervSwordHammer`**: `00`
* **`!Boss`**: `00`
## Main Structure (`Sprite_RotatingTrack_Long`)
This routine handles the Switch Track's drawing and dispatches to its main logic if the sprite is active.
```asm
Sprite_RotatingTrack_Long:
{
PHB : PHK : PLB
JSR Sprite_RotatingTrack_Draw
JSL Sprite_CheckActive : BCC .SpriteIsNotActive
JSR Sprite_RotatingTrack_Main
.SpriteIsNotActive
PLB
RTL
}
```
## Initialization (`Sprite_RotatingTrack_Prep`)
This routine initializes the Switch Track upon spawning. It sets `SprDefl, X` to `$80`. It then calculates the tile attributes of the tile directly above the switch track and sets `SprAction, X` based on the `SPRTILE` value (normalized by subtracting `$D0`). This `SprAction, X` likely determines the initial mode or orientation of the track.
```asm
Sprite_RotatingTrack_Prep:
{
PHB : PHK : PLB
LDA.b #$80 : STA.w SprDefl, X
; Setup Minecart position to look for tile IDs
; We use AND #$F8 to clamp to a 8x8 grid.
; Subtract 8 from the Y position to get the tile right above instead.
LDA.w SprY, X : AND #$F8 : SEC : SBC.b #$08 : STA.b $00
LDA.w SprYH, X : STA.b $01
LDA.w SprX, X : AND #$F8 : STA.b $02
LDA.w SprXH, X : STA.b $03
; Fetch tile attributes based on current coordinates
LDA.b #$00 : JSL Sprite_GetTileAttr
LDA.w SPRTILE : SEC : SBC.b #$D0 : STA.w SprAction, X
PLB
RTL
}
```
## Constants
* **`SwitchRam = $0230`**: A WRAM address that stores the state (on/off) of each individual switch, indexed by its `SprSubtype`. This allows for multiple independent switch tracks.
## Main Logic & State Machine (`Sprite_RotatingTrack_Main`)
This routine manages the visual state of the Switch Track based on its `SprAction` (mode of rotation) and the corresponding switch state in `SwitchRam`.
* **Modes**: The `SprAction, X` determines the mode of rotation, with four defined modes:
* `0` = TopLeft -> TopRight
* `1` = BottomLeft -> TopLeft
* `2` = TopRight -> BottomRight
* `3` = BottomRight -> BottomLeft
* **State-Based Animation**: For each mode, the `SprFrame, X` (animation frame) is set based on the on/off state of the switch (`SwitchRam, Y`). This visually changes the track's orientation.
```asm
Sprite_RotatingTrack_Main:
{
; Get the subtype of the track so that we can get its on/off state.
LDA.w SprSubtype, X : TAY
LDA.w SprAction, X
JSL UseImplicitRegIndexedLocalJumpTable
dw TopLeftToTopRight
dw BottomLeftToTopLeft
dw TopRightToBottomRight
dw BottomRightToBottomLeft
; 00 = TopLeft -> TopRight
TopLeftToTopRight:
{
LDA.w SwitchRam, Y : BNE .part2
LDA.b #$00 : STA.w SprFrame, X
RTS
.part2
LDA.b #$01 : STA.w SprFrame, X
RTS
}
; 01 = BottomLeft -> TopLeft
BottomLeftToTopLeft:
{
LDA.w SwitchRam, Y : BNE .part2_c
LDA.b #$03 : STA.w SprFrame, X
RTS
.part2_c
LDA.b #$00 : STA.w SprFrame, X
RTS
}
; 02 = TopRight -> BottomRight
TopRightToBottomRight:
{
LDA.w SwitchRam, Y : BNE .part2_a
LDA.b #$01 : STA.w SprFrame, X
RTS
.part2_a
LDA.b #$02 : STA.w SprFrame, X
RTS
}
; 03 = BottomRight -> BottomLeft
BottomRightToBottomLeft:
{
LDA.w SwitchRam, Y : BEQ .part2_b
LDA.b #$03 : STA.w SprFrame, X
RTS
.part2_b
LDA.b #$02 : STA.w SprFrame, X
RTS
}
}
```
## Drawing (`Sprite_RotatingTrack_Draw`)
This routine handles OAM allocation and animation for the Switch Track. It explicitly uses `REP #$20` and `SEP #$20` for 16-bit coordinate calculations, ensuring accurate sprite rendering.
```asm
Sprite_RotatingTrack_Draw:
{
JSL Sprite_PrepOamCoord
LDA.b #$04 : JSL OAM_AllocateFromRegionB
LDA $0DC0, X : CLC : ADC $0D90, X : TAY;Animation Frame
LDA .start_index, Y : STA $06
PHX
LDX .nbr_of_tiles, Y ;amount of tiles -1
LDY.b #$00
.nextTile
PHX ; Save current Tile Index?
TXA : CLC : ADC $06 ; Add Animation Index Offset
PHA ; Keep the value with animation index offset?
ASL A : TAX
REP #$20
LDA $00 : STA ($90), Y
AND.w #$0100 : STA $0E
INY
LDA $02 : STA ($90), Y
CLC : ADC #$0010 : CMP.w #$0100
SEP #$20
BCC .on_screen_y
LDA.b #$F0 : STA ($90), Y ;Put the sprite out of the way
STA $0E
.on_screen_y
PLX ; Pullback Animation Index Offset (without the *2 not 16bit anymore)
INY
LDA .chr, X : STA ($90), Y
INY
LDA .properties, X : STA ($90), Y
PHY
TYA : LSR #2 : TAY
LDA.b #$02 : ORA $0F : STA ($92), Y ; store size in oam buffer
PLY : INY
PLX : DEX : BPL .nextTile
PLX
RTS
.start_index
db $00, $01, $02, $03
.nbr_of_tiles
db 0, 0, 0, 0
.chr
db $44
db $44
db $44
db $44
.properties
db $3D
db $7D
db $FD
db $BD
}
```
## Design Patterns
* **Interactive Puzzle Element**: The Switch Track is a key puzzle element that changes its orientation based on an external switch (likely the `mineswitch` sprite), directly influencing the path of minecarts.
* **State-Based Animation**: The track's animation frame (`SprFrame, X`) is directly controlled by the on/off state of a corresponding switch in `SwitchRam`, providing clear visual feedback to the player about its current configuration.
* **Subtype-Driven State**: The `SprSubtype` is used to index into `SwitchRam`, allowing each individual Switch Track to maintain its own independent state. This enables complex puzzle designs with multiple, distinct switch tracks.
* **16-bit OAM Calculations**: Demonstrates explicit use of `REP #$20` and `SEP #$20` for precise 16-bit OAM coordinate calculations, crucial for accurate sprite rendering.

28
Docs/Sprites/Overlord.md Normal file
View File

@@ -0,0 +1,28 @@
# Overlord Sprite Analysis
This document provides an analysis of the "Overlord" sprite system, which is a special type of sprite that acts as a controller for spawning other sprites or triggering events within a room. The main logic is found in `Sprites/overlord.asm`.
## Overview
Overlord sprites are invisible, non-interactive sprites that are placed in a room via a level editor. Their purpose is to run logic in the background, often tied to room-specific events or conditions. They are distinct from standard sprites and are processed by a separate loop.
In this project, the primary use of the Overlord system is to dynamically spawn soldiers in Hyrule Castle after the player acquires the Master Sword.
## `overlord.asm` Analysis
- **File:** `Sprites/overlord.asm`
- **Summary:** This file contains the logic for `Overlord04`, which is hooked into the game at `$09B7AE`. This specific overlord is responsible for continuously spawning soldiers in Hyrule Castle to create a sense of alarm and danger.
### Key Logic
- **`Overlord_KalyxoCastleGuards`:** This is the main entry point for the overlord's logic. It is a simple routine that calls `SummonGuards`.
- **`SummonGuards`:**
- **Trigger Condition:** This routine first checks if Link has the Master Sword (`LDA.l Sword : CMP.b #$02`). It will only proceed if the sword level is 2 or greater.
- **Spawning Logic:** If the condition is met, it calls `Overlord_SpawnSoldierPath`.
- **`Overlord_SpawnSoldierPath`:**
- **Spawn Timer:** This routine uses `OverlordTimerB` as a countdown timer to manage the rate of spawning. It will not spawn a new soldier until the timer reaches zero.
- **Sprite Limit:** It checks the number of active soldiers (`Sprite Type $41`) on screen. If there are already 5 or more, it will not spawn a new one.
- **Spawning:** If the conditions are met, it calls `Sprite_SpawnDynamically_slot_limited` to create a new Blue Soldier (`$41`).
- **Positioning:** The new soldier's position and initial direction are determined by data tables within the routine (`soldier_position_x`, `soldier_position_y`, `soldier_direction`), allowing for multiple spawn points.

113
Docs/Sprites/Overlords.md Normal file
View File

@@ -0,0 +1,113 @@
# Overlord Sprite System Analysis
This document provides a comprehensive analysis of the "Overlord" sprite system, a special class of sprite used for room-level event scripting and control.
## 1. What is an Overlord?
An Overlord is an invisible, non-interactive sprite placed in a room via a level editor. Unlike normal sprites (enemies, NPCs), their purpose is not to interact directly with Link, but to execute logic in the background. They function as "room controllers" or "event triggers."
Common uses for Overlords include:
- Spawning other sprites under specific conditions.
- Modifying the environment (e.g., creating falling tiles, moving floors).
- Coordinating the behavior of multiple other sprites.
- Setting up room-specific traps or puzzles.
In `Oracle of Secrets`, the most prominent example is `Overlord04`, which is used to continuously spawn soldier sprites in Hyrule Castle after Link acquires the Master Sword, creating a sense of alarm.
## 2. How Overlords Work: The Engine Loop
The core logic for the Overlord system resides in **Bank $09** of the vanilla ROM.
1. **Main Entry Point:** The standard sprite processing loop (`bank_06`) calls `JSL Overlord_Main` (at `$068398`), which jumps to the main overlord handler at **`$09B770`**.
2. **Execution Loop (`Overlord_ExecuteAll`):** At `$09B781`, the routine `Overlord_ExecuteAll` begins. It loops five times, once for each of the five available overlord "slots" in RAM. In each iteration, it calls `Overlord_ExecuteSingle`.
3. **Single Overlord Execution (`Overlord_ExecuteSingle`):** This routine, starting at `$09B791`, is the heart of the system. For a given overlord slot, it performs these steps:
a. It calls `Overlord_CheckIfActive` (`$09C08A`) to see if the slot contains an active overlord.
b. It reads the **Overlord Type** from WRAM address `$0F90,X` (where X is the overlord slot 0-4).
c. It uses this Type ID as an index into a jump table located at **`$09B7A8`**.
d. It executes the routine pointed to by the jump table entry, running the specific logic for that overlord type.
## 3. Overlord Jump Table
The jump table at `$09B7A8` is the key to customizing overlords. It contains pointers to the code for each of the 26 (1A) possible overlord types.
| Address | Vanilla Label | Oracle of Secrets Usage |
|---------------|------------------------------|----------------------------------------------|
| `$09B7A8` | `Overlord01_PositionTarget` | Unused |
| `$09B7AA` | `Overlord02_FullRoomCannons` | Unused |
| `$09B7AC` | `Overlord03_VerticalCannon` | Unused |
| **`$09B7AE`** | `Overlord04_Unused` | **Hooked for `Overlord_KalyxoCastleGuards`** |
| `$09B7B0` | `Overlord05_FallingStalfos` | Unused |
| ... | ... | ... |
`Oracle of Secrets` replaces the pointer at `$09B7AE` to point to its own custom logic for the castle guard spawner.
## 4. Overlord RAM Data Structure
Each of the five active overlords has its data stored in a series of arrays in WRAM, indexed by the overlord slot (0-4).
| Address | Description |
|-----------|-----------------------------------------------------------------------------------------------------------------------------------|
| `$0F90,X` | **Overlord Type:** The ID (1-26) that determines which logic to run via the jump table. |
| `$0FA0,X` | **Overlord State/Action:** The current state of the overlord's internal state machine, similar to `SprAction` for normal sprites. |
| `$0FB0,X` | **Overlord Timer A:** A general-purpose timer. |
| `$0FC0,X` | **Overlord Timer B:** A second general-purpose timer. |
| `$0FD0,X` | **Overlord Timer C:** A third general-purpose timer. |
| `$0B08,X` | X-Coordinate (and other properties, loaded from room data). |
| `$0B10,X` | Y-Coordinate (and other properties, loaded from room data). |
When a room is loaded, the `Underworld_LoadSingleOverlord` (`$09C35A`) or `Overworld_LoadSingleOverlord` (`$09C779`) routines read the overlord data defined in the level editor and populate these WRAM slots.
## 5. Creating a Custom Overlord
To create your own custom overlord, follow these steps:
1. **Choose an Overlord Slot:** Find an unused overlord type in the jump table at `$09B7A8`. `Overlord04` is already taken, but others may be available. For this example, let's assume you choose to replace `Overlord05_FallingStalfos` at `$09B7B0`.
2. **Write Your Logic:** Create a new `.asm` file (e.g., `Sprites/Overlords/my_custom_overlord.asm`) or add to the existing `Sprites/overlord.asm`. Your code should define the main routine for your overlord.
```asm
; In my_custom_overlord.asm
MyCustomOverlord_Main:
{
; Your logic here.
; You can use the Overlord Timers and State registers.
; For example, let's use the state register to create a simple two-state machine.
LDA.w $0FA0,X ; Load the current state
JSL JumpTableLocal
dw .State0_Wait
dw .State1_DoSomething
.State0_Wait
; Decrement Timer A. When it hits zero, switch to state 1.
LDA.w $0FB0,X : BNE .keep_waiting
INC.w $0FA0,X ; Go to state 1
LDA.b #$80 : STA.w $0FB0,X ; Reset timer
.keep_waiting
RTS
.State1_DoSomething
; Do something, like spawn a sprite.
; Then go back to state 0.
LDA.b #<Sprite_ID>
JSL Sprite_SpawnDynamically
STZ.w $0FA0,X ; Go back to state 0
RTS
}
```
3. **Hook into the Jump Table:** In a file that is included in your main build file (like `Core/patches.asm`), add an `org` directive to overwrite the vanilla pointer in the jump table.
```asm
; In Core/patches.asm
incsrc ../Sprites/Overlords/my_custom_overlord.asm
pushpc
org $09B7B0 ; Address for Overlord05
dw MyCustomOverlord_Main ; Replace pointer with your routine
pullpc
```
4. **Place in a Room:** Use your level editor to place an "Overlord" object in a room. Set its **Type** to the one you chose (e.g., `05`). When the room is loaded, the game will load your overlord into an active slot, and the main loop will execute your custom code.

View File

@@ -0,0 +1,432 @@
# Probe Sprite System
**Last Updated:** October 3, 2025
**Purpose:** Document the probe sprite detection system used by guards and intelligent enemies
---
## Overview
The **Probe Sprite System** is a collision detection mechanism used by guard-type sprites (Blue Guard, Green Guard, Red Guard) and intelligent enemies to detect Link's presence. Instead of constantly checking collision with Link directly, these enemies spawn invisible "probe" sprites that move in the direction they're facing. When a probe makes contact with Link, it triggers the parent sprite to enter an alert/chase state.
This system is more efficient than constant direct collision checks and creates more realistic enemy behavior where guards react when Link enters their "line of sight."
---
## How It Works
### 1. Probe Spawning
When an enemy wants to check for Link in a specific direction, it spawns a probe sprite:
```asm
; From Sprites/Enemies/darknut.asm
JSL GetDistance8bit_Long : CMP.b #$80 : BCS .no_probe
JSL Sprite_SpawnProbeAlways_long ; Spawn probe if Link is nearby
.no_probe
```
**Key Details:**
- Probes are spawned only when Link is within range (distance < $80)
- Uses sprite ID $41 (Blue Guard sprite slot)
- Probes are invisible and have no collision with tiles
- Probes live for a very short time (usually < 1 second)
### 2. Probe Properties
When spawned, probes are configured with special properties:
```asm
; From Sprites/experimental/probe.asm (reference)
Sprite_SpawnProbeAlways:
{
LDA.b #$41 ; Use guard sprite ID
LDY.b #$0A ; Sprite slot limit
JSL Sprite_SpawnDynamically_slot_limited
BMI .exit
; Set position (slightly offset from spawner)
LDA.b $00 : CLC : ADC.b #$08 ; +8 pixels X
STA.w SprX, Y
LDA.b $02 : CLC : ADC.b #$04 ; +4 pixels Y
STA.w SprY, Y
; Set velocity based on direction
LDA.w .speed_x, X
STA.w SprXSpeed, Y
LDA.w .speed_y, X
STA.w SprYSpeed, Y
; Store parent sprite ID
TXA : INC A
STA.w $0DB0, Y ; Parent sprite index + 1
STA.w $0BA0, Y
; Set timers
LDA.b #$40
STA.w $0F60, Y ; Lifetime timer
STA.w $0E60, Y
LDA.b #$02
STA.w $0CAA, Y ; Priority
.exit
RTS
}
```
**Speed Tables:**
```asm
.speed_x: ; X velocities for 64 directions
db $00, $01, $03, $04, $05, $06, $07, $08
db $08, $08, $07, $06, $05, $04, $03, $01
db $00, -$01, -$03, -$04, -$05, -$06, -$07, -$08
; ... (continues for all 64 directions)
.speed_y: ; Y velocities for 64 directions
db -$08, -$08, -$07, -$06, -$05, -$04, -$03, -$01
db $00, $01, $03, $04, $05, $06, $07, $08
; ... (continues for all 64 directions)
```
### 3. Probe Detection Logic
The probe sprite checks for collision every frame:
```asm
Probe:
{
; Move the probe
LDY.b #$00
LDA.w SprXSpeed, X : BPL .positive_x
DEY
.positive_x
CLC : ADC.w SprX, X : STA.w SprX, X
TYA : ADC.w SprXH, X : STA.w SprXH, X
; Same for Y...
; Check if probe hit Link
REP #$20
LDA.w $0FD8 : SEC : SBC.b $22 ; Link X - Probe X
CLC : ADC.w #$0010 : CMP.w #$0020 ; Within hitbox?
SEP #$20
BCS .no_contact
REP #$20
LDA.b $20 : SEC : SBC.w $0FDA ; Link Y - Probe Y
CLC : ADC.w #$0018 : CMP.w #$0020
SEP #$20
BCS .no_contact
; Check same floor layer
LDA.w $0F20, X : CMP.b $EE : BNE .no_contact
.made_contact
; Get parent sprite index from $0DB0
LDA.w $0DB0, X : DEC A : PHX : TAX
; Trigger parent sprite's alert state
LDA.w SprAction, X : CMP.b #$03 : BEQ .dont_trigger_parent
LDA.b #$03 : STA.w SprAction, X ; Set to chase state
LDA.b #$10 : STA.w SprTimerA, X ; Alert duration
STZ.w SprDelay, X
.dont_trigger_parent
PLX
.no_contact
; Probe has served its purpose, despawn
STZ.w $0DD0, X
RTS
}
```
### 4. Parent Sprite Response
When a probe detects Link, the parent sprite (guard/enemy) reacts:
```asm
; From darknut.asm
LDA.w SprTimerD, X : BEQ .not_alerted
; Probe detected Link - chase behavior
LDA.b #$08 : JSL Sprite_ApplySpeedTowardsPlayer
JSL Sprite_DirectionToFacePlayer
TYA
STA.w SprMiscC, X ; Store facing direction
STA.w SprMiscE, X
STA.w SprAction, X
JSL Guard_ChaseLinkOnOneAxis
JMP .continue
.not_alerted
; Normal patrol behavior
JSR Sprite_Darknut_BasicMove
.continue
```
---
## Usage in Enemies
### Darknut Example
The Darknut uses probes for detection:
```asm
Sprite_Darknut_Main:
{
; Only spawn probe if Link is nearby
JSL GetDistance8bit_Long : CMP.b #$80 : BCS .no_probe
JSL Sprite_SpawnProbeAlways_long
.no_probe
; Check if probe triggered alert
LDA.w SprTimerD, X : BEQ .not_alerted
LDA.b #$90 : STA.w SprTimerD, X ; Refresh alert timer
; ... chase logic ...
.not_alerted
; ... patrol logic ...
}
```
### Guard Example (Vanilla)
Vanilla guards use a more sophisticated probe system:
```asm
Guard_ShootProbeAndStuff:
{
; Calculate probe direction based on guard facing
LDA.w SprMiscC, X ; Guard's facing direction
; Use direction tables to set probe velocity
LDY.b #$00
LDA.w ProbeAndSparkCheckDirXSpeed, Y : STA.b $00
LDA.w ProbeAndSparkCheckDirYSpeed, Y : STA.b $01
; Check tile collision before spawning
JSL Probe_CheckTileSolidity : BCC .passable
; Don't spawn probe if blocked by wall
RTS
.passable
; Spawn the probe
JSR Sprite_SpawnProbeAlways
; Set probe type and properties
LDA.w ProbeType, Y : STA.w $0DB0, Y
}
```
---
## Best Practices
### When to Use Probes
**Good Use Cases:**
- Guard-type enemies with "line of sight" detection
- Enemies that patrol and should react when Link crosses their path
- Boss phases where the boss "looks" for Link
- Security systems or alert mechanisms
**Poor Use Cases:**
- Enemies that should always chase Link (use direct distance checks)
- Fast-moving enemies (probes are too slow)
- Enemies that need precise collision (use `Sprite_CheckDamageToLink`)
- Passive enemies that don't react to Link
### Performance Considerations
1. **Distance Check First:** Always check if Link is nearby before spawning probes
```asm
JSL GetDistance8bit_Long : CMP.b #$80 : BCS .skip_probe
```
2. **Spawn Frequency:** Don't spawn probes every frame
```asm
LDA.w SprTimerA, X : BNE .dont_spawn ; Only spawn when timer expires
```
3. **Probe Lifetime:** Keep probes short-lived (max 64 frames / ~1 second)
4. **Sprite Slot Limit:** Probes use `Sprite_SpawnDynamically_slot_limited` with a limit of $0A (10 sprites max)
### Common Patterns
**Pattern 1: Continuous Scanning**
```asm
; Spawn probe every N frames
LDA.w SprTimerA, X : BNE .skip
LDA.b #$20 : STA.w SprTimerA, X ; Every 32 frames
JSL Sprite_SpawnProbeAlways_long
.skip
```
**Pattern 2: Direction-Based Detection**
```asm
; Only probe in facing direction
LDA.w SprAction, X ; Current facing direction
ASL A : TAY
LDA.w .probe_angles, Y ; Get angle for this direction
JSL Sprite_SpawnProbeAlways_long
```
**Pattern 3: Alert State Management**
```asm
; Probe triggers alert, which decays over time
LDA.w SprTimerD, X : BEQ .calm
; Alert state - chase Link
DEC.w SprTimerD, X ; Decay alert
BNE .still_alert
; Return to patrol when timer expires
JSR Enemy_ReturnToPatrol
.still_alert
JSR Enemy_ChaseLink
RTS
.calm
JSR Enemy_Patrol
RTS
```
---
## Integration with Other Systems
### Parrying System
Probes work alongside the guard parrying system:
```asm
; Darknut blocks sword attacks while alerted
LDA.w SprTimerD, X : BEQ .not_alert
JSL Guard_ParrySwordAttacks ; Active parrying when alert
.not_alert
```
### Multi-Part Sprites
For multi-part enemies (like Kydreeok), each segment can spawn probes:
```asm
; Head segment spawns probe
LDA.w SprSubtype, X : BEQ .not_head
JSL Sprite_SpawnProbeAlways_long
.not_head
```
### Boss Mechanics
Bosses can use probes for phase transitions:
```asm
; Boss becomes aggressive when probe detects Link
.phase_check
LDA.w SprTimerD, X : BEQ .passive_phase
LDA.b #$02 : STA.w SprAction, X ; Aggressive phase
JMP .continue
.passive_phase
; Spawn probe occasionally
LDA.w SprFrame : AND.b #$3F : BNE .continue
JSL Sprite_SpawnProbeAlways_long
.continue
```
---
## Debugging Probes
### Making Probes Visible
For debugging, you can make probes draw sprites:
```asm
; In probe sprite's main routine (normally invisible)
JSR Sprite_PrepOamCoord
LDA.b #$00 : JSL SpriteDraw_SingleLarge ; Draw debug sprite
```
### Checking Probe State
Use WRAM viewer in Mesen-S:
- `$0DB0,X` - Parent sprite index (should be parent + 1)
- `$0DD0,X` - Probe state (should be $09 = active)
- `$0F60,X` - Probe lifetime timer
### Common Issues
**Problem:** Probe doesn't despawn
- **Solution:** Ensure `STZ.w $0DD0, X` is called in all exit paths
**Problem:** Parent doesn't react
- **Solution:** Check that `$0DB0,X` correctly stores parent index + 1
**Problem:** Probe spawns too frequently
- **Solution:** Add distance check and timer between spawns
---
## WRAM Variables
### Probe-Specific Variables
| Address | Variable | Purpose |
|---------|----------|---------|
| `$0DB0,X` | Probe Parent Index | Parent sprite slot + 1 (0 = no parent) |
| `$0BA0,X` | Probe Parent Copy | Backup of parent index |
| `$0F60,X` | Probe Lifetime | Frames until probe despawns |
| `$0E60,X` | Probe Timer Copy | Backup lifetime timer |
| `$0CAA,X` | Probe Priority | Draw priority (usually $02) |
### Parent Sprite Variables
| Address | Variable | Purpose |
|---------|----------|---------|
| `SprTimerD,X` | Alert Timer | Frames remaining in alert state |
| `SprTimerA,X` | Spawn Cooldown | Frames until next probe spawn |
| `SprAction,X` | Behavior State | Current AI state (patrol/chase) |
---
## Reference Implementation
See `Sprites/experimental/probe.asm` for the complete vanilla probe system implementation. Key enemies using probes:
- **Blue/Green/Red Guards** (`Sprite_41`, `Sprite_42`, `Sprite_43`) - Full probe system
- **Darknut** (`Sprites/Enemies/darknut.asm`) - Simplified probe detection
- **Blind Boss** (Special case: probes check for boss-specific collision)
---
## Future Enhancements
### Proposed Features
1. **Directional Probes:** Spawn probes in multiple directions simultaneously
2. **Cone of Vision:** Multiple probes in a fan pattern for wider detection
3. **Probe Types:** Different probe behaviors (slow/fast, short/long range)
4. **Team Alerts:** Probes trigger multiple nearby enemies
5. **Sound Integration:** Play alert sound when probe detects Link
### Castle Ambush System
Probes will be used in the upcoming castle ambush feature:
- Guards patrol castle corridors
- Probes detect Link entering restricted areas
- Detection triggers reinforcement spawn
- Multiple guards share alert state
- Capture sequence activates on detection
See: `Core/capture.asm` and `Sprites/Enemies/custom_guard.asm` (experimental)
---
## See Also
- `Docs/Sprites/Enemies/Darknut.md` - Darknut implementation with probes
- `Docs/Guides/SpriteCreationGuide.md` Section 10.9 - Advanced AI Patterns
- `Core/sprite_functions.asm` - `Sprite_SpawnProbeAlways_long` function
- `Core/symbols.asm` - Probe-related function addresses

View File

@@ -0,0 +1,91 @@
# Dungeons & Indoor Areas
This document details the various systems and enhancements for dungeons and other indoor areas found within the `Dungeons/` directory. These systems provide a framework for creating unique puzzles, mechanics, and environmental behaviors that go far beyond the vanilla game's capabilities.
## 1. Overview
The code in this directory can be broadly categorized into three main areas:
1. **Custom Room Tags:** New, scriptable room behaviors that can be assigned to any room to create special events or puzzles.
2. **Enhanced Mechanics & Objects:** Modifications or additions to existing dungeon elements like spikes, doors, and enemy stats.
3. **Advanced Collision & Object Rendering:** A powerful, layered system for defining tile collision and drawing complex, multi-tile objects.
## 2. Custom Room Tags
Room "tags" are a vanilla mechanic that allows a room to have special properties (e.g., a kill room, a room with shutter doors). This project expands on this by hooking into the tag processing routines to add new, custom behaviors.
### Floor Puzzle (`floor_puzzle.asm`)
- **Hook:** Replaces Tag `0x00` (`holes_0`).
- **Functionality:** Implements a "light all the tiles" puzzle. When Link steps on a special "off" tile (`$0DED`), it transforms into an "on" tile (`$0DEE`) and plays a sound. The system then checks if any "off" tiles remain.
- If all tiles are on, it opens the room's shutter doors (`$0468`) and plays the secret sound.
- If the player steps on a tile that is already "on", it can trigger a kill room effect (`STZ.b $AE`).
### Crumble Floor (`crumblefloor_tag.asm`)
- **Hook:** Replaces Tag `0x03` (`holes_3`).
- **Functionality:** Creates floors that crumble away after being walked on.
- It tracks the tile Link is currently standing on.
- If he steps on a specific "crumble" tile (`$0C62` or `$0C63`), the code replaces it with a cracked tile and then a pit tile, spawning a falling tile visual effect (`Garnish 03`).
- This is designed for creating temporary paths or "don't stop running" challenges.
### Positional Warp (`together_warp_tag.asm`)
- **Hook:** Replaces Tag `0x08` (`Holes8`).
- **Functionality:** Changes the room's warp destination based on the player's position. It divides the room into four quadrants and sets the target room index based on which quadrant the player is in when they trigger a warp (e.g., by falling in a pit). This allows a single room to lead to four different destinations.
### Minish Shutter Door (`custom_tag.asm`)
- **Hook:** Replaces Tag `0x05` (`Holes5`).
- **Functionality:** Creates a shutter door that only opens if the player is in Minish Form (`!CurrentMask == 0x05`). If the condition is met, it opens the door and plays the corresponding sound effect.
### Intro Cutscene (`custom_tag.asm`)
- **Hook:** Replaces Tag `0x39` (`Holes7`).
- **Functionality:** This tag is repurposed to control the game's opening cutscene in Link's house. It's a state machine that handles:
1. Displaying the initial telepathic message from Farore.
2. Gradually lighting up the screen.
3. Waking Link from his bed and giving control to the player.
4. Setting the `GameState` flags to permanently prevent the "Uncle" sprite from appearing in the house again.
## 3. Dungeon Mechanics & Objects
### Key Blocks (`keyblock.asm`)
- **Functionality:** Replaces the vanilla "prison door" object with a lock block that requires a small key.
- **Implementation:** It hooks the object's interaction routine (`$01EB8C`). Before running the vanilla code to open the door, it checks the player's small key count (`$7EF36F`). If the player has one or more keys, it decrements the count and opens the block. If not, the block remains solid.
### Spike Block Subtypes (`spike_subtype.asm`)
- **Functionality:** Expands the vanilla spike block (trap) to allow for different speeds and directions.
- **Implementation:** It hooks the sprite preparation routine for the spike block (`$0691D7`). It reads the sprite's subtype value and uses it as an index into two tables, `speedValuesH` and `speedValuesV`, to set its horizontal and vertical speed. This allows for placing spikes that move vertically or at various speeds, configured directly in the sprite editor.
## 4. Advanced Collision System
The project features a powerful, three-tiered system for handling tile collision, offering immense flexibility.
### Layer 1: Global Collision Patches (`GlobalCollisionTables.asm`)
- **Purpose:** To make baseline changes to the game's default tile behaviors.
- **Implementation:** This file contains a series of `org` patches that directly overwrite data in the vanilla global tile property tables located in ROM bank `$0E`. These tables define the default physical properties of every 16x16 tile in the game (e.g., solid, water, pit, stairs).
### Layer 2: Tileset-Specific Collision (`CollisionTablesExpanded.asm`)
- **Purpose:** To allow the same tile graphic to have different behaviors in different dungeons (e.g., a normal floor tile that becomes slippery in an ice dungeon).
- **Implementation:** It hooks the dungeon tile attribute loading routine (`$0E942A`). The new routine, `Dungeon_LoadCustomTileAttr`, checks the current dungeon's tileset ID (`$0AA2`) and loads an entire set of custom tile properties from a group of tables. This allows, for example, "Glacia Estate" (`group0B`) to have unique ice physics while "Goron Mines" (`group04`) has its own set of properties for minecart tracks.
### Layer 3: Per-Room Custom Collision (`custom_collision.asm`)
- **Purpose:** To provide the highest level of granularity by defining collision on a room-by-room basis, overriding all other rules.
- **Implementation:** The `CustomRoomCollision` routine hooks the room loading process (`$01B95B`). It uses the current room ID (`$A0`) to look up a pointer in a table at `$258090`. If a pointer exists for the current room, it reads a block of custom collision data and writes it directly to the active collision map in WRAM (`$7E2000+`). This is used for creating unique and complex room layouts that would be impossible with the standard grid-based tile system.
## 5. Custom Object Handler (`Dungeons/Objects/object_handler.asm`)
- **Purpose:** To render complex, multi-tile dungeon objects that are not sprites and cannot be created with the vanilla object system.
- **Implementation:** This system hooks the vanilla object drawing routine for several reserved object IDs (e.g., `$31`, `$32`, `$54`). When the game attempts to draw one of these objects, the custom handler (`CustomObjectHandler`) is called instead.
- **Data-Driven:** The handler reads the object's properties and looks up its corresponding graphical data from a series of `.bin` files included from the `Dungeons/Objects/Data/` directory. It then manually draws the object tile by tile. This is used to render things like minecart tracks, custom boss parts (`KydreeokBody`), and detailed scenery like the `IceFurnace`.
## 6. Miscellaneous Patches
- **`enemy_damage.asm`:** Contains `org` patches that directly modify enemy property tables to change their bump damage values.
- **`house_walls.asm`:** Contains `org` patches that modify tilemap data for house walls, likely for cosmetic changes.
- **`attract_scenes.asm`:** Modifies the game's "attract mode" (the gameplay demos on the title screen) to create custom scenes that take place in dungeon environments.

View File

@@ -0,0 +1,103 @@
# Overworld Systems Analysis
## 1. Overview
The `Overworld/` directory contains all code and data related to the game's overworld, including rendering, transitions, time-based events, and custom features. The architecture is centered around `ZSCustomOverworld.asm` (ZSOW), a powerful data-driven system that replaces most of the vanilla game's hardcoded overworld logic.
The primary goal of the overworld code is to provide a flexible and expandable framework for creating a dynamic world. This is achieved by hooking into the original game's engine and replacing static logic with routines that read from configurable data tables in expanded ROM space.
## 2. Core Systems
These two systems form the backbone of the custom overworld engine.
### 2.1. `ZSCustomOverworld.asm` (ZSOW)
ZSOW is the heart of the overworld engine. It replaces vanilla logic for palettes, graphics, overlays, transitions, and sprite loading with a highly configurable, data-driven approach. Its behavior is defined by a large pool of data tables starting at `org $288000`.
**Key Responsibilities:**
* **Data-Driven Configuration:** Reads from tables like `.MainPaletteTable`, `.OWGFXGroupTable`, and `.OverlayTable` to define the look and feel of each of the 160 overworld screens.
* **Flexible Layouts:** Fixes vanilla transition bugs and adds support for non-standard area sizes (e.g., 2x1 "wide" and 1x2 "tall" areas) via custom camera boundary tables (`.ByScreen..._New`).
* **Dynamic Sprite Loading:** Uses the `.Overworld_SpritePointers_state_..._New` tables to load different sprite sets based on the current game state (`$7EF3C5`), allowing enemy and NPC populations to change as the story progresses.
* **Extensive Hooks:** Intercepts dozens of vanilla routines to apply its custom logic. Key hooks include `PreOverworld_LoadProperties_Interupt` (`$0283EE`) for loading area properties, `OverworldHandleTransitions` (`$02A9C4`) for screen transitions, and `LoadOverworldSprites_Interupt` (`$09C4C7`) for sprite loading.
### 2.2. `time_system.asm`
This system implements a full 24-hour day/night cycle, which is crucial for many of the game's puzzles and atmospheric effects.
**Key Features:**
* **In-Game Clock:** Maintains the current time in SRAM (`Hours` at `$7EE000`, `Minutes` at `$7EE001`).
* **Palette Modulation:** The `ColorSubEffect` routine dynamically modifies palettes written to CGRAM to simulate changing light levels. It uses lookup tables to determine the correct color subtraction values for each hour.
* **Time-Based Events:** Includes logic for daily events (e.g., the Magic Bean side-quest) and handling time manipulation via the Song of Time.
* **HUD Integration:** The `DrawClockToHud` routine displays the current time on the player's HUD.
## 3. Sub-Systems and Features
These files implement specific, modular overworld features.
### 3.1. `entrances.asm`
This file expands the vanilla entrance system, allowing for more complex and custom door behaviors.
* It hooks the main entrance routine (`$1BBBF4`) to call `Overworld_UseEntrance`, which uses an expanded list of valid door tile types (`ValidDoorTypesExpanded`).
* It contains the logic to check for follower restrictions and other entry conditions before transitioning the player to an interior map.
### 3.2. `overlays.asm`
This system manages complex, animated overlays for special entrances that are triggered on the overworld map, such as opening a dungeon.
* It defines multi-frame animation sequences for events like the Zora Temple waterfall parting, the castle drawbridge lowering, and the Fortress of Secrets entrance opening.
* Each animation is a state machine that uses a timer (`$C8`) and a frame counter (`$B0`) to step through a sequence of tile-drawing and screen-shaking routines.
### 3.3. `lost_woods.asm`
This implements the classic "repeating maze" puzzle for the Lost Woods (Area `$29`).
* It hooks into the overworld transition logic to check if the player is exiting the Lost Woods screen.
* It compares the player's exit direction against a correct, predefined sequence.
* If the sequence is wrong, it manually manipulates the player and camera coordinates to loop them back to the same screen, creating the illusion of being lost.
### 3.4. `special_areas.asm`
This file enhances the functionality of vanilla "special overworld" areas (like the Master Sword grove), allowing them to be used as full-featured screens.
* `Overworld_CheckForSpecialOverworldTrigger` checks if the player is interacting with a tile that should lead to a special area.
* `LoadSpecialOverworld` is a critical function that sets up the unique properties for these areas, including camera boundaries, palettes, and GFX, by reading from its own set of data tables. This allows for more than the original game's limited number of special areas.
### 3.5. `custom_gfx.asm`
This file contains routines for loading custom graphics sheets into VRAM for specific overworld areas or events. The primary example is `CheckForChangeGraphicsNormalLoadBoat`, which loads custom boat graphics when the player is in area `$30`.
### 3.6. `world_map.asm`
This file contains significant modifications to the full-screen world map.
* It replaces the vanilla icon drawing logic with custom routines (`DrawPowerPendant`, `DrawMasterSwordIcon`, etc.) to display the status of new quest items and dungeons.
* It includes logic to display different icons based on Light World vs. Dark World and overall game progression (`OOSPROG`).
* It implements custom DMA routines (`DMAOwMap`, `DMAOwMapGfx`) to load entirely new world map tilesets and graphics from expanded ROM (`$408000` and `$418000`).
## 4. System Interactions & Porting Status
Integrating ZSOW with existing custom systems is an ongoing effort. The status of these interactions is critical for development.
* **Time System (Palette Modulation):** **Compatible.** The Time System's `LoadDayNightPaletteEffect` acts as a filter on all CGRAM writes. When ZSOW loads a new base palette for an area, the Time System intercepts the write and applies the day/night color subtraction automatically.
* **Day/Night Sprites:** **Resolved.** The conflict where ZSOW's sprite loader bypassed the old day/night logic has been fixed. A `JSL CheckIfNight` call is now integrated directly into ZSOW's `LoadOverworldSprites_Interupt`. This allows ZSOW's sprite tables to correctly load different sprite sets for day and night by using adjacent game states (e.g., state 2 for day, state 3 for night).
* **Lost Woods Puzzle:** **Direct Conflict.** The Lost Woods puzzle's transition override is currently incompatible with ZSOW's more complex transition handler. The `lost_woods.asm` code needs to be refactored into a subroutine that can be called from within `OverworldHandleTransitions` in `ZSCustomOverworld.asm`.
* **Song of Storms (Overlays):** **Resolved.** The conflict where ZSOW would overwrite the rain overlay on screen transitions has been fixed. A new SRAM flag (`SRAM_StormsActive`) tracks the storm state, and a new routine, `HandleStormsOverlay`, is called every frame to enforce the rain overlay if the flag is active, ensuring it persists across transitions.
## 5. File Index
* `ZSCustomOverworld.asm`: The core data-driven overworld engine. Manages palettes, GFX, overlays, transitions, and sprite loading.
* `time_system.asm`: Manages the 24-hour clock, day/night palette effects, and time-based events.
* `overworld.asm`: Main include file for the directory; contains various small patches.
* `entrances.asm`: Handles logic for entering caves, houses, and dungeons from the overworld.
* `overlays.asm`: Manages animated entrance sequences (e.g., waterfalls, drawbridges).
* `lost_woods.asm`: Implements the Lost Woods maze puzzle.
* `special_areas.asm`: Expands the functionality of special overworld areas like the Master Sword grove.
* `custom_gfx.asm`: Routines for loading custom graphics for specific areas or objects.
* `world_map.asm`: Code for the custom full-screen world map, including new icons and map graphics.
* `HardwareRegisters.asm`: `struct` definitions for SNES hardware registers, used for context.

View File

@@ -0,0 +1,74 @@
# Time System (`Overworld/time_system.asm`)
## Overview
This system manages the in-game clock, day/night cycle, and associated palette effects. It runs continuously, updating the time and adjusting visual elements like the sky color and sprite palettes based on the current hour.
## Key Functionality
- **Clock:** A 24-hour clock is maintained in SRAM (`Hours` at `$7EE000`, `Minutes` at `$7EE001`).
- **Palette Modulation:** The core of the system is `ColorSubEffect`, which subtracts values from the red, green, and blue components of palettes based on the time of day, using lookup tables.
- **Time-Based Events:** The system checks for daily events (like the Magic Bean quest) and handles time manipulation effects (like the Song of Time).
- **HUD Display:** It includes logic to draw the current time to the HUD.
## Analysis & Areas for Improvement
The time system is functional but could be significantly improved in terms of structure, readability, and maintainability.
### 1. Move Patches to `Core/patches.asm`
- **Observation:** The file contains numerous `org` patches that modify vanilla game logic to hook in the time system.
- **Suggestion:** Relocate all `org` blocks to the centralized `Core/patches.asm` file. This is the most important cleanup step.
- **Benefit:** This will separate the new system's implementation from the act of patching it into the original code, making both parts easier to understand and manage.
### 2. Use a `struct` for Time-Related Variables
- **Observation:** Time-related variables are defined as individual labels pointing to SRAM addresses (e.g., `Hours`, `Minutes`, `TimeSpeed`, `!BlueVal`).
- **Suggestion:** Group these related variables into a single `struct`.
*Example:*
```asm
struct TimeState
Hours db
Minutes db
TimeSpeed db
; ... other vars ...
BlueVal dw
GreenVal dw
RedVal dw
endstruct
; Then access with:
LDA TimeState.Hours, X
```
- **Benefit:** This provides a clear, high-level definition of the data structure, improves readability, and makes it easier to manage memory layout.
### 3. Use `subroutine` for Code Blocks
- **Observation:** The file consists of many large, labeled blocks of code (e.g., `RunClock`, `DrawClockToHud`, `ColorSubEffect`).
- **Suggestion:** Convert these blocks to use `subroutine`/`endsubroutine`.
- **Benefit:** This clearly defines the scope of each piece of logic, makes labels within them local by default, and improves overall code structure.
### 4. Refactor Large Subroutines
- **Observation:** `RunClock` is a very large and complex subroutine with multiple responsibilities and deep nesting.
- **Suggestion:** Break `RunClock` into smaller, more focused subroutines.
- `TimeSystem_CheckCanRun`: A subroutine to check the game state (`$10`, `$11`) and decide if the clock should tick.
- `TimeSystem_IncrementTime`: A subroutine to handle the core logic of incrementing minutes and hours.
- `TimeSystem_UpdatePalettes`: A subroutine to call the palette update logic when the hour changes.
- **Benefit:** Smaller, single-purpose functions are easier to read, debug, and maintain.
### 5. Replace Magic Numbers with Constants
- **Observation:** The code is replete with hardcoded values for time, palettes, and game states.
- **Suggestion:** Define constants for these values using `!` or `define()`.
*Example:*
```asm
!TIME_SPEED_NORMAL = $3F
!GAME_STATE_OVERWORLD = $09
LDA.b #!TIME_SPEED_NORMAL : STA.l TimeSpeed
LDA $10 : CMP #!GAME_STATE_OVERWORLD : BEQ .overworld
```
- **Benefit:** Makes the code self-documenting and reduces the risk of errors when modifying these values.

View File

@@ -0,0 +1,69 @@
# ZScream Custom Overworld (`Overworld/ZSCustomOverworld.asm`)
## 1. Overview
ZSCustomOverworld is a powerful and extensive system that replaces large parts of the vanilla *A Link to the Past* overworld engine. Its primary purpose is to remove hardcoded behaviors and replace them with a data-driven approach, allowing for a highly customizable overworld.
Instead of relying on hardcoded logic for palettes, graphics, and layouts, ZSCustomOverworld reads this information from a large pool of data tables located in expanded ROM space (starting at `$288000`). These tables are designed to be edited by the ZScream overworld editor.
## 2. Key Features
- **Custom Palettes & Colors:** Assign a unique main palette and background color to every overworld screen.
- **Custom Graphics:** Assign custom static tile graphics (GFX groups) and animated tile sets to each area.
- **Custom Overlays:** Add or remove subscreen overlays (like rain, fog, and clouds) on a per-area basis.
- **Flexible Layouts:** Fixes vanilla bugs related to screen transitions and adds support for new area sizes, such as 2x1 "wide" and 1x2 "tall" areas, in addition to the standard 1x1 and 2x2.
- **Expanded Special Worlds:** Allows the normally limited "special world" areas (like the Master Sword grove) to be used as full-featured overworld screens.
## 3. Core Architecture: Data Tables
The system's flexibility comes from a large data pool starting at `org $288000`. Key tables include:
- **`.BGColorTable`:** A table of 16-bit color values for the background of each overworld screen.
- **`.EnableTable`:** A series of flags to enable or disable specific features of ZSCustomOverworld, such as custom palettes or overlays.
- **`.MainPaletteTable`:** An index (`$00` to `$05`) into the game's main overworld palette sets for each screen.
- **`.MosaicTable`:** A bitfield for each screen to control mosaic transitions on a per-direction basis.
- **`.AnimatedTable`:** The GFX sheet ID for animated tiles for each screen.
- **`.OverlayTable`:** The overlay ID (e.g., `$9F` for rain) for each screen. `$FF` means no overlay.
- **`.OWGFXGroupTable`:** A large table defining the 8 GFX group sheets to be loaded for each overworld screen.
- **`.Overworld_ActualScreenID_New`:** A table that defines the "parent" screen for multi-screen areas (e.g., for a 2x2 area, all four screens point to the top-left screen's ID).
- **`.ByScreen..._New` Tables:** Four tables (`ByScreen1` for right, `2` for left, `3` for down, `4` for up) that define the camera boundaries for screen transitions. These are crucial for supporting non-standard area sizes.
- **`.Overworld_SpritePointers_state_..._New` Tables:** These tables define which sprite set to load for each overworld area based on the game state (`state_0` for the intro, `state_1` for post-Agahnim 1, `state_2` for post-Ganon). This allows for different enemy and NPC populations as the story progresses.
## 4. Key Hooks & Functions
ZSCustomOverworld replaces dozens of vanilla routines. Some of the most critical hooks are:
- `org $0283EE` (**`PreOverworld_LoadProperties_Interupt`**):
- **Original:** `Overworld_LoadProperties`. This function loads music, palettes, and GFX when transitioning from a dungeon/house to the overworld.
- **New Logic:** The ZS version is heavily modified to read from the custom data tables for palettes and GFX instead of using hardcoded logic. It also removes hardcoded music changes for certain exits.
- `org $02C692` (**`Overworld_LoadAreaPalettes`**):
- **Original:** A routine to load overworld palettes.
- **New Logic:** Reads the main palette index from the `.MainPaletteTable` instead of using a hardcoded value.
- `org $02A9C4` (**`OverworldHandleTransitions`**):
- **Original:** The main logic for handling screen-to-screen transitions on the overworld.
- **New Logic:** This is one of the most heavily modified sections. The new logic uses the custom tables (`.ByScreen...`, `.Overworld_ActualScreenID_New`, etc.) to handle transitions between areas of different sizes, fixing vanilla bugs and allowing for new layouts.
- `org $02AF58` (**`Overworld_ReloadSubscreenOverlay_Interupt`**):
- **Original:** Logic for loading subscreen overlays.
- **New Logic:** Reads the overlay ID from the `.OverlayTable` instead of using hardcoded checks for specific areas (like the Misery Mire rain).
- `org $09C4C7` (**`LoadOverworldSprites_Interupt`**):
- **Original:** `LoadOverworldSprites`. This function determines which sprites to load for the current overworld screen.
- **New Logic:** The ZS version reads from the `.Overworld_SpritePointers_state_..._New` tables based on the current game state (`$7EF3C5`) to get a pointer to the correct sprite set for the area. This allows for dynamic sprite populations.
## 5. Configuration
- **`!UseVanillaPool`:** A flag that, when set to 1, forces the system to use data tables that mimic the vanilla game's behavior. This is useful for debugging.
- **`!Func...` Flags:** A large set of individual flags that allow for enabling or disabling specific hooks. This provides granular control for debugging and compatibility testing.
## 6. Analysis & Future Work: Sprite Loading
The `LoadOverworldSprites_Interupt` hook at `org $09C4C7` is a critical component that requires further investigation to support dynamic sprite sets, such as those needed for a day/night cycle.
- **Identified Conflict:** The current ZScream implementation for sprite loading conflicts with external logic that attempts to swap sprite sets based on in-game conditions (e.g., time of day). The original hook's design, which calls `JSL.l Sprite_OverworldReloadAll`, can lead to recursive loops and stack overflows if not handled carefully.
- **Investigation Goal:** The primary goal is to modify `LoadOverworldSprites_Interupt` to accommodate multiple sprite sets for a single area. The system needs to be able to check a condition (like whether it is currently night) and then select the appropriate sprite pointer, rather than relying solely on the static `state_..._New` tables.
- **Technical Challenges:** A previous attempt to integrate this functionality was reverted due to build system issues where labels from other modules (like `Oracle_CheckIfNight16Bit`) were not visible to `ZSCustomOverworld.asm`. A successful solution will require resolving these cross-module dependencies and carefully merging the day/night selection logic with ZScream's existing data-driven approach to sprite loading.

File diff suppressed because it is too large Load Diff

View File

@@ -1,100 +0,0 @@
# Guia del Probador Beta
## Notas
- El juego tiene pequeños errores con los gráficos.
- Los marcadores del mapa del supramundo no siempre son precisos, pueden cambiar dependiendo del NPC con el que hables.
- Los mapas de las mazmorras aún no están completos, pero se pueden comprar en Tingle.
- Si la escena de introducción en el bosque no funciona y el árbol te habla a ti en lugar del pirata, intenta reiniciar tu emulador.
## Introducción
- Conoce a Impa en la playa Loom
- Salta al pozo del pueblo descarriado
- Conoce a Farore en el bosque
## Varado en Abismo del Eón
- Ve al Santuario de los Orígenes en la pirámide.
- Encuentra la Perla de la Luna para convertirte en Link de Game-Boy.
- Conoce a buho en el bosque de los sueños.
- Encuentra la espada y el escudo en el bosque.
- Encuentra el portal de regreso a Kalyxo.
## Isla Kalyxo
Objectivo es coleccionar siete esencias de trifuerza, mientras ocasionalmente visitas el Abismo del Eón para coleccionar pendientes para la Espada Maestra.
Las mazmorras están en la isla, mientras los santuarios están en el abismo del eón.
El vendedor de máscaras está al este del pueblo. El salón de los secretos está al noreste del castillo.
<details>
<summary>Spoiler de ocarina y hongo</summary>
Para realizar varias misiones se requiere la Ocarina, que puedes obtener usando el Polvo Mágico en un pollo en el rancho dentro de la casa. Para obtener el polvo mágico, debes intercambiar el hongo con la bruja en las montañas y luego irte y cruzar un mapa del supramundo antes de regresar.
</details>
<details>
<summary>Spoiler de la Máscara de Deku</summary>
Para conseguir la máscara deku, toca la canción de sanación para el deku afuera de la tienda del vendedor de máscaras.
</details>
<details>
<summary>Spoiler de libro de secretos y botas de correr</summary>
Reproduce la canción de sanación para el niño enfermo del pueblo para las botas de correr. Para obtener el libro de los secretos, corre hacia la estantería de la biblioteca.
</details>
<details>
<summary>Spoiler de la tierra submarina</summary>
Usa la forma de máscara zora y sumérgete en el gran remolino de la playa.
</details>
### Mazmorra Uno: Bosque de setas venenosas
- Objetos: Arco y flecha, hongo
- Jefe: Manhandla
### Mazmorra Dos: Palacio de la cola
- Objecto: La pluma de Roc (saltar)
- Jefe: Moldorm
### Mazmorra Tres: Castillo de Kalyxo
- Objecto: Hoja de pradera (lv2 espada)
- Jefe: Caballeros de eyegores
- Requiere la libro de secretos a abrir la puente.
### Santuario de la Sabiduría
- Objecto: Aletas de Zora
- Requiere la varilla de hielo de las montañas de la isla.
- La varilla de hielo congela el agua para poder caminar sobre ella.
### Mazmorra Cuatro: Templo Zora
- Objecto: Gancho, Máscara Zora
- Jefe: Avanzada Arrghus
### Mazmorra Cinco: Finca Glacia
- Objecto: Varilla de fuego
- Jefe: Twinrova
### Santuario del Poder
- Objecto: Guante de poder
### Mazmorra Seis: Minas Goron
- Objecto: Martillo
- Jefe: Dodongo Rey
### Santuario del Doraje
- Objecto: Escudo de espejo
### Mazmorra Siete: Barco Dragón
- Objecto: Bastón de Somaria
- Jefe: Rey Pirata Kydrog
- Requiere el guante de titanes de la tierra submarina.
### Mazmorra Final: Fortaleza de los secretos
- Objecto: Flechas de plata
- Jefe: Link Oscuro
## Final
- Encuentra el barco pirata en las tierras de lava del abismo eónico.
- Para derrotar al jefe final, Kydreeok, debes destruir ambas cabezas de dragón rápidamente con las flechas plateadas.

View File

@@ -2,7 +2,7 @@
; Custom Tag
; Provide custom room behavior based on room ID
StoryState = $7C
; StoryState is now defined in Core/sram.asm at $7EF39E (persistent SRAM)
RoomTag_Return = $01CC5A
; override routine 0x39 "Holes(7)"
@@ -27,16 +27,23 @@ CustomTag:
HouseTag_Main:
{
LDA.w StoryState
JSL $008781
LDA.l StoryState ; Must use long addressing for SRAM ($7EF39E)
CMP.b #$03 : BCC .valid_state
; If state is invalid (>= 3), force reset to 0 (Intro)
LDA.b #$00 : STA.l StoryState
.valid_state
ASL A : TAX
JSR (.jump_table, X)
RTS
.jump_table
dw HouseTag_TelepathicPlea
dw HouseTag_WakeUpPlayer
dw HouseTag_End
HouseTag_TelepathicPlea:
{
LDA.b #$08 : STA $7EE000 ; Set the time to 8:00am
LDA.b #$08 : STA.l TimeState.Hours ; Set the time to 8:00am
LDA.b #$03 : STA.w $012C ; Play the deku tree music
; Set Link's coordinates to this specific position.
@@ -48,7 +55,7 @@ HouseTag_Main:
; "Accept our quest, Link!"
LDA.b #$1F : LDY.b #$00
JSL Sprite_ShowMessageUnconditional
INC.b StoryState
LDA.l StoryState : INC A : STA.l StoryState ; Long addressing for SRAM
RTS
}
@@ -71,7 +78,7 @@ HouseTag_Main:
;LDA.b #$01 : STA $02E4
STZ $02E4 ; awake from slumber
INC.b StoryState
LDA.l StoryState : INC A : STA.l StoryState ; Long addressing for SRAM
; Make it so Link's uncle never respawns in the house again.
LDA $7EF3C6 : ORA.b #$10 : STA $7EF3C6
@@ -86,7 +93,7 @@ HouseTag_Main:
HouseTag_End:
{
LDA $B6 : BNE .hasMetFarore
LDA #$00 : STA.w StoryState
LDA #$00 : STA.l StoryState ; Long addressing for SRAM
.hasMetFarore
RTS
}

View File

@@ -1,4 +1,6 @@
; Inherits Free Space from Bank07
org $07FA80
!RAM_MAGIC = $7EF36E
incsrc "Items/bottle_net.asm"
; Starts Expanded Bank 0x2B
@@ -11,7 +13,7 @@ incsrc "Items/portal_rod.asm"
incsrc "Items/fishing_rod.asm"
incsrc "Items/magic_rings.asm"
incsrc "Items/fist_damage.asm"
print "End of Items/fist_damage.asm ", pc
%log_end("Items/fist_damage.asm", !LOG_ITEMS)
MagicBeanGfx:
incbin "gfx/magic_bean.bin"
@@ -42,36 +44,44 @@ Link_ConsumeMagicBagItem:
Link_Banana:
{
LDA.l CURHP : CMP.w MAXHP : BCS +
LDA.l CURHP : CMP.w MAXHP : BCS .full
LDA.l CURHP : CLC : ADC.b #$10 : STA.l CURHP
LDA.b #$0D : STA.w $012F ; HUD Heart SFX
+
RTS
SEC : RTS
.full
CLC : RTS
}
Link_Pineapple:
{
RTS
LDA.l !RAM_MAGIC : CMP.b #$80 : BCS .full
LDA.b #$80 : STA.l !RAM_MAGIC
SEC : RTS
.full
CLC : RTS
}
Link_RockMeat:
{
RTS
CLC : RTS
}
Link_Seashells:
{
RTS
CLC : RTS
}
Link_Honeycombs:
{
RTS
LDA.l CURHP : CMP.w MAXHP : BCS .full
LDA.l CURHP : CLC : ADC.b #$10 : STA.l CURHP
SEC : RTS
.full
CLC : RTS
}
Link_DekuSticks:
{
RTS
CLC : RTS
}
}
@@ -80,5 +90,5 @@ Link_ConsumeMagicBagItem:
pushpc
; League of its own
incsrc "Items/ice_rod.asm"
print "End of Items/ice_rod.asm ", pc
%log_end("Items/ice_rod.asm", !LOG_ITEMS)
pullpc

View File

@@ -52,7 +52,6 @@ Dungeon_RevealSecrets:
; Check if we are in a building
LDA $1B : AND #$01 : BEQ .end
; ----------
; Check if we have the book of secrets
LDA $7EF34D : CMP #$01 : BNE $0F ; if not, go to enable BG2
@@ -68,12 +67,11 @@ Dungeon_RevealSecrets:
; enable BG2 (0x02 = 00000010)
LDA $1C : ORA #$02 : STA $1C
; ----------
.end
; @ $068365, JSL $099F91 old hook
RTL
}
print "End of Items/book_of_secrets.asm ", pc
%log_end("Items/book_of_secrets.asm", !LOG_ITEMS)
pushpc

Some files were not shown because too many files have changed in this diff Show More