From ca5c0d9eb8bee6ab9da10e0c14b3ff52d707c4b3 Mon Sep 17 00:00:00 2001 From: zig-for Date: Sat, 13 Apr 2024 18:21:55 -0700 Subject: [PATCH] LADX: Add "boots controls" option (#2085) --- worlds/ladx/LADXR/assembler.py | 2 +- worlds/ladx/LADXR/generator.py | 7 +- worlds/ladx/LADXR/locations/startItem.py | 1 - .../ladx/LADXR/patches/bank3e.asm/chest.asm | 9 +- worlds/ladx/LADXR/patches/core.py | 102 +++++++++++++++++- worlds/ladx/Options.py | 17 ++- 6 files changed, 127 insertions(+), 11 deletions(-) diff --git a/worlds/ladx/LADXR/assembler.py b/worlds/ladx/LADXR/assembler.py index 6c35fac4..c95d4dd9 100644 --- a/worlds/ladx/LADXR/assembler.py +++ b/worlds/ladx/LADXR/assembler.py @@ -757,7 +757,7 @@ class Assembler: def const(name: str, value: int) -> None: name = name.upper() - assert name not in CONST_MAP + assert name not in CONST_MAP or CONST_MAP[name] == value CONST_MAP[name] = value diff --git a/worlds/ladx/LADXR/generator.py b/worlds/ladx/LADXR/generator.py index 0406ad51..e87459fb 100644 --- a/worlds/ladx/LADXR/generator.py +++ b/worlds/ladx/LADXR/generator.py @@ -65,7 +65,7 @@ from .locations.keyLocation import KeyLocation from BaseClasses import ItemClassification from ..Locations import LinksAwakeningLocation -from ..Options import TrendyGame, Palette, MusicChangeCondition +from ..Options import TrendyGame, Palette, MusicChangeCondition, BootsControls # Function to generate a final rom, this patches the rom with all required patches @@ -97,7 +97,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m assembler.const("wTradeSequenceItem2", 0xDB7F) # Normally used to store that we have exchanged the trade item, we use it to store flags of which trade items we have assembler.const("wSeashellsCount", 0xDB41) assembler.const("wGoldenLeaves", 0xDB42) # New memory location where to store the golden leaf counter - assembler.const("wCollectedTunics", 0xDB6D) # Memory location where to store which tunic options are available + assembler.const("wCollectedTunics", 0xDB6D) # Memory location where to store which tunic options are available (and boots) assembler.const("wCustomMessage", 0xC0A0) # We store the link info in unused color dungeon flags, so it gets preserved in the savegame. @@ -243,6 +243,9 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m patches.core.quickswap(rom, 1) elif settings.quickswap == 'b': patches.core.quickswap(rom, 0) + + patches.core.addBootsControls(rom, ap_settings['boots_controls']) + world_setup = logic.world_setup diff --git a/worlds/ladx/LADXR/locations/startItem.py b/worlds/ladx/LADXR/locations/startItem.py index 95dd6ba5..0421c1d6 100644 --- a/worlds/ladx/LADXR/locations/startItem.py +++ b/worlds/ladx/LADXR/locations/startItem.py @@ -10,7 +10,6 @@ class StartItem(DroppedKey): # We need to give something here that we can use to progress. # FEATHER OPTIONS = [SWORD, SHIELD, POWER_BRACELET, OCARINA, BOOMERANG, MAGIC_ROD, TAIL_KEY, SHOVEL, HOOKSHOT, PEGASUS_BOOTS, MAGIC_POWDER, BOMB] - MULTIWORLD = False def __init__(self): diff --git a/worlds/ladx/LADXR/patches/bank3e.asm/chest.asm b/worlds/ladx/LADXR/patches/bank3e.asm/chest.asm index b19e879d..57771c17 100644 --- a/worlds/ladx/LADXR/patches/bank3e.asm/chest.asm +++ b/worlds/ladx/LADXR/patches/bank3e.asm/chest.asm @@ -51,7 +51,7 @@ GiveItemFromChest: dw ChestBow ; CHEST_BOW dw ChestWithItem ; CHEST_HOOKSHOT dw ChestWithItem ; CHEST_MAGIC_ROD - dw ChestWithItem ; CHEST_PEGASUS_BOOTS + dw Boots ; CHEST_PEGASUS_BOOTS dw ChestWithItem ; CHEST_OCARINA dw ChestWithItem ; CHEST_FEATHER dw ChestWithItem ; CHEST_SHOVEL @@ -273,6 +273,13 @@ ChestMagicPowder: ld [$DB4C], a jp ChestWithItem +Boots: + ; We use DB6D to store which tunics we have available + ; ...and the boots + ld a, [wCollectedTunics] + or $04 + ld [wCollectedTunics], a + jp ChestWithItem Flippers: ld a, $01 diff --git a/worlds/ladx/LADXR/patches/core.py b/worlds/ladx/LADXR/patches/core.py index c9f3a7c3..f4752c82 100644 --- a/worlds/ladx/LADXR/patches/core.py +++ b/worlds/ladx/LADXR/patches/core.py @@ -1,9 +1,11 @@ +from .. import assembler from ..assembler import ASM from ..entranceInfo import ENTRANCE_INFO from ..roomEditor import RoomEditor, ObjectWarp, ObjectHorizontal from ..backgroundEditor import BackgroundEditor from .. import utils +from ...Options import BootsControls def bugfixWrittingWrongRoomStatus(rom): # The normal rom contains a pretty nasty bug where door closing triggers in D7/D8 can effect doors in @@ -391,7 +393,7 @@ OAMData: db $20, $20, $20, $00 ;I db $20, $28, $28, $00 ;M db $20, $30, $18, $00 ;E - + db $20, $70, $16, $00 ;D db $20, $78, $18, $00 ;E db $20, $80, $10, $00 ;A @@ -408,7 +410,7 @@ OAMData: db $68, $38, $%02x, $00 ;0 db $68, $40, $%02x, $00 ;0 db $68, $48, $%02x, $00 ;0 - + """ % ((((check_count // 100) % 10) * 2) | 0x40, (((check_count // 10) % 10) * 2) | 0x40, ((check_count % 10) * 2) | 0x40), 0x469D), fill_nop=True) # Lower line of credits roll into XX XX XX rom.patch(0x17, 0x0784, 0x082D, ASM(""" @@ -425,7 +427,7 @@ OAMData: call updateOAM ld a, [$B001] ; seconds call updateOAM - + ld a, [$DB58] ; death count high call updateOAM ld a, [$DB57] ; death count low @@ -473,7 +475,7 @@ OAMData: db $68, $18, $40, $00 ;0 db $68, $20, $40, $00 ;0 db $68, $28, $40, $00 ;0 - + """, 0x4784), fill_nop=True) # Grab the "mostly" complete A-Z font @@ -539,6 +541,97 @@ OAMData: rom.banks[0x38][0x1400+n*0x20:0x1410+n*0x20] = utils.createTileData(gfx_high) rom.banks[0x38][0x1410+n*0x20:0x1420+n*0x20] = utils.createTileData(gfx_low) +def addBootsControls(rom, boots_controls: BootsControls): + if boots_controls == BootsControls.option_vanilla: + return + consts = { + "INVENTORY_PEGASUS_BOOTS": 0x8, + "INVENTORY_POWER_BRACELET": 0x3, + "UsePegasusBoots": 0x1705, + "J_A": (1 << 4), + "J_B": (1 << 5), + "wAButtonSlot": 0xDB01, + "wBButtonSlot": 0xDB00, + "wPegasusBootsChargeMeter": 0xC14B, + "hPressedButtonsMask": 0xCB + } + for c,v in consts.items(): + assembler.const(c, v) + + BOOTS_START_ADDR = 0x11E8 + condition = { + BootsControls.option_bracelet: """ + ld a, [hl] + ; Check if we are using the bracelet + cp INVENTORY_POWER_BRACELET + jr z, .yesBoots + """, + BootsControls.option_press_a: """ + ; Check if we are using the A slot + cp J_A + jr z, .yesBoots + ld a, [hl] + """, + BootsControls.option_press_b: """ + ; Check if we are using the B slot + cp J_B + jr z, .yesBoots + ld a, [hl] + """ + }[boots_controls.value] + + # The new code fits exactly within Nintendo's poorly space optimzied code while having more features + boots_code = assembler.ASM(""" +CheckBoots: + ; check if we own boots + ld a, [wCollectedTunics] + and $04 + ; if not, move on to the next inventory item (shield) + jr z, .out + + ; Check the B button + ld hl, wBButtonSlot + ld d, J_B + call .maybeBoots + + ; Check the A button + inc l ; l = wAButtonSlot - done this way to save a byte or two + ld d, J_A + call .maybeBoots + + ; If neither, reset charge meter and bail + xor a + ld [wPegasusBootsChargeMeter], a + jr .out + +.maybeBoots: + ; Check if we are holding this button even + ldh a, [hPressedButtonsMask] + and d + ret z + """ + # Check the special condition (also loads the current item for button into a) + + condition + + """ + ; Check if we are just using boots regularly + cp INVENTORY_PEGASUS_BOOTS + ret nz +.yesBoots: + ; We're using boots! Do so. + call UsePegasusBoots + ; If we return now we will go back into CheckBoots, we don't want that + ; We instead want to move onto the next item + ; but if we don't cleanup, the next "ret" will take us back there again + ; So we pop the return address off of the stack + pop af +.out: + """, BOOTS_START_ADDR) + + + + original_code = 'fa00dbfe08200ff0cbe6202805cd05171804afea4bc1fa01dbfe08200ff0cbe6102805cd05171804afea4bc1' + rom.patch(0, BOOTS_START_ADDR, original_code, boots_code, fill_nop=True) + def addWarpImprovements(rom, extra_warps): # Patch in a warp icon tile = utils.createTileData( \ @@ -739,4 +832,3 @@ success: exit: ret """)) - diff --git a/worlds/ladx/Options.py b/worlds/ladx/Options.py index ec457064..f7bf6325 100644 --- a/worlds/ladx/Options.py +++ b/worlds/ladx/Options.py @@ -316,6 +316,21 @@ class Overworld(Choice, LADXROption): # [Disable] no music in the whole game""", # aesthetic=True), +class BootsControls(Choice): + """ + Adds additional button to activate Pegasus Boots (does nothing if you haven't picked up your boots!) + [Vanilla] Nothing changes, you have to equip the boots to use them + [Bracelet] Holding down the button for the bracelet also activates boots (somewhat like Link to the Past) + [Press A] Holding down A activates boots + [Press B] Holding down B activates boots + """ + display_name = "Boots Controls" + option_vanilla = 0 + option_bracelet = 1 + option_press_a = 2 + option_press_b = 3 + + class LinkPalette(Choice, LADXROption): """ Sets link's palette @@ -485,5 +500,5 @@ links_awakening_options: typing.Dict[str, typing.Type[Option]] = { 'music_change_condition': MusicChangeCondition, 'nag_messages': NagMessages, 'ap_title_screen': APTitleScreen, - + 'boots_controls': BootsControls, }