From e08deff6f9f0c2192da6f8e7b01fa22e9d930914 Mon Sep 17 00:00:00 2001 From: zig-for Date: Sun, 1 Oct 2023 16:16:25 -0700 Subject: [PATCH] LADX: Implement remake style warp selection (#1587) --- worlds/ladx/LADXR/generator.py | 23 ++-- worlds/ladx/LADXR/patches/core.py | 205 +++++++++++++++++++++++++++++- worlds/ladx/Options.py | 17 ++- 3 files changed, 234 insertions(+), 11 deletions(-) diff --git a/worlds/ladx/LADXR/generator.py b/worlds/ladx/LADXR/generator.py index 733ab314da81..72d631da86a0 100644 --- a/worlds/ladx/LADXR/generator.py +++ b/worlds/ladx/LADXR/generator.py @@ -55,6 +55,9 @@ from . import hints from .patches import bank34 +from .utils import formatText +from ..Options import TrendyGame, Palette +from .roomEditor import RoomEditor, Object from .patches.aesthetics import rgb_to_bin, bin_to_rgb from .locations.keyLocation import KeyLocation @@ -134,7 +137,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m patches.core.fixWrongWarp(rom) patches.core.alwaysAllowSecretBook(rom) patches.core.injectMainLoop(rom) - + from ..Options import ShuffleSmallKeys, ShuffleNightmareKeys if ap_settings["shuffle_small_keys"] != ShuffleSmallKeys.option_original_dungeon or ap_settings["shuffle_nightmare_keys"] != ShuffleNightmareKeys.option_original_dungeon: @@ -239,7 +242,7 @@ 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) - + world_setup = logic.world_setup JUNK_HINT = 0.33 @@ -263,7 +266,7 @@ def gen_hint(): name = "Your" else: name = f"{multiworld.player_name[location.item.player]}'s" - + if isinstance(location, LinksAwakeningLocation): location_name = location.ladxr_item.metadata.name else: @@ -323,7 +326,7 @@ def gen_hint(): # TODO: if 0 or 4, 5, remove inaccurate conveyor tiles - from .roomEditor import RoomEditor, Object + room_editor = RoomEditor(rom, 0x2A0) if ap_settings["trendy_game"] == TrendyGame.option_easy: @@ -352,12 +355,12 @@ def gen_hint(): } def speed(): return rnd.randint(*speeds[ap_settings["trendy_game"]]) - rom.banks[0x4][0x76A0-0x4000] = 0xFF - speed() + rom.banks[0x4][0x76A0-0x4000] = 0xFF - speed() rom.banks[0x4][0x76A2-0x4000] = speed() rom.banks[0x4][0x76A6-0x4000] = speed() rom.banks[0x4][0x76A8-0x4000] = 0xFF - speed() if int(ap_settings["trendy_game"]) >= TrendyGame.option_hardest: - rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed() + rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed() rom.banks[0x4][0x76A3-0x4000] = speed() rom.banks[0x4][0x76A5-0x4000] = speed() rom.banks[0x4][0x76A7-0x4000] = 0xFF - speed() @@ -374,12 +377,14 @@ def speed(): [0x0f, 0x38, 0x0f], [0x30, 0x62, 0x30], [0x8b, 0xac, 0x0f], - [0x9b, 0xbc, 0x0f], + [0x9b, 0xbc, 0x0f], ] for color in gb_colors: for channel in range(3): color[channel] = color[channel] * 31 // 0xbc - + + if ap_settings["warp_improvements"]: + patches.core.addWarpImprovements(rom, ap_settings["additional_warp_points"]) palette = ap_settings["palette"] if palette != Palette.option_normal: @@ -410,7 +415,7 @@ def clamp(x, min, max): for address in range(start, end, 2): packed = (rom.banks[bank][address + 1] << 8) | rom.banks[bank][address] r,g,b = bin_to_rgb(packed) - + # 1 bit if palette == Palette.option_1bit: r &= 0b10000 diff --git a/worlds/ladx/LADXR/patches/core.py b/worlds/ladx/LADXR/patches/core.py index a202e661f945..c9f3a7c34b60 100644 --- a/worlds/ladx/LADXR/patches/core.py +++ b/worlds/ladx/LADXR/patches/core.py @@ -255,8 +255,9 @@ def warpHome(rom): """ % (type, map, room, x, y)), fill_nop=True) # Patch the RAM clear not to delete our custom dialog when we screen transition + # This is kind of horrible as it relies on bank 1 being loaded, lol rom.patch(0x01, 0x042C, "C629", "6B7E") - rom.patch(0x01, 0x3E6B, 0x3FFF, ASM(""" + rom.patch(0x01, 0x3E6B, 0x3E7B, ASM(""" ld bc, $A0 call $29DC ld bc, $1200 @@ -537,3 +538,205 @@ def addFrameCounter(rom, check_count): gfx_low = "\n".join([line.split(" ")[n] for line in tile_graphics.split("\n")[8:]]) 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 addWarpImprovements(rom, extra_warps): + # Patch in a warp icon + tile = utils.createTileData( \ +"""11111111 +10000000 +10200320 +10323200 +10033300 +10023230 +10230020 +10000000""", key="0231") + MINIMAP_BASE = 0x3800 + + # This is replacing a junk tile never used on the minimap + rom.banks[0x2C][MINIMAP_BASE + len(tile) * 0x65 : MINIMAP_BASE + len(tile) * 0x66] = tile + + # Allow using ENTITY_WARP for finding which map sections are warps + # Interesting - 3CA0 should be free, but something has pushed all the code forward a byte + rom.patch(0x02, 0x3CA1, None, ASM(""" + ld e, $0F + ld d, $00 + warp_search_loop: + ; Warp search loop + ld hl, $C3A0 + add hl, de ; $5FE1: $19 + ld a, [hl] ; $5FE2: $7E + cp $61 ; ENTITY_WARP + jr nz, search_continue ; if it's not a warp, check the next one + ld hl, $C280 + add hl, de + ld a, [hl] + and a + jr z, search_continue ; if this is despawned, check the next one + found: + jp $511B ; found + search_continue: + dec e + ld a, e + cp $FF + jr nz, warp_search_loop + + not_found: + jp $512B + + """)) + + # Insert redirect to above code + rom.patch(0x02, 0x1109, ASM(""" + ldh a, [$F6] + cp 1 + + """), ASM(""" + jp $7CA1 + nop + """)) + # Leaves some extra bytes behind, if we need more space in 0x02 + + # On warp hole, open map instead + rom.patch(0x19, 0x1DB9, None, ASM(""" + ld a, 7 ; Set GAMEPLAY_MAP + ld [$DB95], a + ld a, 0 ; reset subtype + ld [$DB96], a + ld a, 1 ; Set flag for using teleport + ld [$FFDD], a + + ret + """), fill_nop=True) + + # Patch over some instructions that decided if we are in debug mode holding some + # buttons with instead checking for FFDD (why FFDD? It appears to be never used anywhere, so we repurpose it for "is in teleport mode") + rom.banks[0x01][0x17B8] = 0xDD + rom.banks[0x01][0x17B9] = 0xFF + rom.banks[0x01][0x17FD] = 0xDD + rom.banks[0x01][0x17FE] = 0xFF + + # If in warp mode, don't allow manual exit + rom.patch(0x01, 0x1800, "20021E60", ASM("jp nz, $5818"), fill_nop=True) + + # Allow warp with just B + rom.banks[0x01][0x17C0] = 0x20 + + # Allow cursor to move over black squares + # This allows warping to undiscovered areas - a fine cheat, but needs a check for wOverworldRoomStatus in the warp code + CHEAT_WARP_ANYWHERE = False + if CHEAT_WARP_ANYWHERE: + rom.patch(0x01, 0x1AE8, None, ASM("jp $5AF5")) + + # This disables the arrows around the selection bubble + #rom.patch(0x01, 0x1B6F, None, ASM("ret"), fill_nop=True) + + # Fix lag when moving the cursor + # One option - just disable the delay code + #rom.patch(0x01, 0x1A76, 0x1A76+3, ASM("xor a"), fill_nop=True) + #rom.banks[0x01][0x1A7C] = 0 + # Another option - just remove the animation + rom.banks[0x01][0x1B20] = 0 + rom.banks[0x01][0x1B3B] = 0 + + # Patch the icon for all teleports + all_warps = [0x01, 0x95, 0x2C, 0xEC] + + + if extra_warps: + # mamu + all_warps.append(0x45) + # Tweak the flute location + rom.banks[0x14][0x0E95] += 0x10 + rom.banks[0x14][0x0EA3] += 0x01 + + mamu_pond = RoomEditor(rom, 0x45) + # Remove some tall grass so we can add a warp instead + mamu_pond.changeObject(1, 6, 0xE8) + mamu_pond.moveObject(1, 6, 3, 5) + mamu_pond.addEntity(3, 5, 0x61) + + mamu_pond.store(rom) + + # eagle + all_warps.append(0x0F) + room = RoomEditor(rom, 0x0F) + # Move one cliff edge and change it into a pit + room.changeObject(7, 6, 0xE8) + room.moveObject(7, 6, 6, 4) + + # Add the warp + room.addEntity(6, 4, 0x61) + # move the two corners + room.moveObject(6, 7, 7, 7) + room.moveObject(6, 6, 7, 6) + for object in room.objects: + # Extend the lower wall + if ((object.x == 0 and object.y == 7) + # Extend the lower floor + or (object.x == 0 and object.y == 6)): + room.overlay[object.x + object.count + object.y * 10] = object.type_id + object.count += 1 + room.store(rom) + + for warp in all_warps: + # Set icon + rom.banks[0x20][0x168B + warp] = 0x55 + # Set text + if not rom.banks[0x01][0x1959 + warp]: + rom.banks[0x01][0x1959 + warp] = 0x42 + # Set palette + # rom.banks[0x20][0x178B + 0x95] = 0x1 + + # Setup [?!] icon on map and associated text + rom.banks[0x01][0x1909 + 0x42] = 0x2B + rom.texts[0x02B] = utils.formatText('Warp') + + # call warp function (why not just jmp?!) + rom.patch(0x01, 0x17C3, None, ASM(""" + call $7E7B + ret + """)) + + # Build a switch statement by hand + warp_jump = "".join(f"cp ${hex(warp)[2:]}\njr z, success\n" for warp in all_warps) + + rom.patch(0x01, 0x3E7B, None, ASM(f""" +TeleportHandler: + + ld a, [$DBB4] ; Load the current selected tile + ; TODO: check if actually revealed so we can have free movement + ; Check cursor against different tiles to see if we are selecting a warp + {warp_jump} + jr exit + +success: + ld a, $0B + ld [$DB95], a ; Gameplay type + xor a + ld [$D401], a ; wWarp0MapCategory + ldh [$DD], a ; unset teleport flag(!!!) + ld [$D402], a ; wWarp0Map + ld a, [$DBB4] ; wDBB4 + ld [$D403], a ; wWarp0Room + + ld a, $68 + ld [$D404], a ; wWarp0DestinationX + ldh [$98], a ; LinkPositionY + ld [$D475], a + ld a, $70 + ld [$D405], a ; wWarp0DestinationY + ldh [$99], a ; LinkPositionX + ld a, $66 + ld [$D416], a ; wWarp0PositionTileIndex + ld a, $07 + ld [$DB96], a ; wGameplaySubtype + ldh a, [$A2] + ld [$DBC8], a + call $0C83 ; ApplyMapFadeOutTransition + xor a ; $5DF3: $AF + ld [$C167], a ; $5DF4: $EA $67 $C1 + +exit: + ret + """)) + diff --git a/worlds/ladx/Options.py b/worlds/ladx/Options.py index 4c85e5c3d0d9..eec8f3127c5d 100644 --- a/worlds/ladx/Options.py +++ b/worlds/ladx/Options.py @@ -378,7 +378,20 @@ class Palette(Choice): option_greyscale = 3 option_pink = 4 option_inverted = 5 - + +class WarpImprovements(DefaultOffToggle): + """ + [On] Adds remake style warp screen to the game. Choose your warp destination on the map after jumping in a portal and press B to select. + [Off] No change + """ + +class AdditionalWarpPoints(DefaultOffToggle): + """ + [On] (requires warp improvements) Adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower + [Off] No change + """ + + links_awakening_options: typing.Dict[str, typing.Type[Option]] = { 'logic': Logic, # 'heartpiece': DefaultOnToggle, # description='Includes heart pieces in the item pool'), @@ -400,6 +413,8 @@ class Palette(Choice): # 'bowwow': Bowwow, # 'overworld': Overworld, 'link_palette': LinkPalette, + 'warp_improvements': WarpImprovements, + 'additional_warp_points': AdditionalWarpPoints, 'trendy_game': TrendyGame, 'gfxmod': GfxMod, 'palette': Palette,