diff --git a/worlds/ladx/LADXR/generator.py b/worlds/ladx/LADXR/generator.py index 58efda6c04..6fcbcc5fc6 100644 --- a/worlds/ladx/LADXR/generator.py +++ b/worlds/ladx/LADXR/generator.py @@ -2,7 +2,6 @@ import binascii import importlib.util import importlib.machinery import random -import pickle import Utils from collections import defaultdict from typing import Dict @@ -61,7 +60,11 @@ from .patches import bank34 from .roomEditor import RoomEditor, Object from .patches.aesthetics import rgb_to_bin, bin_to_rgb -from .. import Options +from .logic import Logic as LADXRLogic +from .settings import Settings as LADXRSettings +from .worldSetup import WorldSetup as LADXRWorldSetup +from .locations.keyLocation import KeyLocation + class VersionError(Exception): pass @@ -86,8 +89,27 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): random.seed(patch_data["seed"] + patch_data["player"]) multi_key = binascii.unhexlify(patch_data["multi_key"].encode()) - item_list = pickle.loads(binascii.unhexlify(patch_data["item_list"].encode())) - options = patch_data["options"] + + ladxr_settings = LADXRSettings(patch_data["ladxr_settings_dict"]) + world_setup = LADXRWorldSetup() + world_setup.goal = patch_data["world_setup"]["goal"] + world_setup.multichest = patch_data["world_setup"]["multichest"] + world_setup.entrance_mapping = patch_data["world_setup"]["entrance_mapping"] + world_setup.boss_mapping = patch_data["world_setup"]["boss_mapping"] + world_setup.miniboss_mapping = patch_data["world_setup"]["miniboss_mapping"] + ladxr_logic = LADXRLogic(configuration_options=ladxr_settings, world_setup=world_setup) + item_list = [item for item in ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)] + for spot in patch_data["rom_item_placements"]: + ladxr_item = next((item for item in item_list if item.nameId == spot["name_id"]), None) + if not ladxr_item: + continue + ladxr_item.item = spot["item"][1:] if spot["item"].startswith('*') else spot["item"] + ladxr_item.custom_item_name = spot["custom_item_name"] + mw = None + if patch_data["player"] != spot["item_owner"]: + mw = min(spot["item_owner"], 101) + ladxr_item.mw = mw + rom_patches = [] rom = ROMWithTables(base_rom, rom_patches) rom.player_names = patch_data["other_player_names"] @@ -101,7 +123,7 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): for pymod in pymods: pymod.prePatch(rom) - if options["gfxmod"]: + if ladxr_settings.gfxmod: try: gfx_mod_file = LinksAwakeningWorld.settings.gfx_mod_file patches.aesthetics.gfxMod(rom, gfx_mod_file) @@ -136,7 +158,7 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): assembler.const("wLinkSpawnDelay", 0xDE13) #assembler.const("HARDWARE_LINK", 1) - assembler.const("HARD_MODE", 1 if options["hard_mode"] else 0) + assembler.const("HARD_MODE", 1 if ladxr_settings.hardmode else 0) patches.core.cleanup(rom) patches.save.singleSaveSlot(rom) @@ -159,17 +181,16 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): patches.core.alwaysAllowSecretBook(rom) patches.core.injectMainLoop(rom) - if options["shuffle_small_keys"] != Options.ShuffleSmallKeys.option_original_dungeon or\ - options["shuffle_nightmare_keys"] != Options.ShuffleNightmareKeys.option_original_dungeon: + if ladxr_settings.shufflesmallkeys != 'originaldungeon' or ladxr_settings.shufflenightmarekeys != 'originaldungeon': patches.inventory.advancedInventorySubscreen(rom) patches.inventory.moreSlots(rom) - # if ladxr_settings["witch"]: + # if ladxr_settings.witch: patches.witch.updateWitch(rom) patches.softlock.fixAll(rom) - if not options["rooster"]: + if not ladxr_settings.rooster: patches.maptweaks.tweakMap(rom) patches.maptweaks.tweakBirdKeyRoom(rom) - if options["overworld"] == Options.Overworld.option_open_mabe: + if ladxr_settings.overworld == 'openmabe': patches.maptweaks.openMabe(rom) patches.chest.fixChests(rom) patches.shop.fixShop(rom) @@ -181,9 +202,9 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): patches.tarin.updateTarin(rom) patches.fishingMinigame.updateFinishingMinigame(rom) patches.health.upgradeHealthContainers(rom) - # if ladxr_settings["owlstatues"] in ("dungeon", "both"): + # if ladxr_settings.owlstatues in ("dungeon", "both"): # patches.owl.upgradeDungeonOwlStatues(rom) - # if ladxr_settings["owlstatues"] in ("overworld", "both"): + # if ladxr_settings.owlstatues in ("overworld", "both"): # patches.owl.upgradeOverworldOwlStatues(rom) patches.goldenLeaf.fixGoldenLeaf(rom) patches.heartPiece.fixHeartPiece(rom) @@ -194,17 +215,17 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): patches.songs.upgradeManbo(rom) patches.songs.upgradeMamu(rom) - patches.tradeSequence.patchTradeSequence(rom, options) + patches.tradeSequence.patchTradeSequence(rom, ladxr_settings) patches.bowwow.fixBowwow(rom, everywhere=False) - # if ladxr_settings["bowwow"] != 'normal': + # if ladxr_settings.bowwow != 'normal': # patches.bowwow.bowwowMapPatches(rom) patches.desert.desertAccess(rom) - # if ladxr_settings["overworld"] == 'dungeondive': + # if ladxr_settings.overworld == 'dungeondive': # patches.overworld.patchOverworldTilesets(rom) # patches.overworld.createDungeonOnlyOverworld(rom) - # elif ladxr_settings["overworld"] == 'nodungeons': + # elif ladxr_settings.overworld == 'nodungeons': # patches.dungeon.patchNoDungeons(rom) - #elif world.ladxr_settings["overworld"] == 'random': + #elif ladxr_settings.overworld == 'random': # patches.overworld.patchOverworldTilesets(rom) # mapgen.store_map(rom, world.ladxr_logic.world.map) #if settings.dungeon_items == 'keysy': @@ -212,102 +233,94 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): # patches.reduceRNG.slowdownThreeOfAKind(rom) patches.reduceRNG.fixHorseHeads(rom) patches.bomb.onlyDropBombsWhenHaveBombs(rom) - if options["music_change_condition"] == Options.MusicChangeCondition.option_always: + if ladxr_settings.musicchange == 'always': patches.aesthetics.noSwordMusic(rom) patches.aesthetics.reduceMessageLengths(rom, random) patches.aesthetics.allowColorDungeonSpritesEverywhere(rom) - if options["music"] == Options.Music.option_shuffled: + if ladxr_settings.music == 'shuffled': patches.music.randomizeMusic(rom, random) - elif options["music"] == Options.Music.option_off: + elif ladxr_settings.music == 'off': patches.music.noMusic(rom) - if options["no_flash"]: + if ladxr_settings.noflash: patches.aesthetics.removeFlashingLights(rom) - if options["hard_mode"] == Options.HardMode.option_oracle: + if ladxr_settings.hardmode == 'oracle': patches.hardMode.oracleMode(rom) - elif options["hard_mode"] == Options.HardMode.option_hero: + elif ladxr_settings.hardmode == 'hero': patches.hardMode.heroMode(rom) - elif options["hard_mode"] == Options.HardMode.option_ohko: + elif ladxr_settings.hardmode == 'ohko': patches.hardMode.oneHitKO(rom) - #if ladxr_settings["superweapons"]: + #if ladxr_settings.superweapons: # patches.weapons.patchSuperWeapons(rom) - if options["text_mode"] == Options.TextMode.option_fast: + if ladxr_settings.textmode == 'fast': patches.aesthetics.fastText(rom) - #if ladxr_settings["textmode"] == 'none': + #if ladxr_settings.textmode == 'none': # patches.aesthetics.fastText(rom) # patches.aesthetics.noText(rom) - if not options["nag_messages"]: + if not ladxr_settings.nagmessages: patches.aesthetics.removeNagMessages(rom) - if options["low_hp_beep"] == Options.LowHpBeep.option_slow: + if ladxr_settings.lowhpbeep == 'slow': patches.aesthetics.slowLowHPBeep(rom) - if options["low_hp_beep"] == Options.LowHpBeep.option_none: + if ladxr_settings.lowhpbeep == 'none': patches.aesthetics.removeLowHPBeep(rom) - if 0 <= options["link_palette"]: - patches.aesthetics.forceLinksPalette(rom, options["link_palette"]) + if 0 <= int(ladxr_settings.linkspalette): + patches.aesthetics.forceLinksPalette(rom, int(ladxr_settings.linkspalette)) if args.romdebugmode: # The default rom has this build in, just need to set a flag and we get this save. rom.patch(0, 0x0003, "00", "01") # Patch the sword check on the shopkeeper turning around. - if options["stealing"] == Options.Stealing.option_disabled: + if ladxr_settings.steal == 'disabled': rom.patch(4, 0x36F9, "FA4EDB", "3E0000") rom.texts[0x2E] = utils.formatText("Hey! Welcome! Did you know that I have eyes on the back of my head?") rom.texts[0x2F] = utils.formatText("Nothing escapes my gaze! Your thieving ways shall never prosper!") - #if ladxr_settings["hpmode"] == 'inverted': + #if ladxr_settings.hpmode == 'inverted': # patches.health.setStartHealth(rom, 9) - #elif ladxr_settings["hpmode"] == '1': + #elif ladxr_settings.hpmode == '1': # patches.health.setStartHealth(rom, 1) patches.inventory.songSelectAfterOcarinaSelect(rom) - if options["quickswap"] == Options.Quickswap.option_a: + if ladxr_settings.quickswap == 'a': patches.core.quickswap(rom, 1) - elif options["quickswap"] == Options.Quickswap.option_b: + elif ladxr_settings.quickswap == 'b': patches.core.quickswap(rom, 0) - patches.core.addBootsControls(rom, options["boots_controls"]) + patches.core.addBootsControls(rom, ladxr_settings.bootscontrols) random.seed(patch_data["seed"] + patch_data["player"]) hints.addHints(rom, random, patch_data["hint_texts"]) - if patch_data["world_setup"]["goal"] == "raft": + if world_setup.goal == "raft": patches.goal.setRaftGoal(rom) - elif patch_data["world_setup"]["goal"] in ("bingo", "bingo-full"): - patches.bingo.setBingoGoal(rom, patch_data["world_setup"]["bingo_goals"], patch_data["world_setup"]["goal"]) - elif patch_data["world_setup"]["goal"] == "seashells": + elif world_setup.goal in ("bingo", "bingo-full"): + patches.bingo.setBingoGoal(rom, world_setup.bingo_goals, world_setup.goal) + elif world_setup.goal == "seashells": patches.goal.setSeashellGoal(rom, 20) else: - patches.goal.setRequiredInstrumentCount(rom, patch_data["world_setup"]["goal"]) + patches.goal.setRequiredInstrumentCount(rom, world_setup.goal) # Patch the generated logic into the rom - patches.chest.setMultiChest(rom, patch_data["world_setup"]["multichest"]) - #if ladxr_settings["overworld"] not in {"dungeondive", "random"}: - patches.entrances.changeEntrances(rom, patch_data["world_setup"]["entrance_mapping"]) - for spot in item_list: - if spot.item and spot.item.startswith("*"): - spot.item = spot.item[1:] - mw = None - if spot.item_owner != spot.location_owner: - mw = spot.item_owner - if mw > 101: - # There are only 101 player name slots (99 + "The Server" + "another world"), so don't use more than that - mw = 101 - spot.patch(rom, spot.item, multiworld=mw) - patches.enemies.changeBosses(rom, patch_data["world_setup"]["boss_mapping"]) - patches.enemies.changeMiniBosses(rom, patch_data["world_setup"]["miniboss_mapping"]) + patches.chest.setMultiChest(rom, world_setup.multichest) + #if ladxr_settings.overworld not in {"dungeondive", "random"}: + patches.entrances.changeEntrances(rom, world_setup.entrance_mapping) + for ladxr_item in item_list: + ladxr_item.patch(rom, ladxr_item.item, multiworld=ladxr_item.mw) + patches.enemies.changeBosses(rom, world_setup.boss_mapping) + patches.enemies.changeMiniBosses(rom, world_setup.miniboss_mapping) if not args.romdebugmode: patches.core.addFrameCounter(rom, len(item_list)) patches.core.warpHome(rom) # Needs to be done after setting the start location. patches.titleScreen.setRomInfo(rom, patch_data) - if options["ap_title_screen"]: + if ladxr_settings.aptitlescreen: patches.titleScreen.setTitleGraphics(rom) patches.endscreen.updateEndScreen(rom) patches.aesthetics.updateSpriteData(rom) if args.doubletrouble: patches.enemies.doubleTrouble(rom) - if options["text_shuffle"]: + if ladxr_settings.textshuffle: excluded_ids = [ # Overworld owl statues 0x1B6, 0x1B7, 0x1B8, 0x1B9, 0x1BA, 0x1BB, 0x1BC, 0x1BD, 0x1BE, 0x22D, @@ -366,14 +379,14 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): rom.texts[shuffled[bucket_idx][0]] = data - if options["trendy_game"] != Options.TrendyGame.option_normal: + if ladxr_settings.trendygame != 'normal': # TODO: if 0 or 4, 5, remove inaccurate conveyor tiles room_editor = RoomEditor(rom, 0x2A0) - if options["trendy_game"] == Options.TrendyGame.option_easy: + if ladxr_settings.trendygame == 'easy': # Set physics flag on all objects for i in range(0, 6): rom.banks[0x4][0x6F1E + i -0x4000] = 0x4 @@ -384,7 +397,7 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): # Add new conveyor to "push" yoshi (it's only a visual) room_editor.objects.append(Object(5, 3, 0xD0)) - if options["trendy_game"] >= Options.TrendyGame.option_harder: + if ladxr_settings.trendygame in ('harder', 'hardest', 'impossible'): """ Data_004_76A0:: db $FC, $00, $04, $00, $00 @@ -393,18 +406,18 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): db $00, $04, $00, $FC, $00 """ speeds = { - Options.TrendyGame.option_harder: (3, 8), - Options.TrendyGame.option_hardest: (3, 8), - Options.TrendyGame.option_impossible: (3, 16), + 'harder': (3, 8), + 'hardest': (3, 8), + 'impossible': (3, 16), } def speed(): random.seed(patch_data["seed"] + patch_data["player"]) - return random.randint(*speeds[options["trendy_game"]]) + return random.randint(*speeds[ladxr_settings.trendygame]) 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 options["trendy_game"] >= Options.TrendyGame.option_hardest: + if ladxr_settings.trendygame in ('hardest', 'impossible'): rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed() rom.banks[0x4][0x76A3-0x4000] = speed() rom.banks[0x4][0x76A5-0x4000] = speed() @@ -428,11 +441,11 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): for channel in range(3): color[channel] = color[channel] * 31 // 0xbc - if options["warps"] != Options.Warps.option_vanilla: - patches.core.addWarpImprovements(rom, options["warps"] == Options.Warps.option_improved_additional) + if ladxr_settings.warps != 'vanilla': + patches.core.addWarpImprovements(rom, ladxr_settings.warps == 'improved_additional') - palette = options["palette"] - if palette != Options.Palette.option_normal: + palette = ladxr_settings.palette + if palette != 'normal': ranges = { # Object palettes # Overworld palettes @@ -462,22 +475,22 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): r,g,b = bin_to_rgb(packed) # 1 bit - if palette == Options.Palette.option_1bit: + if palette == '1bit': r &= 0b10000 g &= 0b10000 b &= 0b10000 # 2 bit - elif palette == Options.Palette.option_1bit: + elif palette == '1bit': r &= 0b11000 g &= 0b11000 b &= 0b11000 # Invert - elif palette == Options.Palette.option_inverted: + elif palette == 'inverted': r = 31 - r g = 31 - g b = 31 - b # Pink - elif palette == Options.Palette.option_pink: + elif palette == 'pink': r = r // 2 r += 16 r = int(r) @@ -486,7 +499,7 @@ def generateRom(base_rom: bytes, args, patch_data: Dict): b += 16 b = int(b) b = clamp(b, 0, 0x1F) - elif palette == Options.Palette.option_greyscale: + elif palette == 'greyscale': # gray=int(0.299*r+0.587*g+0.114*b) gray = (r + g + b) // 3 r = g = b = gray diff --git a/worlds/ladx/LADXR/logic/dungeon1.py b/worlds/ladx/LADXR/logic/dungeon1.py index 645c50d1d5..8bbcffe525 100644 --- a/worlds/ladx/LADXR/logic/dungeon1.py +++ b/worlds/ladx/LADXR/logic/dungeon1.py @@ -20,7 +20,7 @@ class Dungeon1: if options.owlstatues == "both" or options.owlstatues == "dungeon": Location(dungeon=1).add(OwlStatue(0x10A)).connect(dungeon1_right_side, STONE_BEAK1) dungeon1_3_of_a_kind = Location(dungeon=1).add(DungeonChest(0x10A)).connect(dungeon1_right_side, OR(r.attack_hookshot_no_bomb, SHIELD)) # three of a kind, shield stops the suit from changing - dungeon1_miniboss = Location(dungeon=1).connect(dungeon1_right_side, AND(r.miniboss_requirements[world_setup.miniboss_mapping[0]], FEATHER)) + dungeon1_miniboss = Location(dungeon=1).connect(dungeon1_right_side, AND(r.miniboss_requirements[world_setup.miniboss_mapping['0']], FEATHER)) dungeon1_boss = Location(dungeon=1).connect(dungeon1_miniboss, NIGHTMARE_KEY1) boss = Location(dungeon=1).add(HeartContainer(0x106), Instrument(0x102)).connect(dungeon1_boss, r.boss_requirements[world_setup.boss_mapping[0]]) @@ -30,7 +30,7 @@ class Dungeon1: if options.logic == 'glitched' or options.logic == 'hell': boss_key.connect(entrance, r.super_jump_feather) # super jump - dungeon1_miniboss.connect(dungeon1_right_side, r.miniboss_requirements[world_setup.miniboss_mapping[0]]) # damage boost or buffer pause over the pit to cross or mushroom + dungeon1_miniboss.connect(dungeon1_right_side, r.miniboss_requirements[world_setup.miniboss_mapping['0']]) # damage boost or buffer pause over the pit to cross or mushroom if options.logic == 'hell': feather_chest.connect(dungeon1_upper_left, SWORD) # keep slashing the spiked beetles until they keep moving 1 pixel close towards you and the pit, to get them to fall diff --git a/worlds/ladx/LADXR/logic/dungeon2.py b/worlds/ladx/LADXR/logic/dungeon2.py index 6ee6cc4a80..240bafdea0 100644 --- a/worlds/ladx/LADXR/logic/dungeon2.py +++ b/worlds/ladx/LADXR/logic/dungeon2.py @@ -23,7 +23,7 @@ class Dungeon2: dungeon2_r5 = Location(dungeon=2).connect(dungeon2_r4, AND(KEY2, FOUND(KEY2, 3))) # push two blocks together room with owl statue if options.owlstatues == "both" or options.owlstatues == "dungeon": Location(dungeon=2).add(OwlStatue(0x12F)).connect(dungeon2_r5, STONE_BEAK2) # owl statue is before miniboss - miniboss = Location(dungeon=2).add(DungeonChest(0x126)).add(DungeonChest(0x121)).connect(dungeon2_r5, AND(FEATHER, r.miniboss_requirements[world_setup.miniboss_mapping[1]])) # post hinox + miniboss = Location(dungeon=2).add(DungeonChest(0x126)).add(DungeonChest(0x121)).connect(dungeon2_r5, AND(FEATHER, r.miniboss_requirements[world_setup.miniboss_mapping['1']])) # post hinox if options.owlstatues == "both" or options.owlstatues == "dungeon": Location(dungeon=2).add(OwlStatue(0x129)).connect(miniboss, STONE_BEAK2) # owl statue after the miniboss @@ -45,7 +45,7 @@ class Dungeon2: dungeon2_map_chest.connect(dungeon2_l2, AND(r.attack_hookshot_powder, r.boots_bonk_pit)) # use boots to jump over the pits dungeon2_r4.connect(dungeon2_r3, OR(r.boots_bonk_pit, r.hookshot_spam_pit)) # can use both pegasus boots bonks or hookshot spam to cross the pit room dungeon2_r4.connect(shyguy_key_drop, r.rear_attack_range, one_way=True) # adjust for alternate requirements for dungeon2_r4 - miniboss.connect(dungeon2_r5, AND(r.boots_dash_2d, r.miniboss_requirements[world_setup.miniboss_mapping[1]])) # use boots to dash over the spikes in the 2d section + miniboss.connect(dungeon2_r5, AND(r.boots_dash_2d, r.miniboss_requirements[world_setup.miniboss_mapping['1']])) # use boots to dash over the spikes in the 2d section dungeon2_pre_stairs_boss.connect(dungeon2_r6, AND(HOOKSHOT, OR(BOW, BOMB, MAGIC_ROD, AND(OCARINA, SONG1)), FOUND(KEY2, 5))) # hookshot clip through the pot using both pol's voice dungeon2_post_stairs_boss.connect(dungeon2_pre_stairs_boss, OR(BOMB, r.boots_jump)) # use a bomb to lower the last platform, or boots + feather to cross over top (only relevant in hell logic) dungeon2_pre_boss.connect(dungeon2_post_stairs_boss, AND(r.boots_bonk_pit, r.hookshot_spam_pit)) # boots bonk off bottom wall + hookshot spam across the two 1 tile pits vertically diff --git a/worlds/ladx/LADXR/logic/dungeon3.py b/worlds/ladx/LADXR/logic/dungeon3.py index 33782be16c..f0cfa49ea9 100644 --- a/worlds/ladx/LADXR/logic/dungeon3.py +++ b/worlds/ladx/LADXR/logic/dungeon3.py @@ -33,7 +33,7 @@ class Dungeon3: Location(dungeon=3).add(DroppedKey(0x14D)).connect(area_right, r.attack_hookshot_powder) # key after the stairs. dungeon3_nightmare_key_chest = Location(dungeon=3).add(DungeonChest(0x147)).connect(area_right, AND(BOMB, FEATHER, PEGASUS_BOOTS)) # nightmare key chest - dungeon3_post_dodongo_chest = Location(dungeon=3).add(DungeonChest(0x146)).connect(area_right, AND(r.attack_hookshot_powder, r.miniboss_requirements[world_setup.miniboss_mapping[2]])) # boots after the miniboss + dungeon3_post_dodongo_chest = Location(dungeon=3).add(DungeonChest(0x146)).connect(area_right, AND(r.attack_hookshot_powder, r.miniboss_requirements[world_setup.miniboss_mapping['2']])) # boots after the miniboss compass_chest = Location(dungeon=3).add(DungeonChest(0x142)).connect(area_right, OR(SWORD, BOMB, AND(SHIELD, r.attack_hookshot_powder))) # bomb only activates with sword, bomb or shield dungeon3_3_bombite_room = Location(dungeon=3).add(DroppedKey(0x141)).connect(compass_chest, BOMB) # 3 bombite room Location(dungeon=3).add(DroppedKey(0x148)).connect(area_right, r.attack_no_boomerang) # 2 zol 2 owl drop key diff --git a/worlds/ladx/LADXR/logic/dungeon4.py b/worlds/ladx/LADXR/logic/dungeon4.py index a7e06557fa..9a360fca37 100644 --- a/worlds/ladx/LADXR/logic/dungeon4.py +++ b/worlds/ladx/LADXR/logic/dungeon4.py @@ -29,7 +29,7 @@ class Dungeon4: left_water_area = Location(dungeon=4).connect(before_miniboss, OR(FEATHER, FLIPPERS)) # area left with zol chest and 5 symbol puzzle (water area) left_water_area.add(DungeonChest(0x16D)) # gel chest left_water_area.add(DungeonChest(0x168)) # key chest near the puzzle - miniboss = Location(dungeon=4).connect(before_miniboss, AND(KEY4, FOUND(KEY4, 5), r.miniboss_requirements[world_setup.miniboss_mapping[3]])) + miniboss = Location(dungeon=4).connect(before_miniboss, AND(KEY4, FOUND(KEY4, 5), r.miniboss_requirements[world_setup.miniboss_mapping['3']])) terrace_zols_chest = Location(dungeon=4).connect(before_miniboss, FLIPPERS) # flippers to move around miniboss through 5 tile room miniboss = Location(dungeon=4).connect(terrace_zols_chest, POWER_BRACELET, one_way=True) # reach flippers chest through the miniboss room terrace_zols_chest.add(DungeonChest(0x160)) # flippers chest diff --git a/worlds/ladx/LADXR/logic/dungeon5.py b/worlds/ladx/LADXR/logic/dungeon5.py index b61e48e255..17a40f1e73 100644 --- a/worlds/ladx/LADXR/logic/dungeon5.py +++ b/worlds/ladx/LADXR/logic/dungeon5.py @@ -15,7 +15,7 @@ class Dungeon5: Location(dungeon=5).add(OwlStatue(0x19A)).connect(area2, STONE_BEAK5) Location(dungeon=5).add(DungeonChest(0x19B)).connect(area2, r.attack_hookshot_powder) # map chest blade_trap_chest = Location(dungeon=5).add(DungeonChest(0x197)).connect(area2, HOOKSHOT) # key chest on the left - post_gohma = Location(dungeon=5).connect(area2, AND(HOOKSHOT, r.miniboss_requirements[world_setup.miniboss_mapping[4]], KEY5, FOUND(KEY5,2))) # staircase after gohma + post_gohma = Location(dungeon=5).connect(area2, AND(HOOKSHOT, r.miniboss_requirements[world_setup.miniboss_mapping['4']], KEY5, FOUND(KEY5,2))) # staircase after gohma staircase_before_boss = Location(dungeon=5).connect(post_gohma, AND(HOOKSHOT, FEATHER)) # bottom right section pits room before boss door. Path via gohma after_keyblock_boss = Location(dungeon=5).connect(staircase_before_boss, AND(KEY5, FOUND(KEY5, 3))) # top right section pits room before boss door after_stalfos = Location(dungeon=5).add(DungeonChest(0x196)).connect(area2, AND(SWORD, BOMB)) # Need to defeat master stalfos once for this empty chest; l2 sword beams kill but obscure diff --git a/worlds/ladx/LADXR/logic/dungeon6.py b/worlds/ladx/LADXR/logic/dungeon6.py index cde40a6b2d..aff3e72b1c 100644 --- a/worlds/ladx/LADXR/logic/dungeon6.py +++ b/worlds/ladx/LADXR/logic/dungeon6.py @@ -24,7 +24,7 @@ class Dungeon6: # right side to_miniboss = Location(dungeon=6).connect(entrance, KEY6) - miniboss = Location(dungeon=6).connect(to_miniboss, AND(BOMB, r.miniboss_requirements[world_setup.miniboss_mapping[5]])) + miniboss = Location(dungeon=6).connect(to_miniboss, AND(BOMB, r.miniboss_requirements[world_setup.miniboss_mapping['5']])) lower_right_side = Location(dungeon=6).add(DungeonChest(0x1BE)).connect(entrance, AND(r.attack_wizrobe, COUNT(POWER_BRACELET, 2))) # waterway key medicine_chest = Location(dungeon=6).add(DungeonChest(0x1D1)).connect(lower_right_side, FEATHER) # ledge chest medicine if options.owlstatues == "both" or options.owlstatues == "dungeon": diff --git a/worlds/ladx/LADXR/logic/dungeon7.py b/worlds/ladx/LADXR/logic/dungeon7.py index 6188138f38..e2fdf986c2 100644 --- a/worlds/ladx/LADXR/logic/dungeon7.py +++ b/worlds/ladx/LADXR/logic/dungeon7.py @@ -22,7 +22,7 @@ class Dungeon7: # Most of the dungeon can be accessed at this point. if options.owlstatues == "both" or options.owlstatues == "dungeon": bottomleft_owl = Location(dungeon=7).add(OwlStatue(0x21C)).connect(bottomleftF2_area, AND(BOMB, STONE_BEAK7)) - nightmare_key = Location(dungeon=7).add(DungeonChest(0x224)).connect(bottomleftF2_area, r.miniboss_requirements[world_setup.miniboss_mapping[6]]) # nightmare key after the miniboss + nightmare_key = Location(dungeon=7).add(DungeonChest(0x224)).connect(bottomleftF2_area, r.miniboss_requirements[world_setup.miniboss_mapping['6']]) # nightmare key after the miniboss mirror_shield_chest = Location(dungeon=7).add(DungeonChest(0x21A)).connect(bottomleftF2_area, r.hit_switch) # mirror shield chest, need to be able to hit a switch to reach or bottomleftF2_area.connect(mirror_shield_chest, AND(KEY7, FOUND(KEY7, 3)), one_way = True) # reach mirror shield chest from hinox area by opening keyblock toprightF1_chest = Location(dungeon=7).add(DungeonChest(0x204)).connect(bottomleftF2_area, r.hit_switch) # chest on the F1 right ledge. Added attack_hookshot since switch needs to be hit to get back up diff --git a/worlds/ladx/LADXR/logic/dungeon8.py b/worlds/ladx/LADXR/logic/dungeon8.py index 5da2f8234e..0265da2c19 100644 --- a/worlds/ladx/LADXR/logic/dungeon8.py +++ b/worlds/ladx/LADXR/logic/dungeon8.py @@ -40,7 +40,7 @@ class Dungeon8: middle_center_2 = Location(dungeon=8).connect(middle_center_1, AND(KEY8, FOUND(KEY8, 4))) middle_center_3 = Location(dungeon=8).connect(middle_center_2, KEY8) miniboss_entrance = Location(dungeon=8).connect(middle_center_3, AND(HOOKSHOT, KEY8, FOUND(KEY8, 7))) # hookshot to get across to keyblock, 7 to fix keylock issues if keys are used on other keyblocks - miniboss = Location(dungeon=8).connect(miniboss_entrance, AND(FEATHER, r.miniboss_requirements[world_setup.miniboss_mapping[7]])) # feather for 2d section, sword to kill + miniboss = Location(dungeon=8).connect(miniboss_entrance, AND(FEATHER, r.miniboss_requirements[world_setup.miniboss_mapping['7']])) # feather for 2d section, sword to kill miniboss.add(DungeonChest(0x237)) # fire rod chest up_left = Location(dungeon=8).connect(upper_center, AND(r.attack_hookshot_powder, AND(KEY8, FOUND(KEY8, 4)))) @@ -94,7 +94,7 @@ class Dungeon8: entrance.connect(bottomright_pot_chest, r.shaq_jump, one_way=True) # use NW zamboni staircase backwards, and get a naked shaq jump off the bottom wall in the bottom right corner to pass by the pot gibdos_drop_key.connect(upper_center, AND(FEATHER, SHIELD)) # lock gibdos into pits and crack the tile they stand on, then use shield to bump them into the pit medicine_chest.connect(upper_center, AND(r.pit_buffer_boots, HOOKSHOT)) # boots bonk + lava buffer to the bottom wall, then bonk onto the middle section - miniboss.connect(miniboss_entrance, AND(r.boots_bonk_2d_hell, r.miniboss_requirements[world_setup.miniboss_mapping[7]])) # get through 2d section with boots bonks + miniboss.connect(miniboss_entrance, AND(r.boots_bonk_2d_hell, r.miniboss_requirements[world_setup.miniboss_mapping['7']])) # get through 2d section with boots bonks top_left_stairs.connect(map_chest, AND(r.jesus_buffer, r.boots_bonk_2d_hell, MAGIC_ROD)) # boots bonk + lava buffer from map chest to entrance_up, then boots bonk through 2d section nightmare_key.connect(top_left_stairs, AND(r.boots_bonk_pit, SWORD, FOUND(KEY8, 7))) # use a boots bonk to cross the 2d section + the lava in cueball room bottom_right.connect(entrance_up, AND(POWER_BRACELET, r.jesus_buffer), one_way=True) # take staircase to NW zamboni room, boots bonk onto the lava and water buffer all the way down to push the zamboni diff --git a/worlds/ladx/LADXR/patches/core.py b/worlds/ladx/LADXR/patches/core.py index 10e85f9dc5..4ae95b818a 100644 --- a/worlds/ladx/LADXR/patches/core.py +++ b/worlds/ladx/LADXR/patches/core.py @@ -541,8 +541,8 @@ 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: int): - if boots_controls == BootsControls.option_vanilla: +def addBootsControls(rom, bootscontrols): + if bootscontrols == 'vanilla': return consts = { "INVENTORY_PEGASUS_BOOTS": 0x8, @@ -560,25 +560,25 @@ def addBootsControls(rom, boots_controls: int): BOOTS_START_ADDR = 0x11E8 condition = { - BootsControls.option_bracelet: """ + 'bracelet': """ ld a, [hl] ; Check if we are using the bracelet cp INVENTORY_POWER_BRACELET jr z, .yesBoots """, - BootsControls.option_press_a: """ + 'pressa': """ ; Check if we are using the A slot cp J_A jr z, .yesBoots ld a, [hl] """, - BootsControls.option_press_b: """ + 'pressb': """ ; Check if we are using the B slot cp J_B jr z, .yesBoots ld a, [hl] """ - }[boots_controls] + }[bootscontrols] # The new code fits exactly within Nintendo's poorly space optimzied code while having more features boots_code = assembler.ASM(""" diff --git a/worlds/ladx/LADXR/patches/tradeSequence.py b/worlds/ladx/LADXR/patches/tradeSequence.py index ef6f635d45..0eb46ae23a 100644 --- a/worlds/ladx/LADXR/patches/tradeSequence.py +++ b/worlds/ladx/LADXR/patches/tradeSequence.py @@ -387,7 +387,7 @@ def patchVarious(rom, settings): # Boomerang trade guy # if settings.boomerang not in {'trade', 'gift'} or settings.overworld in {'normal', 'nodungeons'}: - if settings["tradequest"]: + if settings.tradequest: # Update magnifier checks rom.patch(0x19, 0x05EC, ASM("ld a, [wTradeSequenceItem]\ncp $0E\njp nz, $7E61"), ASM("ld a, [wTradeSequenceItem2]\nand $20\njp z, $7E61")) # show the guy rom.patch(0x00, 0x3199, ASM("ld a, [wTradeSequenceItem]\ncp $0E\njr nz, $06"), ASM("ld a, [wTradeSequenceItem2]\nand $20\njr z, $06")) # load the proper room layout diff --git a/worlds/ladx/LADXR/settings.py b/worlds/ladx/LADXR/settings.py index c0949f2678..23640f2164 100644 --- a/worlds/ladx/LADXR/settings.py +++ b/worlds/ladx/LADXR/settings.py @@ -68,7 +68,7 @@ class Setting: class Settings: - def __init__(self, ap_options): + def __init__(self, settings_dict): self.__all = [ Setting('seed', 'Main', '<', 'Seed', placeholder='Leave empty for random seed', default="", multiworld=False, description="""For multiple people to generate the same randomization result, enter the generated seed number here. @@ -178,6 +178,14 @@ Note, some entrances can lead into water, use the warp-to-home from the save&qui description='Replaces the hints from owl statues with additional randomized items'), Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False, description='All items will be more powerful, faster, harder, bigger stronger. You name it.'), + Setting('trendygame', 'Special', 'a', 'Trendy Game', description="", + options=[('easy', 'e', 'Easy'), ('normal', 'n', 'Normal'), ('hard', 'h', 'Hard'), ('harder', 'r', 'Harder'), ('hardest', 't', 'Hardest'), ('impossible', 'i', 'Impossible')], default='normal'), + Setting('warps', 'Special', 'a', 'Warps', description="", + options=[('vanilla', 'v', 'Vanilla'), ('improved', 'i', 'Improved'), ('improvedadditional', 'a', 'Improved Additional')], default='vanilla'), + Setting('shufflenightmarekeys', 'Special', 'a', 'Shuffle Nightmare Keys', description="", + options=[('originaldungeon', '0', 'Original Dungeon'), ('owndungeons', '1', 'Own Dungeons'), ('ownworld', '2', 'Own World'), ('anyworld', '3', 'Any World'), ('differentworld', '4', 'Different World')], default="originaldungeon"), + Setting('shufflesmallkeys', 'Special', 'a', 'Shuffle Small Keys', description="", + options=[('originaldungeon', '0', 'Original Dungeon'), ('owndungeons', '1', 'Own Dungeons'), ('ownworld', '2', 'Own World'), ('anyworld', '3', 'Any World'), ('differentworld', '4', 'Different World')], default="originaldungeon"), Setting('quickswap', 'User options', 'Q', 'Quickswap', options=[('none', '', 'Disabled'), ('a', 'a', 'Swap A button'), ('b', 'b', 'Swap B button')], default='none', description='Adds that the select button swaps with either A or B. The item is swapped with the top inventory slot. The map is not available when quickswap is enabled.', aesthetic=True), @@ -192,7 +200,7 @@ Note, some entrances can lead into water, use the warp-to-home from the save&qui Setting('nagmessages', 'User options', 'S', 'Show nag messages', default=False, description='Enables the nag messages normally shown when touching stones and crystals', aesthetic=True), - Setting('gfxmod', 'User options', 'c', 'Graphics', default='', + Setting('gfxmod', 'User options', 'c', 'Graphics', default=False, description='Generally affects at least Link\'s sprite, but can alter any graphics in the game', aesthetic=True), Setting('linkspalette', 'User options', 'C', "Link's color", @@ -202,25 +210,31 @@ Note, some entrances can lead into water, use the warp-to-home from the save&qui [Normal] color of link depends on the tunic. [Green/Yellow/Red/Blue] forces link into one of these colors. [?? A/B/C/D] colors of link are usually inverted and color depends on the area you are in."""), + Setting('palette', 'User options', 'a', 'Palette', description="", + options=[('normal', 'n', 'Normal'), ('1bit', '1', '1 Bit'), ('2bit', '2', '2 Bit'), ('greyscale', 'g', 'Greyscale'), ('pink', 'p', 'Pink'), ('inverted', 'i', 'Inverted')], default='normal', aesthetic=True), Setting('music', 'User options', 'M', 'Music', options=[('', '', 'Default'), ('random', 'r', 'Random'), ('off', 'o', 'Disable')], default='', description=""" [Random] Randomizes overworld and dungeon music' [Disable] no music in the whole game""", aesthetic=True), + Setting('musicchange', 'User options', 'a', 'Music Change Condition', description="", + options=[('always', 'a', 'Always'), ('sword', 's', 'Sword')], default='always', aesthetic=True), + Setting('bootscontrols', 'User options', 'a', 'Boots Controls', description="", + options=[('vanilla', 'v', 'Vanilla'), ('bracelet', 'p', 'Bracelet'), ('pressa', 'a', 'Press A'), ('pressb', 'b', 'Press B')], default='vanilla', aesthetic=True), + Setting('foreignitemicons', 'User options', 'a', 'Foreign Item Icons', description="", + options=[('guessbyname', 'g', 'Guess By Name'), ('indicateprogression', 'p', 'Indicate Progression')], default="guessbyname", aesthetic=True), + Setting('aptitlescreen', 'User options', 'a', 'AP Title Screen', description="", default=True), + Setting('textshuffle', 'User options', 'a', 'Text Shuffle', description="", default=False), ] self.__by_key = {s.key: s for s in self.__all} - # Make sure all short keys are unique - short_keys = set() - for s in self.__all: - assert s.short_key not in short_keys, s.label - short_keys.add(s.short_key) - self.ap_options = ap_options + # don't worry about unique short keys for AP + #short_keys = set() + #for s in self.__all: + # assert s.short_key not in short_keys, s.label + # short_keys.add(s.short_key) - for option in self.ap_options.values(): - if not hasattr(option, 'to_ladxr_option'): - continue - name, value = option.to_ladxr_option(self.ap_options) + for name, value in settings_dict.items(): if value == "true": value = 1 elif value == "false": diff --git a/worlds/ladx/LADXR/worldSetup.py b/worlds/ladx/LADXR/worldSetup.py index d7ca37f203..940753a7d1 100644 --- a/worlds/ladx/LADXR/worldSetup.py +++ b/worlds/ladx/LADXR/worldSetup.py @@ -28,7 +28,7 @@ class WorldSetup: self.boss_mapping = list(range(9)) self.miniboss_mapping = { # Main minibosses - 0: "ROLLING_BONES", 1: "HINOX", 2: "DODONGO", 3: "CUE_BALL", 4: "GHOMA", 5: "SMASHER", 6: "GRIM_CREEPER", 7: "BLAINO", + '0': "ROLLING_BONES", '1': "HINOX", '2': "DODONGO", '3': "CUE_BALL", '4': "GHOMA", '5': "SMASHER", '6': "GRIM_CREEPER", '7': "BLAINO", # Color dungeon needs to be special, as always. "c1": "AVALAUNCH", "c2": "GIANT_BUZZ_BLOB", # Overworld diff --git a/worlds/ladx/Options.py b/worlds/ladx/Options.py index 47328036dd..e368e9d74f 100644 --- a/worlds/ladx/Options.py +++ b/worlds/ladx/Options.py @@ -60,11 +60,12 @@ class TradeQuest(DefaultOffToggle, LADXROption): ladxr_name = "tradequest" -class TextShuffle(DefaultOffToggle): +class TextShuffle(DefaultOffToggle, LADXROption): """ Shuffles all text in the game. """ display_name = "Text Shuffle" + ladxr_name = "textshuffle" class Rooster(DefaultOnToggle, LADXROption): @@ -112,11 +113,12 @@ class DungeonShuffle(DefaultOffToggle, LADXROption): ladxr_name = "dungeonshuffle" -class APTitleScreen(DefaultOnToggle): +class APTitleScreen(DefaultOnToggle, LADXROption): """ Enables AP specific title screen and disables the intro cutscene. """ display_name = "AP Title Screen" + ladxr_name = "aptitlescreen" class BossShuffle(Choice): @@ -142,7 +144,7 @@ class DungeonItemShuffle(Choice): ladxr_item: str -class ShuffleNightmareKeys(DungeonItemShuffle): +class ShuffleNightmareKeys(DungeonItemShuffle, LADXROption): """ **Original Dungeon:** The item will be within its original dungeon. @@ -156,9 +158,10 @@ class ShuffleNightmareKeys(DungeonItemShuffle): """ display_name = "Shuffle Nightmare Keys" ladxr_item = "NIGHTMARE_KEY" + ladxr_name = "shufflenightmarekeys" -class ShuffleSmallKeys(DungeonItemShuffle): +class ShuffleSmallKeys(DungeonItemShuffle, LADXROption): """ **Original Dungeon:** The item will be within its original dungeon. @@ -172,6 +175,7 @@ class ShuffleSmallKeys(DungeonItemShuffle): """ display_name = "Shuffle Small Keys" ladxr_item = "KEY" + ladxr_name = "shufflesmallkeys" class ShuffleMaps(DungeonItemShuffle): @@ -266,7 +270,7 @@ class Goal(Choice, LADXROption): def to_ladxr_option(self, all_options): if self.value == self.option_instruments: - return ("goal", all_options["instrument_count"]) + return ("goal", int(all_options["instrument_count"])) else: return LADXROption.to_ladxr_option(self, all_options) @@ -291,7 +295,7 @@ class NagMessages(DefaultOffToggle, LADXROption): ladxr_name = "nagmessages" -class MusicChangeCondition(Choice): +class MusicChangeCondition(Choice, LADXROption): """ Controls how the music changes. @@ -304,6 +308,7 @@ class MusicChangeCondition(Choice): option_sword = 0 option_always = 1 default = option_always + ladxr_name = "musicchange" class HardMode(Choice, LADXROption): @@ -396,7 +401,7 @@ class NoFlash(DefaultOnToggle, LADXROption): ladxr_name = "noflash" -class BootsControls(Choice): +class BootsControls(Choice, LADXROption): """ Adds an additional button to activate Pegasus Boots (does nothing if you haven't picked up your boots!) @@ -418,6 +423,7 @@ class BootsControls(Choice): alias_a = 2 option_press_b = 3 alias_b = 3 + ladxr_name = "bootscontrols" class LinkPalette(Choice, LADXROption): @@ -444,7 +450,7 @@ class LinkPalette(Choice, LADXROption): return self.ladxr_name, str(self.value) -class TrendyGame(Choice): +class TrendyGame(Choice, LADXROption): """ **Easy:** All of the items hold still for you. @@ -468,16 +474,18 @@ class TrendyGame(Choice): option_hardest = 4 option_impossible = 5 default = option_normal + ladxr_name = "trendygame" -class GfxMod(DefaultOffToggle): +class GfxMod(DefaultOffToggle, LADXROption): """ If enabled, the patcher will prompt the user for a modification file to change sprites in the game and optionally some text. """ display_name = "GFX Modification" + ladxr_name = "gfxmod" -class Palette(Choice): +class Palette(Choice, LADXROption): """ Sets the palette for the game. @@ -504,6 +512,7 @@ class Palette(Choice): option_greyscale = 3 option_pink = 4 option_inverted = 5 + ladxr_name = "palette" class Music(Choice, LADXROption): @@ -530,7 +539,7 @@ class Music(Choice, LADXROption): return self.ladxr_name, s -class Warps(Choice): +class Warps(Choice, LADXROption): """ **Improved:** 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. @@ -544,6 +553,7 @@ class Warps(Choice): option_improved = 1 option_improved_additional = 2 default = option_vanilla + ladxr_name = 'warps' class InGameHints(DefaultOnToggle): @@ -583,7 +593,7 @@ class StabilizeItemPool(DefaultOffToggle): rich_text_doc = True -class ForeignItemIcons(Choice): +class ForeignItemIcons(Choice, LADXROption): """ Choose how to display foreign items. @@ -597,6 +607,7 @@ class ForeignItemIcons(Choice): option_guess_by_name = 0 option_indicate_progression = 1 default = option_guess_by_name + ladxr_name = 'foreignitemicons' ladx_option_groups = [ diff --git a/worlds/ladx/Rom.py b/worlds/ladx/Rom.py index e0ee4b98ea..faecdf38d1 100644 --- a/worlds/ladx/Rom.py +++ b/worlds/ladx/Rom.py @@ -6,13 +6,11 @@ import json import pkgutil import bsdiff4 import binascii -import pickle from typing import TYPE_CHECKING from .Common import * from .LADXR import generator from .LADXR.main import get_parser from .LADXR.hints import generate_hint_texts -from .LADXR.locations.keyLocation import KeyLocation LADX_HASH = "07c211479386825042efb4ad31bb525f" if TYPE_CHECKING: @@ -35,7 +33,7 @@ class LADXPatchExtensions(worlds.Files.APPatchExtension): @staticmethod def patch_title_screen(caller: worlds.Files.APProcedurePatch, rom: bytes, data_file: str) -> bytes: patch_data = json.loads(caller.get_file(data_file).decode("utf-8")) - if patch_data["options"]["ap_title_screen"]: + if patch_data["ladxr_settings_dict"]["aptitlescreen"] == 'true': return bsdiff4.patch(rom, pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4")) return rom @@ -56,7 +54,6 @@ class LADXProcedurePatch(worlds.Files.APProcedurePatch): def write_patch_data(world: "LinksAwakeningWorld", patch: LADXProcedurePatch): - item_list = pickle.dumps([item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)]) data_dict = { "generated_world_version": world.world_version.as_simple_string(), "out_base": world.multiworld.get_out_file_name_base(patch.player), @@ -67,44 +64,16 @@ def write_patch_data(world: "LinksAwakeningWorld", patch: LADXProcedurePatch): "player": patch.player, "player_name": patch.player_name, "other_player_names": list(world.multiworld.player_name.values()), - "item_list": binascii.hexlify(item_list).decode(), + "rom_item_placements": world.rom_item_placements, "hint_texts": generate_hint_texts(world), "world_setup": { "goal": world.ladxr_logic.world_setup.goal, - "bingo_goals": world.ladxr_logic.world_setup.bingo_goals, "multichest": world.ladxr_logic.world_setup.multichest, "entrance_mapping": world.ladxr_logic.world_setup.entrance_mapping, "boss_mapping": world.ladxr_logic.world_setup.boss_mapping, "miniboss_mapping": world.ladxr_logic.world_setup.miniboss_mapping, }, - "options": world.options.as_dict( - "tradequest", - "rooster", - "experimental_dungeon_shuffle", - "experimental_entrance_shuffle", - "goal", - "instrument_count", - "link_palette", - "warps", - "trendy_game", - "gfxmod", - "palette", - "text_shuffle", - "shuffle_nightmare_keys", - "shuffle_small_keys", - "music", - "music_change_condition", - "nag_messages", - "ap_title_screen", - "boots_controls", - "stealing", - "quickswap", - "hard_mode", - "low_hp_beep", - "text_mode", - "no_flash", - "overworld", - ), + "ladxr_settings_dict": world.ladxr_settings_dict, } patch.write_file("data.json", json.dumps(data_dict).encode('utf-8')) diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py index c9f907046a..e36ff77a24 100644 --- a/worlds/ladx/__init__.py +++ b/worlds/ladx/__init__.py @@ -180,7 +180,17 @@ class LinksAwakeningWorld(World): } def convert_ap_options_to_ladxr_logic(self): - self.ladxr_settings = LADXRSettings(dataclasses.asdict(self.options)) + # store a dict of ladxr settings as a middle step so that we can also create a + # ladxr settings object on the other side of the patch + options_dict = dataclasses.asdict(self.options) + self.ladxr_settings_dict = {} + for option in options_dict.values(): + if not hasattr(option, 'to_ladxr_option'): + continue + name, value = option.to_ladxr_option(options_dict) + if name: + self.ladxr_settings_dict[name] = value + self.ladxr_settings = LADXRSettings(self.ladxr_settings_dict) self.ladxr_settings.validate() world_setup = LADXRWorldSetup() @@ -503,36 +513,36 @@ class LinksAwakeningWorld(World): return "TRADING_ITEM_LETTER" def generate_output(self, output_directory: str): - # copy items back to locations + self.rom_item_placements = [] for r in self.multiworld.get_regions(self.player): for loc in r.locations: if isinstance(loc, LinksAwakeningLocation): assert(loc.item) - + spot = {} # If we're a links awakening item, just use the item if isinstance(loc.item, LinksAwakeningItem): - loc.ladxr_item.item = loc.item.item_data.ladxr_id + spot["item"] = loc.item.item_data.ladxr_id # If the item name contains "sword", use a sword icon, etc # Otherwise, use a cute letter as the icon elif self.options.foreign_item_icons == 'guess_by_name': - loc.ladxr_item.item = self.guess_icon_for_other_world(loc.item) - loc.ladxr_item.setCustomItemName(loc.item.name) + spot["item"] = self.guess_icon_for_other_world(loc.item) else: if loc.item.advancement: - loc.ladxr_item.item = 'PIECE_OF_POWER' + spot["item"] = 'PIECE_OF_POWER' else: - loc.ladxr_item.item = 'GUARDIAN_ACORN' - loc.ladxr_item.setCustomItemName(loc.item.name) + spot["item"] = 'GUARDIAN_ACORN' + + spot["custom_item_name"] = loc.item.name if loc.item: - loc.ladxr_item.item_owner = loc.item.player + spot["item_owner"] = loc.item.player else: - loc.ladxr_item.item_owner = self.player + spot["item_owner"] = self.player - # Kind of kludge, make it possible for the location to differentiate between local and remote items - loc.ladxr_item.location_owner = self.player + spot["name_id"] = loc.ladxr_item.nameId + self.rom_item_placements.append(spot) patch = LADXProcedurePatch(player=self.player, player_name=self.player_name)