LADX: Converted to new options API (+other small refactors) (#3542)

* Refactored various things

* Renamed hidden variable in dungeon item shuffle block

* Fixed LADXRSettings initialization

* Rename ladxr_options -> ladxr_settings

* Remove unnecessary int cast

* Update worlds/ladx/LADXR/generator.py

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>

---------

Co-authored-by: Exempt-Medic <60412657+Exempt-Medic@users.noreply.github.com>
This commit is contained in:
Star Rauchenberger 2024-06-17 22:48:15 -04:00 committed by GitHub
parent 898509e7ee
commit af213c9e5d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 191 additions and 162 deletions

View File

@ -4,6 +4,7 @@ import importlib.machinery
import os import os
import pkgutil import pkgutil
from collections import defaultdict from collections import defaultdict
from typing import TYPE_CHECKING
from .romTables import ROMWithTables from .romTables import ROMWithTables
from . import assembler from . import assembler
@ -67,10 +68,14 @@ from BaseClasses import ItemClassification
from ..Locations import LinksAwakeningLocation from ..Locations import LinksAwakeningLocation
from ..Options import TrendyGame, Palette, MusicChangeCondition, BootsControls from ..Options import TrendyGame, Palette, MusicChangeCondition, BootsControls
if TYPE_CHECKING:
from .. import LinksAwakeningWorld
# Function to generate a final rom, this patches the rom with all required patches # Function to generate a final rom, this patches the rom with all required patches
def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, multiworld=None, player_name=None, player_names=[], player_id = 0): def generateRom(args, world: "LinksAwakeningWorld"):
rom_patches = [] rom_patches = []
player_names = list(world.multiworld.player_name.values())
rom = ROMWithTables(args.input_filename, rom_patches) rom = ROMWithTables(args.input_filename, rom_patches)
rom.player_names = player_names rom.player_names = player_names
@ -84,10 +89,10 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
for pymod in pymods: for pymod in pymods:
pymod.prePatch(rom) pymod.prePatch(rom)
if settings.gfxmod: if world.ladxr_settings.gfxmod:
patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", settings.gfxmod)) patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", world.ladxr_settings.gfxmod))
item_list = [item for item in logic.iteminfo_list if not isinstance(item, KeyLocation)] item_list = [item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)]
assembler.resetConsts() assembler.resetConsts()
assembler.const("INV_SIZE", 16) assembler.const("INV_SIZE", 16)
@ -116,7 +121,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
assembler.const("wLinkSpawnDelay", 0xDE13) assembler.const("wLinkSpawnDelay", 0xDE13)
#assembler.const("HARDWARE_LINK", 1) #assembler.const("HARDWARE_LINK", 1)
assembler.const("HARD_MODE", 1 if settings.hardmode != "none" else 0) assembler.const("HARD_MODE", 1 if world.ladxr_settings.hardmode != "none" else 0)
patches.core.cleanup(rom) patches.core.cleanup(rom)
patches.save.singleSaveSlot(rom) patches.save.singleSaveSlot(rom)
@ -130,7 +135,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
patches.core.easyColorDungeonAccess(rom) patches.core.easyColorDungeonAccess(rom)
patches.owl.removeOwlEvents(rom) patches.owl.removeOwlEvents(rom)
patches.enemies.fixArmosKnightAsMiniboss(rom) patches.enemies.fixArmosKnightAsMiniboss(rom)
patches.bank3e.addBank3E(rom, auth, player_id, player_names) patches.bank3e.addBank3E(rom, world.multi_key, world.player, player_names)
patches.bank3f.addBank3F(rom) patches.bank3f.addBank3F(rom)
patches.bank34.addBank34(rom, item_list) patches.bank34.addBank34(rom, item_list)
patches.core.removeGhost(rom) patches.core.removeGhost(rom)
@ -141,10 +146,11 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
from ..Options import ShuffleSmallKeys, ShuffleNightmareKeys 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: if world.options.shuffle_small_keys != ShuffleSmallKeys.option_original_dungeon or\
world.options.shuffle_nightmare_keys != ShuffleNightmareKeys.option_original_dungeon:
patches.inventory.advancedInventorySubscreen(rom) patches.inventory.advancedInventorySubscreen(rom)
patches.inventory.moreSlots(rom) patches.inventory.moreSlots(rom)
if settings.witch: if world.ladxr_settings.witch:
patches.witch.updateWitch(rom) patches.witch.updateWitch(rom)
patches.softlock.fixAll(rom) patches.softlock.fixAll(rom)
patches.maptweaks.tweakMap(rom) patches.maptweaks.tweakMap(rom)
@ -158,9 +164,9 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
patches.tarin.updateTarin(rom) patches.tarin.updateTarin(rom)
patches.fishingMinigame.updateFinishingMinigame(rom) patches.fishingMinigame.updateFinishingMinigame(rom)
patches.health.upgradeHealthContainers(rom) patches.health.upgradeHealthContainers(rom)
if settings.owlstatues in ("dungeon", "both"): if world.ladxr_settings.owlstatues in ("dungeon", "both"):
patches.owl.upgradeDungeonOwlStatues(rom) patches.owl.upgradeDungeonOwlStatues(rom)
if settings.owlstatues in ("overworld", "both"): if world.ladxr_settings.owlstatues in ("overworld", "both"):
patches.owl.upgradeOverworldOwlStatues(rom) patches.owl.upgradeOverworldOwlStatues(rom)
patches.goldenLeaf.fixGoldenLeaf(rom) patches.goldenLeaf.fixGoldenLeaf(rom)
patches.heartPiece.fixHeartPiece(rom) patches.heartPiece.fixHeartPiece(rom)
@ -170,106 +176,110 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
patches.songs.upgradeMarin(rom) patches.songs.upgradeMarin(rom)
patches.songs.upgradeManbo(rom) patches.songs.upgradeManbo(rom)
patches.songs.upgradeMamu(rom) patches.songs.upgradeMamu(rom)
if settings.tradequest: if world.ladxr_settings.tradequest:
patches.tradeSequence.patchTradeSequence(rom, settings.boomerang) patches.tradeSequence.patchTradeSequence(rom, world.ladxr_settings.boomerang)
else: else:
# Monkey bridge patch, always have the bridge there. # Monkey bridge patch, always have the bridge there.
rom.patch(0x00, 0x333D, assembler.ASM("bit 4, e\njr Z, $05"), b"", fill_nop=True) rom.patch(0x00, 0x333D, assembler.ASM("bit 4, e\njr Z, $05"), b"", fill_nop=True)
patches.bowwow.fixBowwow(rom, everywhere=settings.bowwow != 'normal') patches.bowwow.fixBowwow(rom, everywhere=world.ladxr_settings.bowwow != 'normal')
if settings.bowwow != 'normal': if world.ladxr_settings.bowwow != 'normal':
patches.bowwow.bowwowMapPatches(rom) patches.bowwow.bowwowMapPatches(rom)
patches.desert.desertAccess(rom) patches.desert.desertAccess(rom)
if settings.overworld == 'dungeondive': if world.ladxr_settings.overworld == 'dungeondive':
patches.overworld.patchOverworldTilesets(rom) patches.overworld.patchOverworldTilesets(rom)
patches.overworld.createDungeonOnlyOverworld(rom) patches.overworld.createDungeonOnlyOverworld(rom)
elif settings.overworld == 'nodungeons': elif world.ladxr_settings.overworld == 'nodungeons':
patches.dungeon.patchNoDungeons(rom) patches.dungeon.patchNoDungeons(rom)
elif settings.overworld == 'random': elif world.ladxr_settings.overworld == 'random':
patches.overworld.patchOverworldTilesets(rom) patches.overworld.patchOverworldTilesets(rom)
mapgen.store_map(rom, logic.world.map) mapgen.store_map(rom, world.ladxr_logic.world.map)
#if settings.dungeon_items == 'keysy': #if settings.dungeon_items == 'keysy':
# patches.dungeon.removeKeyDoors(rom) # patches.dungeon.removeKeyDoors(rom)
# patches.reduceRNG.slowdownThreeOfAKind(rom) # patches.reduceRNG.slowdownThreeOfAKind(rom)
patches.reduceRNG.fixHorseHeads(rom) patches.reduceRNG.fixHorseHeads(rom)
patches.bomb.onlyDropBombsWhenHaveBombs(rom) patches.bomb.onlyDropBombsWhenHaveBombs(rom)
if ap_settings['music_change_condition'] == MusicChangeCondition.option_always: if world.options.music_change_condition == MusicChangeCondition.option_always:
patches.aesthetics.noSwordMusic(rom) patches.aesthetics.noSwordMusic(rom)
patches.aesthetics.reduceMessageLengths(rom, rnd) patches.aesthetics.reduceMessageLengths(rom, world.random)
patches.aesthetics.allowColorDungeonSpritesEverywhere(rom) patches.aesthetics.allowColorDungeonSpritesEverywhere(rom)
if settings.music == 'random': if world.ladxr_settings.music == 'random':
patches.music.randomizeMusic(rom, rnd) patches.music.randomizeMusic(rom, world.random)
elif settings.music == 'off': elif world.ladxr_settings.music == 'off':
patches.music.noMusic(rom) patches.music.noMusic(rom)
if settings.noflash: if world.ladxr_settings.noflash:
patches.aesthetics.removeFlashingLights(rom) patches.aesthetics.removeFlashingLights(rom)
if settings.hardmode == "oracle": if world.ladxr_settings.hardmode == "oracle":
patches.hardMode.oracleMode(rom) patches.hardMode.oracleMode(rom)
elif settings.hardmode == "hero": elif world.ladxr_settings.hardmode == "hero":
patches.hardMode.heroMode(rom) patches.hardMode.heroMode(rom)
elif settings.hardmode == "ohko": elif world.ladxr_settings.hardmode == "ohko":
patches.hardMode.oneHitKO(rom) patches.hardMode.oneHitKO(rom)
if settings.superweapons: if world.ladxr_settings.superweapons:
patches.weapons.patchSuperWeapons(rom) patches.weapons.patchSuperWeapons(rom)
if settings.textmode == 'fast': if world.ladxr_settings.textmode == 'fast':
patches.aesthetics.fastText(rom) patches.aesthetics.fastText(rom)
if settings.textmode == 'none': if world.ladxr_settings.textmode == 'none':
patches.aesthetics.fastText(rom) patches.aesthetics.fastText(rom)
patches.aesthetics.noText(rom) patches.aesthetics.noText(rom)
if not settings.nagmessages: if not world.ladxr_settings.nagmessages:
patches.aesthetics.removeNagMessages(rom) patches.aesthetics.removeNagMessages(rom)
if settings.lowhpbeep == 'slow': if world.ladxr_settings.lowhpbeep == 'slow':
patches.aesthetics.slowLowHPBeep(rom) patches.aesthetics.slowLowHPBeep(rom)
if settings.lowhpbeep == 'none': if world.ladxr_settings.lowhpbeep == 'none':
patches.aesthetics.removeLowHPBeep(rom) patches.aesthetics.removeLowHPBeep(rom)
if 0 <= int(settings.linkspalette): if 0 <= int(world.ladxr_settings.linkspalette):
patches.aesthetics.forceLinksPalette(rom, int(settings.linkspalette)) patches.aesthetics.forceLinksPalette(rom, int(world.ladxr_settings.linkspalette))
if args.romdebugmode: if args.romdebugmode:
# The default rom has this build in, just need to set a flag and we get this save. # The default rom has this build in, just need to set a flag and we get this save.
rom.patch(0, 0x0003, "00", "01") rom.patch(0, 0x0003, "00", "01")
# Patch the sword check on the shopkeeper turning around. # Patch the sword check on the shopkeeper turning around.
if settings.steal == 'never': if world.ladxr_settings.steal == 'never':
rom.patch(4, 0x36F9, "FA4EDB", "3E0000") rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
elif settings.steal == 'always': elif world.ladxr_settings.steal == 'always':
rom.patch(4, 0x36F9, "FA4EDB", "3E0100") rom.patch(4, 0x36F9, "FA4EDB", "3E0100")
if settings.hpmode == 'inverted': if world.ladxr_settings.hpmode == 'inverted':
patches.health.setStartHealth(rom, 9) patches.health.setStartHealth(rom, 9)
elif settings.hpmode == '1': elif world.ladxr_settings.hpmode == '1':
patches.health.setStartHealth(rom, 1) patches.health.setStartHealth(rom, 1)
patches.inventory.songSelectAfterOcarinaSelect(rom) patches.inventory.songSelectAfterOcarinaSelect(rom)
if settings.quickswap == 'a': if world.ladxr_settings.quickswap == 'a':
patches.core.quickswap(rom, 1) patches.core.quickswap(rom, 1)
elif settings.quickswap == 'b': elif world.ladxr_settings.quickswap == 'b':
patches.core.quickswap(rom, 0) patches.core.quickswap(rom, 0)
patches.core.addBootsControls(rom, ap_settings['boots_controls']) patches.core.addBootsControls(rom, world.options.boots_controls)
world_setup = logic.world_setup world_setup = world.ladxr_logic.world_setup
JUNK_HINT = 0.33 JUNK_HINT = 0.33
RANDOM_HINT= 0.66 RANDOM_HINT= 0.66
# USEFUL_HINT = 1.0 # USEFUL_HINT = 1.0
# TODO: filter events, filter unshuffled keys # TODO: filter events, filter unshuffled keys
all_items = multiworld.get_items() all_items = world.multiworld.get_items()
our_items = [item for item in all_items if item.player == player_id and item.location and item.code is not None and item.location.show_in_spoiler] our_items = [item for item in all_items
if item.player == world.player
and item.location
and item.code is not None
and item.location.show_in_spoiler]
our_useful_items = [item for item in our_items if ItemClassification.progression in item.classification] our_useful_items = [item for item in our_items if ItemClassification.progression in item.classification]
def gen_hint(): def gen_hint():
chance = rnd.uniform(0, 1) chance = world.random.uniform(0, 1)
if chance < JUNK_HINT: if chance < JUNK_HINT:
return None return None
elif chance < RANDOM_HINT: elif chance < RANDOM_HINT:
location = rnd.choice(our_items).location location = world.random.choice(our_items).location
else: # USEFUL_HINT else: # USEFUL_HINT
location = rnd.choice(our_useful_items).location location = world.random.choice(our_useful_items).location
if location.item.player == player_id: if location.item.player == world.player:
name = "Your" name = "Your"
else: else:
name = f"{multiworld.player_name[location.item.player]}'s" name = f"{world.multiworld.player_name[location.item.player]}'s"
if isinstance(location, LinksAwakeningLocation): if isinstance(location, LinksAwakeningLocation):
location_name = location.ladxr_item.metadata.name location_name = location.ladxr_item.metadata.name
@ -277,8 +287,8 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
location_name = location.name location_name = location.name
hint = f"{name} {location.item} is at {location_name}" hint = f"{name} {location.item} is at {location_name}"
if location.player != player_id: if location.player != world.player:
hint += f" in {multiworld.player_name[location.player]}'s world" hint += f" in {world.multiworld.player_name[location.player]}'s world"
# Cap hint size at 85 # Cap hint size at 85
# Realistically we could go bigger but let's be safe instead # Realistically we could go bigger but let's be safe instead
@ -286,7 +296,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
return hint return hint
hints.addHints(rom, rnd, gen_hint) hints.addHints(rom, world.random, gen_hint)
if world_setup.goal == "raft": if world_setup.goal == "raft":
patches.goal.setRaftGoal(rom) patches.goal.setRaftGoal(rom)
@ -299,7 +309,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
# Patch the generated logic into the rom # Patch the generated logic into the rom
patches.chest.setMultiChest(rom, world_setup.multichest) patches.chest.setMultiChest(rom, world_setup.multichest)
if settings.overworld not in {"dungeondive", "random"}: if world.ladxr_settings.overworld not in {"dungeondive", "random"}:
patches.entrances.changeEntrances(rom, world_setup.entrance_mapping) patches.entrances.changeEntrances(rom, world_setup.entrance_mapping)
for spot in item_list: for spot in item_list:
if spot.item and spot.item.startswith("*"): if spot.item and spot.item.startswith("*"):
@ -318,15 +328,16 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
patches.core.addFrameCounter(rom, len(item_list)) patches.core.addFrameCounter(rom, len(item_list))
patches.core.warpHome(rom) # Needs to be done after setting the start location. patches.core.warpHome(rom) # Needs to be done after setting the start location.
patches.titleScreen.setRomInfo(rom, auth, seed_name, settings, player_name, player_id) patches.titleScreen.setRomInfo(rom, world.multi_key, world.multiworld.seed_name, world.ladxr_settings,
if ap_settings["ap_title_screen"]: world.player_name, world.player)
if world.options.ap_title_screen:
patches.titleScreen.setTitleGraphics(rom) patches.titleScreen.setTitleGraphics(rom)
patches.endscreen.updateEndScreen(rom) patches.endscreen.updateEndScreen(rom)
patches.aesthetics.updateSpriteData(rom) patches.aesthetics.updateSpriteData(rom)
if args.doubletrouble: if args.doubletrouble:
patches.enemies.doubleTrouble(rom) patches.enemies.doubleTrouble(rom)
if ap_settings["text_shuffle"]: if world.options.text_shuffle:
buckets = defaultdict(list) buckets = defaultdict(list)
# For each ROM bank, shuffle text within the bank # For each ROM bank, shuffle text within the bank
for n, data in enumerate(rom.texts._PointerTable__data): for n, data in enumerate(rom.texts._PointerTable__data):
@ -336,20 +347,20 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
for bucket in buckets.values(): for bucket in buckets.values():
# For each bucket, make a copy and shuffle # For each bucket, make a copy and shuffle
shuffled = bucket.copy() shuffled = bucket.copy()
rnd.shuffle(shuffled) world.random.shuffle(shuffled)
# Then put new text in # Then put new text in
for bucket_idx, (orig_idx, data) in enumerate(bucket): for bucket_idx, (orig_idx, data) in enumerate(bucket):
rom.texts[shuffled[bucket_idx][0]] = data rom.texts[shuffled[bucket_idx][0]] = data
if ap_settings["trendy_game"] != TrendyGame.option_normal: if world.options.trendy_game != TrendyGame.option_normal:
# TODO: if 0 or 4, 5, remove inaccurate conveyor tiles # TODO: if 0 or 4, 5, remove inaccurate conveyor tiles
room_editor = RoomEditor(rom, 0x2A0) room_editor = RoomEditor(rom, 0x2A0)
if ap_settings["trendy_game"] == TrendyGame.option_easy: if world.options.trendy_game == TrendyGame.option_easy:
# Set physics flag on all objects # Set physics flag on all objects
for i in range(0, 6): for i in range(0, 6):
rom.banks[0x4][0x6F1E + i -0x4000] = 0x4 rom.banks[0x4][0x6F1E + i -0x4000] = 0x4
@ -360,7 +371,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
# Add new conveyor to "push" yoshi (it's only a visual) # Add new conveyor to "push" yoshi (it's only a visual)
room_editor.objects.append(Object(5, 3, 0xD0)) room_editor.objects.append(Object(5, 3, 0xD0))
if int(ap_settings["trendy_game"]) >= TrendyGame.option_harder: if world.options.trendy_game >= TrendyGame.option_harder:
""" """
Data_004_76A0:: Data_004_76A0::
db $FC, $00, $04, $00, $00 db $FC, $00, $04, $00, $00
@ -374,12 +385,12 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
TrendyGame.option_impossible: (3, 16), TrendyGame.option_impossible: (3, 16),
} }
def speed(): def speed():
return rnd.randint(*speeds[ap_settings["trendy_game"]]) return world.random.randint(*speeds[world.options.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][0x76A2-0x4000] = speed()
rom.banks[0x4][0x76A6-0x4000] = speed() rom.banks[0x4][0x76A6-0x4000] = speed()
rom.banks[0x4][0x76A8-0x4000] = 0xFF - speed() rom.banks[0x4][0x76A8-0x4000] = 0xFF - speed()
if int(ap_settings["trendy_game"]) >= TrendyGame.option_hardest: if world.options.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][0x76A3-0x4000] = speed()
rom.banks[0x4][0x76A5-0x4000] = speed() rom.banks[0x4][0x76A5-0x4000] = speed()
@ -403,10 +414,10 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
for channel in range(3): for channel in range(3):
color[channel] = color[channel] * 31 // 0xbc color[channel] = color[channel] * 31 // 0xbc
if ap_settings["warp_improvements"]: if world.options.warp_improvements:
patches.core.addWarpImprovements(rom, ap_settings["additional_warp_points"]) patches.core.addWarpImprovements(rom, world.options.additional_warp_points)
palette = ap_settings["palette"] palette = world.options.palette
if palette != Palette.option_normal: if palette != Palette.option_normal:
ranges = { ranges = {
# Object palettes # Object palettes
@ -472,8 +483,8 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
SEED_LOCATION = 0x0134 SEED_LOCATION = 0x0134
# Patch over the title # Patch over the title
assert(len(auth) == 12) assert(len(world.multi_key) == 12)
rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(auth)) rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(world.multi_key))
for pymod in pymods: for pymod in pymods:
pymod.postPatch(rom) pymod.postPatch(rom)

View File

@ -1,7 +1,9 @@
from dataclasses import dataclass
import os.path import os.path
import typing import typing
import logging import logging
from Options import Choice, Option, Toggle, DefaultOnToggle, Range, FreeText from Options import Choice, Toggle, DefaultOnToggle, Range, FreeText, PerGameCommonOptions
from collections import defaultdict from collections import defaultdict
import Utils import Utils
@ -14,7 +16,7 @@ class LADXROption:
def to_ladxr_option(self, all_options): def to_ladxr_option(self, all_options):
if not self.ladxr_name: if not self.ladxr_name:
return None, None return None, None
return (self.ladxr_name, self.name_lookup[self.value].replace("_", "")) return (self.ladxr_name, self.name_lookup[self.value].replace("_", ""))
@ -32,9 +34,10 @@ class Logic(Choice, LADXROption):
option_hard = 2 option_hard = 2
option_glitched = 3 option_glitched = 3
option_hell = 4 option_hell = 4
default = option_normal default = option_normal
class TradeQuest(DefaultOffToggle, LADXROption): class TradeQuest(DefaultOffToggle, LADXROption):
""" """
[On] adds the trade items to the pool (the trade locations will always be local items) [On] adds the trade items to the pool (the trade locations will always be local items)
@ -43,12 +46,14 @@ class TradeQuest(DefaultOffToggle, LADXROption):
display_name = "Trade Quest" display_name = "Trade Quest"
ladxr_name = "tradequest" ladxr_name = "tradequest"
class TextShuffle(DefaultOffToggle): class TextShuffle(DefaultOffToggle):
""" """
[On] Shuffles all the text in the game [On] Shuffles all the text in the game
[Off] (default) doesn't shuffle them. [Off] (default) doesn't shuffle them.
""" """
class Rooster(DefaultOnToggle, LADXROption): class Rooster(DefaultOnToggle, LADXROption):
""" """
[On] Adds the rooster to the item pool. [On] Adds the rooster to the item pool.
@ -57,6 +62,7 @@ class Rooster(DefaultOnToggle, LADXROption):
display_name = "Rooster" display_name = "Rooster"
ladxr_name = "rooster" ladxr_name = "rooster"
class Boomerang(Choice): class Boomerang(Choice):
""" """
[Normal] requires Magnifying Lens to get the boomerang. [Normal] requires Magnifying Lens to get the boomerang.
@ -67,6 +73,7 @@ class Boomerang(Choice):
gift = 1 gift = 1
default = gift default = gift
class EntranceShuffle(Choice, LADXROption): class EntranceShuffle(Choice, LADXROption):
""" """
[WARNING] Experimental, may fail to fill [WARNING] Experimental, may fail to fill
@ -75,19 +82,20 @@ class EntranceShuffle(Choice, LADXROption):
If random start location and/or dungeon shuffle is enabled, then these will be shuffled with all the non-connector entrance pool. If random start location and/or dungeon shuffle is enabled, then these will be shuffled with all the non-connector entrance pool.
Note, some entrances can lead into water, use the warp-to-home from the save&quit menu to escape this.""" Note, some entrances can lead into water, use the warp-to-home from the save&quit menu to escape this."""
#[Advanced] Simple, but two-way connector caves are shuffled in their own pool as well. # [Advanced] Simple, but two-way connector caves are shuffled in their own pool as well.
#[Expert] Advanced, but caves/houses without items are also shuffled into the Simple entrance pool. # [Expert] Advanced, but caves/houses without items are also shuffled into the Simple entrance pool.
#[Insanity] Expert, but the Raft Minigame hut and Mamu's cave are added to the non-connector pool. # [Insanity] Expert, but the Raft Minigame hut and Mamu's cave are added to the non-connector pool.
option_none = 0 option_none = 0
option_simple = 1 option_simple = 1
#option_advanced = 2 # option_advanced = 2
#option_expert = 3 # option_expert = 3
#option_insanity = 4 # option_insanity = 4
default = option_none default = option_none
display_name = "Experimental Entrance Shuffle" display_name = "Experimental Entrance Shuffle"
ladxr_name = "entranceshuffle" ladxr_name = "entranceshuffle"
class DungeonShuffle(DefaultOffToggle, LADXROption): class DungeonShuffle(DefaultOffToggle, LADXROption):
""" """
[WARNING] Experimental, may fail to fill [WARNING] Experimental, may fail to fill
@ -96,12 +104,14 @@ class DungeonShuffle(DefaultOffToggle, LADXROption):
display_name = "Experimental Dungeon Shuffle" display_name = "Experimental Dungeon Shuffle"
ladxr_name = "dungeonshuffle" ladxr_name = "dungeonshuffle"
class APTitleScreen(DefaultOnToggle): class APTitleScreen(DefaultOnToggle):
""" """
Enables AP specific title screen and disables the intro cutscene Enables AP specific title screen and disables the intro cutscene
""" """
display_name = "AP Title Screen" display_name = "AP Title Screen"
class BossShuffle(Choice): class BossShuffle(Choice):
none = 0 none = 0
shuffle = 1 shuffle = 1
@ -115,10 +125,12 @@ class DungeonItemShuffle(Choice):
option_own_world = 2 option_own_world = 2
option_any_world = 3 option_any_world = 3
option_different_world = 4 option_different_world = 4
#option_delete = 5 # option_delete = 5
#option_start_with = 6 # option_start_with = 6
alias_true = 3 alias_true = 3
alias_false = 0 alias_false = 0
ladxr_item: str
class ShuffleNightmareKeys(DungeonItemShuffle): class ShuffleNightmareKeys(DungeonItemShuffle):
""" """
@ -132,6 +144,7 @@ class ShuffleNightmareKeys(DungeonItemShuffle):
display_name = "Shuffle Nightmare Keys" display_name = "Shuffle Nightmare Keys"
ladxr_item = "NIGHTMARE_KEY" ladxr_item = "NIGHTMARE_KEY"
class ShuffleSmallKeys(DungeonItemShuffle): class ShuffleSmallKeys(DungeonItemShuffle):
""" """
Shuffle Small Keys Shuffle Small Keys
@ -143,6 +156,8 @@ class ShuffleSmallKeys(DungeonItemShuffle):
""" """
display_name = "Shuffle Small Keys" display_name = "Shuffle Small Keys"
ladxr_item = "KEY" ladxr_item = "KEY"
class ShuffleMaps(DungeonItemShuffle): class ShuffleMaps(DungeonItemShuffle):
""" """
Shuffle Dungeon Maps Shuffle Dungeon Maps
@ -155,6 +170,7 @@ class ShuffleMaps(DungeonItemShuffle):
display_name = "Shuffle Maps" display_name = "Shuffle Maps"
ladxr_item = "MAP" ladxr_item = "MAP"
class ShuffleCompasses(DungeonItemShuffle): class ShuffleCompasses(DungeonItemShuffle):
""" """
Shuffle Dungeon Compasses Shuffle Dungeon Compasses
@ -167,6 +183,7 @@ class ShuffleCompasses(DungeonItemShuffle):
display_name = "Shuffle Compasses" display_name = "Shuffle Compasses"
ladxr_item = "COMPASS" ladxr_item = "COMPASS"
class ShuffleStoneBeaks(DungeonItemShuffle): class ShuffleStoneBeaks(DungeonItemShuffle):
""" """
Shuffle Owl Beaks Shuffle Owl Beaks
@ -179,6 +196,7 @@ class ShuffleStoneBeaks(DungeonItemShuffle):
display_name = "Shuffle Stone Beaks" display_name = "Shuffle Stone Beaks"
ladxr_item = "STONE_BEAK" ladxr_item = "STONE_BEAK"
class ShuffleInstruments(DungeonItemShuffle): class ShuffleInstruments(DungeonItemShuffle):
""" """
Shuffle Instruments Shuffle Instruments
@ -195,6 +213,7 @@ class ShuffleInstruments(DungeonItemShuffle):
option_vanilla = 100 option_vanilla = 100
alias_false = 100 alias_false = 100
class Goal(Choice, LADXROption): class Goal(Choice, LADXROption):
""" """
The Goal of the game The Goal of the game
@ -207,7 +226,7 @@ class Goal(Choice, LADXROption):
option_instruments = 1 option_instruments = 1
option_seashells = 2 option_seashells = 2
option_open = 3 option_open = 3
default = option_instruments default = option_instruments
def to_ladxr_option(self, all_options): def to_ladxr_option(self, all_options):
@ -216,6 +235,7 @@ class Goal(Choice, LADXROption):
else: else:
return LADXROption.to_ladxr_option(self, all_options) return LADXROption.to_ladxr_option(self, all_options)
class InstrumentCount(Range, LADXROption): class InstrumentCount(Range, LADXROption):
""" """
Sets the number of instruments required to open the Egg Sets the number of instruments required to open the Egg
@ -226,6 +246,7 @@ class InstrumentCount(Range, LADXROption):
range_end = 8 range_end = 8
default = 8 default = 8
class NagMessages(DefaultOffToggle, LADXROption): class NagMessages(DefaultOffToggle, LADXROption):
""" """
Controls if nag messages are shown when rocks and crystals are touched. Useful for glitches, annoying for everyone else. Controls if nag messages are shown when rocks and crystals are touched. Useful for glitches, annoying for everyone else.
@ -233,6 +254,7 @@ class NagMessages(DefaultOffToggle, LADXROption):
display_name = "Nag Messages" display_name = "Nag Messages"
ladxr_name = "nagmessages" ladxr_name = "nagmessages"
class MusicChangeCondition(Choice): class MusicChangeCondition(Choice):
""" """
Controls how the music changes. Controls how the music changes.
@ -243,6 +265,8 @@ class MusicChangeCondition(Choice):
option_sword = 0 option_sword = 0
option_always = 1 option_always = 1
default = option_always default = option_always
# Setting('hpmode', 'Gameplay', 'm', 'Health mode', options=[('default', '', 'Normal'), ('inverted', 'i', 'Inverted'), ('1', '1', 'Start with 1 heart'), ('low', 'l', 'Low max')], default='default', # Setting('hpmode', 'Gameplay', 'm', 'Health mode', options=[('default', '', 'Normal'), ('inverted', 'i', 'Inverted'), ('1', '1', 'Start with 1 heart'), ('low', 'l', 'Low max')], default='default',
# description=""" # description="""
# [Normal} health works as you would expect. # [Normal} health works as you would expect.
@ -271,6 +295,7 @@ class Bowwow(Choice):
swordless = 1 swordless = 1
default = normal default = normal
class Overworld(Choice, LADXROption): class Overworld(Choice, LADXROption):
""" """
[Dungeon Dive] Create a different overworld where all the dungeons are directly accessible and almost no chests are located in the overworld. [Dungeon Dive] Create a different overworld where all the dungeons are directly accessible and almost no chests are located in the overworld.
@ -284,9 +309,10 @@ class Overworld(Choice, LADXROption):
# option_shuffled = 3 # option_shuffled = 3
default = option_normal default = option_normal
#Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False,
# Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False,
# description='All items will be more powerful, faster, harder, bigger stronger. You name it.'), # description='All items will be more powerful, faster, harder, bigger stronger. You name it.'),
#Setting('quickswap', 'User options', 'Q', 'Quickswap', options=[('none', '', 'Disabled'), ('a', 'a', 'Swap A button'), ('b', 'b', 'Swap B button')], default='none', # 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.', # 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), # aesthetic=True),
# Setting('textmode', 'User options', 'f', 'Text mode', options=[('fast', '', 'Fast'), ('default', 'd', 'Normal'), ('none', 'n', 'No-text')], default='fast', # Setting('textmode', 'User options', 'f', 'Text mode', options=[('fast', '', 'Fast'), ('default', 'd', 'Normal'), ('none', 'n', 'No-text')], default='fast',
@ -329,7 +355,7 @@ class BootsControls(Choice):
option_bracelet = 1 option_bracelet = 1
option_press_a = 2 option_press_a = 2
option_press_b = 3 option_press_b = 3
class LinkPalette(Choice, LADXROption): class LinkPalette(Choice, LADXROption):
""" """
@ -352,6 +378,7 @@ class LinkPalette(Choice, LADXROption):
def to_ladxr_option(self, all_options): def to_ladxr_option(self, all_options):
return self.ladxr_name, str(self.value) return self.ladxr_name, str(self.value)
class TrendyGame(Choice): class TrendyGame(Choice):
""" """
[Easy] All of the items hold still for you [Easy] All of the items hold still for you
@ -370,6 +397,7 @@ class TrendyGame(Choice):
option_impossible = 5 option_impossible = 5
default = option_normal default = option_normal
class GfxMod(FreeText, LADXROption): class GfxMod(FreeText, LADXROption):
""" """
Sets the sprite for link, among other things Sets the sprite for link, among other things
@ -380,7 +408,7 @@ class GfxMod(FreeText, LADXROption):
normal = '' normal = ''
default = 'Link' default = 'Link'
__spriteDir: str = Utils.local_path(os.path.join('data', 'sprites','ladx')) __spriteDir: str = Utils.local_path(os.path.join('data', 'sprites', 'ladx'))
__spriteFiles: typing.DefaultDict[str, typing.List[str]] = defaultdict(list) __spriteFiles: typing.DefaultDict[str, typing.List[str]] = defaultdict(list)
extensions = [".bin", ".bdiff", ".png", ".bmp"] extensions = [".bin", ".bdiff", ".png", ".bmp"]
@ -389,16 +417,15 @@ class GfxMod(FreeText, LADXROption):
name, extension = os.path.splitext(file) name, extension = os.path.splitext(file)
if extension in extensions: if extension in extensions:
__spriteFiles[name].append(file) __spriteFiles[name].append(file)
def __init__(self, value: str): def __init__(self, value: str):
super().__init__(value) super().__init__(value)
def verify(self, world, player_name: str, plando_options) -> None: def verify(self, world, player_name: str, plando_options) -> None:
if self.value == "Link" or self.value in GfxMod.__spriteFiles: if self.value == "Link" or self.value in GfxMod.__spriteFiles:
return return
raise Exception(f"LADX Sprite '{self.value}' not found. Possible sprites are: {['Link'] + list(GfxMod.__spriteFiles.keys())}") raise Exception(
f"LADX Sprite '{self.value}' not found. Possible sprites are: {['Link'] + list(GfxMod.__spriteFiles.keys())}")
def to_ladxr_option(self, all_options): def to_ladxr_option(self, all_options):
if self.value == -1 or self.value == "Link": if self.value == -1 or self.value == "Link":
@ -407,10 +434,12 @@ class GfxMod(FreeText, LADXROption):
assert self.value in GfxMod.__spriteFiles assert self.value in GfxMod.__spriteFiles
if len(GfxMod.__spriteFiles[self.value]) > 1: if len(GfxMod.__spriteFiles[self.value]) > 1:
logger.warning(f"{self.value} does not uniquely identify a file. Possible matches: {GfxMod.__spriteFiles[self.value]}. Using {GfxMod.__spriteFiles[self.value][0]}") logger.warning(
f"{self.value} does not uniquely identify a file. Possible matches: {GfxMod.__spriteFiles[self.value]}. Using {GfxMod.__spriteFiles[self.value][0]}")
return self.ladxr_name, self.__spriteDir + "/" + GfxMod.__spriteFiles[self.value][0] return self.ladxr_name, self.__spriteDir + "/" + GfxMod.__spriteFiles[self.value][0]
class Palette(Choice): class Palette(Choice):
""" """
Sets the palette for the game. Sets the palette for the game.
@ -430,6 +459,7 @@ class Palette(Choice):
option_pink = 4 option_pink = 4
option_inverted = 5 option_inverted = 5
class Music(Choice, LADXROption): class Music(Choice, LADXROption):
""" """
[Vanilla] Regular Music [Vanilla] Regular Music
@ -441,7 +471,6 @@ class Music(Choice, LADXROption):
option_shuffled = 1 option_shuffled = 1
option_off = 2 option_off = 2
def to_ladxr_option(self, all_options): def to_ladxr_option(self, all_options):
s = "" s = ""
if self.value == self.option_shuffled: if self.value == self.option_shuffled:
@ -450,55 +479,57 @@ class Music(Choice, LADXROption):
s = "off" s = "off"
return self.ladxr_name, s return self.ladxr_name, s
class WarpImprovements(DefaultOffToggle): 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. [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 [Off] No change
""" """
class AdditionalWarpPoints(DefaultOffToggle): class AdditionalWarpPoints(DefaultOffToggle):
""" """
[On] (requires warp improvements) Adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower [On] (requires warp improvements) Adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower
[Off] No change [Off] No change
""" """
links_awakening_options: typing.Dict[str, typing.Type[Option]] = {
'logic': Logic, @dataclass
class LinksAwakeningOptions(PerGameCommonOptions):
logic: Logic
# 'heartpiece': DefaultOnToggle, # description='Includes heart pieces in the item pool'), # 'heartpiece': DefaultOnToggle, # description='Includes heart pieces in the item pool'),
# 'seashells': DefaultOnToggle, # description='Randomizes the secret sea shells hiding in the ground/trees. (chest are always randomized)'), # 'seashells': DefaultOnToggle, # description='Randomizes the secret sea shells hiding in the ground/trees. (chest are always randomized)'),
# 'heartcontainers': DefaultOnToggle, # description='Includes boss heart container drops in the item pool'), # 'heartcontainers': DefaultOnToggle, # description='Includes boss heart container drops in the item pool'),
# 'instruments': DefaultOffToggle, # description='Instruments are placed on random locations, dungeon goal will just contain a random item.'), # 'instruments': DefaultOffToggle, # description='Instruments are placed on random locations, dungeon goal will just contain a random item.'),
'tradequest': TradeQuest, # description='Trade quest items are randomized, each NPC takes its normal trade quest item, but gives a random item'), tradequest: TradeQuest # description='Trade quest items are randomized, each NPC takes its normal trade quest item, but gives a random item'),
# 'witch': DefaultOnToggle, # description='Adds both the toadstool and the reward for giving the toadstool to the witch to the item pool'), # 'witch': DefaultOnToggle, # description='Adds both the toadstool and the reward for giving the toadstool to the witch to the item pool'),
'rooster': Rooster, # description='Adds the rooster to the item pool. Without this option, the rooster spot is still a check giving an item. But you will never find the rooster. Any rooster spot is accessible without rooster by other means.'), rooster: Rooster # description='Adds the rooster to the item pool. Without this option, the rooster spot is still a check giving an item. But you will never find the rooster. Any rooster spot is accessible without rooster by other means.'),
# 'boomerang': Boomerang, # 'boomerang': Boomerang,
# 'randomstartlocation': DefaultOffToggle, # 'Randomize where your starting house is located'), # 'randomstartlocation': DefaultOffToggle, # 'Randomize where your starting house is located'),
'experimental_dungeon_shuffle': DungeonShuffle, # 'Randomizes the dungeon that each dungeon entrance leads to'), experimental_dungeon_shuffle: DungeonShuffle # 'Randomizes the dungeon that each dungeon entrance leads to'),
'experimental_entrance_shuffle': EntranceShuffle, experimental_entrance_shuffle: EntranceShuffle
# 'bossshuffle': BossShuffle, # 'bossshuffle': BossShuffle,
# 'minibossshuffle': BossShuffle, # 'minibossshuffle': BossShuffle,
'goal': Goal, goal: Goal
'instrument_count': InstrumentCount, instrument_count: InstrumentCount
# 'itempool': ItemPool, # 'itempool': ItemPool,
# 'bowwow': Bowwow, # 'bowwow': Bowwow,
# 'overworld': Overworld, # 'overworld': Overworld,
'link_palette': LinkPalette, link_palette: LinkPalette
'warp_improvements': WarpImprovements, warp_improvements: WarpImprovements
'additional_warp_points': AdditionalWarpPoints, additional_warp_points: AdditionalWarpPoints
'trendy_game': TrendyGame, trendy_game: TrendyGame
'gfxmod': GfxMod, gfxmod: GfxMod
'palette': Palette, palette: Palette
'text_shuffle': TextShuffle, text_shuffle: TextShuffle
'shuffle_nightmare_keys': ShuffleNightmareKeys, shuffle_nightmare_keys: ShuffleNightmareKeys
'shuffle_small_keys': ShuffleSmallKeys, shuffle_small_keys: ShuffleSmallKeys
'shuffle_maps': ShuffleMaps, shuffle_maps: ShuffleMaps
'shuffle_compasses': ShuffleCompasses, shuffle_compasses: ShuffleCompasses
'shuffle_stone_beaks': ShuffleStoneBeaks, shuffle_stone_beaks: ShuffleStoneBeaks
'music': Music, music: Music
'shuffle_instruments': ShuffleInstruments, shuffle_instruments: ShuffleInstruments
'music_change_condition': MusicChangeCondition, music_change_condition: MusicChangeCondition
'nag_messages': NagMessages, nag_messages: NagMessages
'ap_title_screen': APTitleScreen, ap_title_screen: APTitleScreen
'boots_controls': BootsControls, boots_controls: BootsControls
}

View File

@ -1,4 +1,4 @@
import settings
import worlds.Files import worlds.Files
import hashlib import hashlib
import Utils import Utils
@ -32,7 +32,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
def get_base_rom_path(file_name: str = "") -> str: def get_base_rom_path(file_name: str = "") -> str:
options = Utils.get_options() options = settings.get_settings()
if not file_name: if not file_name:
file_name = options["ladx_options"]["rom_file"] file_name = options["ladx_options"]["rom_file"]
if not os.path.exists(file_name): if not os.path.exists(file_name):

View File

@ -1,4 +1,5 @@
import binascii import binascii
import dataclasses
import os import os
import pkgutil import pkgutil
import tempfile import tempfile
@ -17,13 +18,13 @@ from .LADXR import generator
from .LADXR.itempool import ItemPool as LADXRItemPool from .LADXR.itempool import ItemPool as LADXRItemPool
from .LADXR.locations.constants import CHEST_ITEMS from .LADXR.locations.constants import CHEST_ITEMS
from .LADXR.locations.instrument import Instrument from .LADXR.locations.instrument import Instrument
from .LADXR.logic import Logic as LAXDRLogic from .LADXR.logic import Logic as LADXRLogic
from .LADXR.main import get_parser from .LADXR.main import get_parser
from .LADXR.settings import Settings as LADXRSettings from .LADXR.settings import Settings as LADXRSettings
from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup
from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion, from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion,
create_regions_from_ladxr, get_locations_to_id) create_regions_from_ladxr, get_locations_to_id)
from .Options import DungeonItemShuffle, links_awakening_options, ShuffleInstruments from .Options import DungeonItemShuffle, ShuffleInstruments, LinksAwakeningOptions
from .Rom import LADXDeltaPatch, get_base_rom_path from .Rom import LADXDeltaPatch, get_base_rom_path
DEVELOPER_MODE = False DEVELOPER_MODE = False
@ -73,8 +74,9 @@ class LinksAwakeningWorld(World):
""" """
game = LINKS_AWAKENING # name of the game/world game = LINKS_AWAKENING # name of the game/world
web = LinksAwakeningWebWorld() web = LinksAwakeningWebWorld()
option_definitions = links_awakening_options # options the player can set options_dataclass = LinksAwakeningOptions
options: LinksAwakeningOptions
settings: typing.ClassVar[LinksAwakeningSettings] settings: typing.ClassVar[LinksAwakeningSettings]
topology_present = True # show path to required location checks in spoiler topology_present = True # show path to required location checks in spoiler
@ -102,7 +104,11 @@ class LinksAwakeningWorld(World):
prefill_dungeon_items = None prefill_dungeon_items = None
player_options = None ladxr_settings: LADXRSettings
ladxr_logic: LADXRLogic
ladxr_itempool: LADXRItemPool
multi_key: bytearray
rupees = { rupees = {
ItemName.RUPEES_20: 20, ItemName.RUPEES_20: 20,
@ -113,17 +119,13 @@ class LinksAwakeningWorld(World):
} }
def convert_ap_options_to_ladxr_logic(self): def convert_ap_options_to_ladxr_logic(self):
self.player_options = { self.ladxr_settings = LADXRSettings(dataclasses.asdict(self.options))
option: getattr(self.multiworld, option)[self.player] for option in self.option_definitions
}
self.laxdr_options = LADXRSettings(self.player_options) self.ladxr_settings.validate()
self.laxdr_options.validate()
world_setup = LADXRWorldSetup() world_setup = LADXRWorldSetup()
world_setup.randomize(self.laxdr_options, self.multiworld.random) world_setup.randomize(self.ladxr_settings, self.random)
self.ladxr_logic = LAXDRLogic(configuration_options=self.laxdr_options, world_setup=world_setup) self.ladxr_logic = LADXRLogic(configuration_options=self.ladxr_settings, world_setup=world_setup)
self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.laxdr_options, self.multiworld.random).toDict() self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.ladxr_settings, self.random).toDict()
def create_regions(self) -> None: def create_regions(self) -> None:
# Initialize # Initialize
@ -180,8 +182,8 @@ class LinksAwakeningWorld(World):
# For any and different world, set item rule instead # For any and different world, set item rule instead
for dungeon_item_type in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]: for dungeon_item_type in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]:
option = "shuffle_" + dungeon_item_type option_name = "shuffle_" + dungeon_item_type
option = self.player_options[option] option: DungeonItemShuffle = getattr(self.options, option_name)
dungeon_item_types[option.ladxr_item] = option.value dungeon_item_types[option.ladxr_item] = option.value
@ -189,11 +191,11 @@ class LinksAwakeningWorld(World):
num_items = 8 if dungeon_item_type == "instruments" else 9 num_items = 8 if dungeon_item_type == "instruments" else 9
if option.value == DungeonItemShuffle.option_own_world: if option.value == DungeonItemShuffle.option_own_world:
self.multiworld.local_items[self.player].value |= { self.options.local_items.value |= {
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1) ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
} }
elif option.value == DungeonItemShuffle.option_different_world: elif option.value == DungeonItemShuffle.option_different_world:
self.multiworld.non_local_items[self.player].value |= { self.options.non_local_items.value |= {
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1) ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
} }
# option_original_dungeon = 0 # option_original_dungeon = 0
@ -215,7 +217,7 @@ class LinksAwakeningWorld(World):
else: else:
item = self.create_item(item_name) item = self.create_item(item_name)
if not self.multiworld.tradequest[self.player] and isinstance(item.item_data, TradeItemData): if not self.options.tradequest and isinstance(item.item_data, TradeItemData):
location = self.multiworld.get_location(item.item_data.vanilla_location, self.player) location = self.multiworld.get_location(item.item_data.vanilla_location, self.player)
location.place_locked_item(item) location.place_locked_item(item)
location.show_in_spoiler = False location.show_in_spoiler = False
@ -287,7 +289,7 @@ class LinksAwakeningWorld(World):
if item.player == self.player if item.player == self.player
and item.item_data.ladxr_id in start_loc.ladxr_item.OPTIONS and not item.location] and item.item_data.ladxr_id in start_loc.ladxr_item.OPTIONS and not item.location]
if possible_start_items: if possible_start_items:
index = self.multiworld.random.choice(possible_start_items) index = self.random.choice(possible_start_items)
start_item = self.multiworld.itempool.pop(index) start_item = self.multiworld.itempool.pop(index)
start_loc.place_locked_item(start_item) start_loc.place_locked_item(start_item)
@ -336,7 +338,7 @@ class LinksAwakeningWorld(World):
# Get the list of locations and shuffle # Get the list of locations and shuffle
all_dungeon_locs_to_fill = sorted(all_dungeon_locs) all_dungeon_locs_to_fill = sorted(all_dungeon_locs)
self.multiworld.random.shuffle(all_dungeon_locs_to_fill) self.random.shuffle(all_dungeon_locs_to_fill)
# Get the list of items and sort by priority # Get the list of items and sort by priority
def priority(item): def priority(item):
@ -465,34 +467,19 @@ class LinksAwakeningWorld(World):
loc.ladxr_item.location_owner = self.player loc.ladxr_item.location_owner = self.player
rom_name = Rom.get_base_rom_path() rom_name = Rom.get_base_rom_path()
out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}.gbc" out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.player_name}.gbc"
out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc") out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc")
parser = get_parser() parser = get_parser()
args = parser.parse_args([rom_name, "-o", out_name, "--dump"]) args = parser.parse_args([rom_name, "-o", out_name, "--dump"])
name_for_rom = self.multiworld.player_name[self.player] rom = generator.generateRom(args, self)
all_names = [self.multiworld.player_name[i + 1] for i in range(len(self.multiworld.player_name))]
rom = generator.generateRom(
args,
self.laxdr_options,
self.player_options,
self.multi_key,
self.multiworld.seed_name,
self.ladxr_logic,
rnd=self.multiworld.per_slot_randoms[self.player],
player_name=name_for_rom,
player_names=all_names,
player_id = self.player,
multiworld=self.multiworld)
with open(out_path, "wb") as handle: with open(out_path, "wb") as handle:
rom.save(handle, name="LADXR") rom.save(handle, name="LADXR")
# Write title screen after everything else is done - full gfxmods may stomp over the egg tiles # Write title screen after everything else is done - full gfxmods may stomp over the egg tiles
if self.player_options["ap_title_screen"]: if self.options.ap_title_screen:
with tempfile.NamedTemporaryFile(delete=False) as title_patch: with tempfile.NamedTemporaryFile(delete=False) as title_patch:
title_patch.write(pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4")) title_patch.write(pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4"))
@ -500,16 +487,16 @@ class LinksAwakeningWorld(World):
os.unlink(title_patch.name) os.unlink(title_patch.name)
patch = LADXDeltaPatch(os.path.splitext(out_path)[0]+LADXDeltaPatch.patch_file_ending, player=self.player, patch = LADXDeltaPatch(os.path.splitext(out_path)[0]+LADXDeltaPatch.patch_file_ending, player=self.player,
player_name=self.multiworld.player_name[self.player], patched_path=out_path) player_name=self.player_name, patched_path=out_path)
patch.write() patch.write()
if not DEVELOPER_MODE: if not DEVELOPER_MODE:
os.unlink(out_path) os.unlink(out_path)
def generate_multi_key(self): def generate_multi_key(self):
return bytearray(self.multiworld.random.getrandbits(8) for _ in range(10)) + self.player.to_bytes(2, 'big') return bytearray(self.random.getrandbits(8) for _ in range(10)) + self.player.to_bytes(2, 'big')
def modify_multidata(self, multidata: dict): def modify_multidata(self, multidata: dict):
multidata["connect_names"][binascii.hexlify(self.multi_key).decode()] = multidata["connect_names"][self.multiworld.player_name[self.player]] multidata["connect_names"][binascii.hexlify(self.multi_key).decode()] = multidata["connect_names"][self.player_name]
def collect(self, state, item: Item) -> bool: def collect(self, state, item: Item) -> bool:
change = super().collect(state, item) change = super().collect(state, item)