diff --git a/Docs/Core/MemoryMap.md b/Docs/Core/MemoryMap.md index 3c50648..cc45b33 100644 --- a/Docs/Core/MemoryMap.md +++ b/Docs/Core/MemoryMap.md @@ -95,12 +95,12 @@ This section details the layout of the save file memory. 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 | -|------------|--------------------|--------------------------------------------------------| +| Bank (Hex) | Address Range (PC) | Purpose / Contents | +|------------|-----------------------|--------------------------------------------------------| | $20 | `$208000` - `$20FFFF` | Expanded Music | -| $21-$27 | | ZScream Reserved | +| $21-$27 | | ZScream Reserved | | $28 | `$288000` - `$28FFFF` | ZSCustomOverworld data and code | -| $29-$2A | | ZScream Reserved | +| $29-$2A | | ZScream Reserved | | $2B | `$2B8000` - `$2BFFFF` | Items | | $2C | `$2C8000` - `$2CFFFF` | Underworld/Dungeons | | $2D | `$2D8000` - `$2DFFFF` | Menu | @@ -118,10 +118,10 @@ This section details the allocation of custom code and data within the ROM banks | $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 | +| $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` | \ No newline at end of file +| Patches | Various | Targeted modifications within vanilla ROM addresses | `Core/patches.asm`, `Util/item_cheat.asm` | \ No newline at end of file diff --git a/Docs/Sprites/Overlords.md b/Docs/Sprites/Overlords.md new file mode 100644 index 0000000..b222624 --- /dev/null +++ b/Docs/Sprites/Overlords.md @@ -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 # + 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. diff --git a/Items/all_items.asm b/Items/all_items.asm index 4b41318..f95793a 100644 --- a/Items/all_items.asm +++ b/Items/all_items.asm @@ -11,7 +11,7 @@ incsrc "Items/portal_rod.asm" incsrc "Items/fishing_rod.asm" incsrc "Items/magic_rings.asm" incsrc "Items/fist_damage.asm" -%print_debug("End of Items/fist_damage.asm ") +%log_end("Items/fist_damage.asm", !LOG_ITEMS) MagicBeanGfx: incbin "gfx/magic_bean.bin" @@ -80,5 +80,5 @@ Link_ConsumeMagicBagItem: pushpc ; League of its own incsrc "Items/ice_rod.asm" -%print_debug("End of Items/ice_rod.asm ") +%log_end("Items/ice_rod.asm", !LOG_ITEMS) pullpc diff --git a/Items/book_of_secrets.asm b/Items/book_of_secrets.asm index a095e15..083b107 100644 --- a/Items/book_of_secrets.asm +++ b/Items/book_of_secrets.asm @@ -73,5 +73,5 @@ Dungeon_RevealSecrets: RTL } -print "End of Items/book_of_secrets.asm ", pc +%log_end("Items/book_of_secrets.asm", !LOG_ITEMS) pushpc diff --git a/Items/bottle_net.asm b/Items/bottle_net.asm index 9f6f9dd..d640269 100644 --- a/Items/bottle_net.asm +++ b/Items/bottle_net.asm @@ -215,5 +215,5 @@ Bottle_DrinkMilk: RTL } -print "End of Items/bottle_net.asm ", pc +%log_end("Items/bottle_net.asm", !LOG_ITEMS) pushpc diff --git a/Items/fishing_rod.asm b/Items/fishing_rod.asm index d5504ae..abac5e3 100644 --- a/Items/fishing_rod.asm +++ b/Items/fishing_rod.asm @@ -515,4 +515,4 @@ DismissRodFromMenu: RTL } -print "End of Items/fishing_rod.asm ", pc +%log_end("Items/fishing_rod.asm", !LOG_ITEMS) diff --git a/Items/jump_feather.asm b/Items/jump_feather.asm index 5c430bf..97861a3 100644 --- a/Items/jump_feather.asm +++ b/Items/jump_feather.asm @@ -88,5 +88,5 @@ CheckIfJumpingForSpikeDamage: db $04 ; red } -print "End of Items/jump_feather.asm ", pc +%log_end("Items/jump_feather.asm", !LOG_ITEMS) pushpc diff --git a/Items/ocarina.asm b/Items/ocarina.asm index 6c438e7..ada69b1 100644 --- a/Items/ocarina.asm +++ b/Items/ocarina.asm @@ -386,7 +386,7 @@ UpdateFluteSong_Long: .not_pressed RTL } -print "End of Items/ocarina.asm ", pc +%log_end("Items/ocarina.asm", !LOG_ITEMS) pushpc ; Bank2B freespace diff --git a/Items/portal_rod.asm b/Items/portal_rod.asm index 02cd5c0..3f359bb 100644 --- a/Items/portal_rod.asm +++ b/Items/portal_rod.asm @@ -221,7 +221,7 @@ ScrollToPortal: RTL } -print "End of Items/portal_rod.asm ", pc +%log_end("Items/portal_rod.asm", !LOG_ITEMS) pushpc ; ; Portal Rod logic based on Fire Rod diff --git a/Menu/menu.asm b/Menu/menu.asm index 7d38a5a..104c8b7 100644 --- a/Menu/menu.asm +++ b/Menu/menu.asm @@ -762,9 +762,9 @@ Submenu_Return: menu_frame: incbin "tilemaps/menu_frame.tilemap" quest_icons: incbin "tilemaps/quest_icons.tilemap" incsrc "menu_map_names.asm" -%print_debug("End of Menu/menu.asm ") +%log_end("Menu/menu.asm", !LOG_MENU) incsrc "menu_hud.asm" -%print_debug("End of Menu/menu_hud.asm ") +%log_end("Menu/menu_hud.asm", !LOG_MENU) incsrc "menu_journal.asm" -%print_debug("End of Menu/menu_journal.asm ") +%log_end("Menu/menu_journal.asm", !LOG_MENU) diff --git a/Oracle_main.asm b/Oracle_main.asm index 6322124..e0cb2a9 100644 --- a/Oracle_main.asm +++ b/Oracle_main.asm @@ -39,7 +39,7 @@ incsrc "Util/macros.asm" incsrc "Overworld/ZSCustomOverworld.asm" -%print_debug("End of ZSCustomOverworld.asm ") +%log_end("ZSCustomOverworld.asm", !LOG_OVERWORLD) ; Vanilla WRAM and SRAM incsrc "Core/ram.asm" @@ -76,6 +76,7 @@ namespace Oracle incsrc "Core/patches.asm" ; incsrc "Core/capture.asm" - print "\nFinished applying patches" + print "" + print "Finished applying patches" } namespace off diff --git a/Overworld/overworld.asm b/Overworld/overworld.asm index 86704e2..b7ff717 100644 --- a/Overworld/overworld.asm +++ b/Overworld/overworld.asm @@ -37,25 +37,25 @@ org $0EDE29 ; ========================================================= incsrc "Overworld/lost_woods.asm" -print "End of Overworld/lost_woods.asm ", pc +%log_end("Overworld/lost_woods.asm", !LOG_OVERWORLD) org $348000 ; Free space pushpc incsrc "Overworld/time_system.asm" -print "End of Overworld/time_system.asm ", pc +%log_end("Overworld/time_system.asm", !LOG_OVERWORLD) incsrc "Overworld/overlays.asm" -print "End of Overworld/overlays.asm ", pc +%log_end("Overworld/overlays.asm", !LOG_OVERWORLD) incsrc "Overworld/entrances.asm" -print "End of Overworld/entrances.asm ", pc +%log_end("Overworld/entrances.asm", !LOG_OVERWORLD) incsrc "Overworld/custom_gfx.asm" -print "End of Overworld/custom_gfx.asm ", pc +%log_end("Overworld/custom_gfx.asm", !LOG_OVERWORLD) pushpc incsrc "Overworld/world_map.asm" -print "End of world_map.asm ", pc +%log_end("Overworld/world_map.asm", !LOG_OVERWORLD) ; ========================================================= ; Get Lv2 Sword from chest diff --git a/Sprites/NPCs/followers.asm b/Sprites/NPCs/followers.asm index b9e5be2..c13b7a5 100644 --- a/Sprites/NPCs/followers.asm +++ b/Sprites/NPCs/followers.asm @@ -415,7 +415,7 @@ Sprite_39_ZoraBaby: RTS } } -print "End of Sprite 39 Locksmith ", pc +%log_end("Sprite 39 Locksmith", !LOG_SPRITES) assert pc() <= $06BD9C pullpc diff --git a/Util/macros.asm b/Util/macros.asm index 9dd5c37..4c76902 100644 --- a/Util/macros.asm +++ b/Util/macros.asm @@ -25,7 +25,9 @@ endmacro ; Usage: %log_section("Sprites", !LOG_SPRITES) macro log_section(name, flag) if !DEBUG == 1 && == 1 - print "\n--- ---" + print "" + print "--- ---" + print "" endif endmacro