mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 04:01:32 -06:00
LADX: generate without rom (#4278)
This commit is contained in:
@@ -2,13 +2,15 @@ import binascii
|
|||||||
import importlib.util
|
import importlib.util
|
||||||
import importlib.machinery
|
import importlib.machinery
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
import random
|
||||||
|
import pickle
|
||||||
|
import Utils
|
||||||
|
import settings
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import TYPE_CHECKING
|
from typing import Dict
|
||||||
|
|
||||||
from .romTables import ROMWithTables
|
from .romTables import ROMWithTables
|
||||||
from . import assembler
|
from . import assembler
|
||||||
from . import mapgen
|
|
||||||
from . import patches
|
from . import patches
|
||||||
from .patches import overworld as _
|
from .patches import overworld as _
|
||||||
from .patches import dungeon as _
|
from .patches import dungeon as _
|
||||||
@@ -57,27 +59,20 @@ from .patches import tradeSequence as _
|
|||||||
from . import hints
|
from . import hints
|
||||||
|
|
||||||
from .patches import bank34
|
from .patches import bank34
|
||||||
from .utils import formatText
|
|
||||||
from .roomEditor import RoomEditor, Object
|
from .roomEditor import RoomEditor, Object
|
||||||
from .patches.aesthetics import rgb_to_bin, bin_to_rgb
|
from .patches.aesthetics import rgb_to_bin, bin_to_rgb
|
||||||
|
|
||||||
from .locations.keyLocation import KeyLocation
|
from .. import Options
|
||||||
|
|
||||||
from BaseClasses import ItemClassification
|
|
||||||
from ..Locations import LinksAwakeningLocation
|
|
||||||
from ..Options import TrendyGame, Palette, MusicChangeCondition, Warps
|
|
||||||
|
|
||||||
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, world: "LinksAwakeningWorld"):
|
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"]
|
||||||
rom_patches = []
|
rom_patches = []
|
||||||
player_names = list(world.multiworld.player_name.values())
|
rom = ROMWithTables(base_rom, rom_patches)
|
||||||
|
rom.player_names = patch_data["other_player_names"]
|
||||||
rom = ROMWithTables(args.input_filename, rom_patches)
|
|
||||||
rom.player_names = player_names
|
|
||||||
pymods = []
|
pymods = []
|
||||||
if args.pymod:
|
if args.pymod:
|
||||||
for pymod in args.pymod:
|
for pymod in args.pymod:
|
||||||
@@ -88,10 +83,13 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
for pymod in pymods:
|
for pymod in pymods:
|
||||||
pymod.prePatch(rom)
|
pymod.prePatch(rom)
|
||||||
|
|
||||||
if world.ladxr_settings.gfxmod:
|
if options["gfxmod"]:
|
||||||
patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", world.ladxr_settings.gfxmod))
|
user_settings = settings.get_settings()
|
||||||
|
try:
|
||||||
item_list = [item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)]
|
gfx_mod_file = user_settings["ladx_options"]["gfx_mod_file"]
|
||||||
|
patches.aesthetics.gfxMod(rom, gfx_mod_file)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass # if user just doesnt provide gfxmod file, let patching continue
|
||||||
|
|
||||||
assembler.resetConsts()
|
assembler.resetConsts()
|
||||||
assembler.const("INV_SIZE", 16)
|
assembler.const("INV_SIZE", 16)
|
||||||
@@ -121,7 +119,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
assembler.const("wLinkSpawnDelay", 0xDE13)
|
assembler.const("wLinkSpawnDelay", 0xDE13)
|
||||||
|
|
||||||
#assembler.const("HARDWARE_LINK", 1)
|
#assembler.const("HARDWARE_LINK", 1)
|
||||||
assembler.const("HARD_MODE", 1 if world.ladxr_settings.hardmode != "none" else 0)
|
assembler.const("HARD_MODE", 1 if options["hard_mode"] else 0)
|
||||||
|
|
||||||
patches.core.cleanup(rom)
|
patches.core.cleanup(rom)
|
||||||
patches.save.singleSaveSlot(rom)
|
patches.save.singleSaveSlot(rom)
|
||||||
@@ -135,7 +133,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
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, world.multi_key, world.player, player_names)
|
patches.bank3e.addBank3E(rom, multi_key, patch_data["player"], patch_data["other_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)
|
||||||
@@ -144,19 +142,17 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
patches.core.alwaysAllowSecretBook(rom)
|
patches.core.alwaysAllowSecretBook(rom)
|
||||||
patches.core.injectMainLoop(rom)
|
patches.core.injectMainLoop(rom)
|
||||||
|
|
||||||
from ..Options import ShuffleSmallKeys, ShuffleNightmareKeys
|
if options["shuffle_small_keys"] != Options.ShuffleSmallKeys.option_original_dungeon or\
|
||||||
|
options["shuffle_nightmare_keys"] != Options.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 world.ladxr_settings.witch:
|
# if ladxr_settings["witch"]:
|
||||||
patches.witch.updateWitch(rom)
|
patches.witch.updateWitch(rom)
|
||||||
patches.softlock.fixAll(rom)
|
patches.softlock.fixAll(rom)
|
||||||
if not world.ladxr_settings.rooster:
|
if not options["rooster"]:
|
||||||
patches.maptweaks.tweakMap(rom)
|
patches.maptweaks.tweakMap(rom)
|
||||||
patches.maptweaks.tweakBirdKeyRoom(rom)
|
patches.maptweaks.tweakBirdKeyRoom(rom)
|
||||||
if world.ladxr_settings.overworld == "openmabe":
|
if options["overworld"] == Options.Overworld.option_open_mabe:
|
||||||
patches.maptweaks.openMabe(rom)
|
patches.maptweaks.openMabe(rom)
|
||||||
patches.chest.fixChests(rom)
|
patches.chest.fixChests(rom)
|
||||||
patches.shop.fixShop(rom)
|
patches.shop.fixShop(rom)
|
||||||
@@ -168,10 +164,10 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
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 world.ladxr_settings.owlstatues in ("dungeon", "both"):
|
# if ladxr_settings["owlstatues"] in ("dungeon", "both"):
|
||||||
patches.owl.upgradeDungeonOwlStatues(rom)
|
# patches.owl.upgradeDungeonOwlStatues(rom)
|
||||||
if world.ladxr_settings.owlstatues in ("overworld", "both"):
|
# if 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)
|
||||||
patches.seashell.fixSeashell(rom)
|
patches.seashell.fixSeashell(rom)
|
||||||
@@ -180,143 +176,95 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
patches.songs.upgradeMarin(rom)
|
patches.songs.upgradeMarin(rom)
|
||||||
patches.songs.upgradeManbo(rom)
|
patches.songs.upgradeManbo(rom)
|
||||||
patches.songs.upgradeMamu(rom)
|
patches.songs.upgradeMamu(rom)
|
||||||
patches.tradeSequence.patchTradeSequence(rom, world.ladxr_settings)
|
|
||||||
patches.bowwow.fixBowwow(rom, everywhere=world.ladxr_settings.bowwow != 'normal')
|
patches.tradeSequence.patchTradeSequence(rom, options)
|
||||||
if world.ladxr_settings.bowwow != 'normal':
|
patches.bowwow.fixBowwow(rom, everywhere=False)
|
||||||
patches.bowwow.bowwowMapPatches(rom)
|
# if ladxr_settings["bowwow"] != 'normal':
|
||||||
|
# patches.bowwow.bowwowMapPatches(rom)
|
||||||
patches.desert.desertAccess(rom)
|
patches.desert.desertAccess(rom)
|
||||||
if world.ladxr_settings.overworld == 'dungeondive':
|
# if ladxr_settings["overworld"] == 'dungeondive':
|
||||||
patches.overworld.patchOverworldTilesets(rom)
|
# patches.overworld.patchOverworldTilesets(rom)
|
||||||
patches.overworld.createDungeonOnlyOverworld(rom)
|
# patches.overworld.createDungeonOnlyOverworld(rom)
|
||||||
elif world.ladxr_settings.overworld == 'nodungeons':
|
# elif ladxr_settings["overworld"] == 'nodungeons':
|
||||||
patches.dungeon.patchNoDungeons(rom)
|
# patches.dungeon.patchNoDungeons(rom)
|
||||||
elif world.ladxr_settings.overworld == 'random':
|
#elif world.ladxr_settings["overworld"] == 'random':
|
||||||
patches.overworld.patchOverworldTilesets(rom)
|
# patches.overworld.patchOverworldTilesets(rom)
|
||||||
mapgen.store_map(rom, world.ladxr_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 world.options.music_change_condition == MusicChangeCondition.option_always:
|
if options["music_change_condition"] == Options.MusicChangeCondition.option_always:
|
||||||
patches.aesthetics.noSwordMusic(rom)
|
patches.aesthetics.noSwordMusic(rom)
|
||||||
patches.aesthetics.reduceMessageLengths(rom, world.random)
|
patches.aesthetics.reduceMessageLengths(rom, random)
|
||||||
patches.aesthetics.allowColorDungeonSpritesEverywhere(rom)
|
patches.aesthetics.allowColorDungeonSpritesEverywhere(rom)
|
||||||
if world.ladxr_settings.music == 'random':
|
if options["music"] == Options.Music.option_shuffled:
|
||||||
patches.music.randomizeMusic(rom, world.random)
|
patches.music.randomizeMusic(rom, random)
|
||||||
elif world.ladxr_settings.music == 'off':
|
elif options["music"] == Options.Music.option_off:
|
||||||
patches.music.noMusic(rom)
|
patches.music.noMusic(rom)
|
||||||
if world.ladxr_settings.noflash:
|
if options["no_flash"]:
|
||||||
patches.aesthetics.removeFlashingLights(rom)
|
patches.aesthetics.removeFlashingLights(rom)
|
||||||
if world.ladxr_settings.hardmode == "oracle":
|
if options["hard_mode"] == Options.HardMode.option_oracle:
|
||||||
patches.hardMode.oracleMode(rom)
|
patches.hardMode.oracleMode(rom)
|
||||||
elif world.ladxr_settings.hardmode == "hero":
|
elif options["hard_mode"] == Options.HardMode.option_hero:
|
||||||
patches.hardMode.heroMode(rom)
|
patches.hardMode.heroMode(rom)
|
||||||
elif world.ladxr_settings.hardmode == "ohko":
|
elif options["hard_mode"] == Options.HardMode.option_ohko:
|
||||||
patches.hardMode.oneHitKO(rom)
|
patches.hardMode.oneHitKO(rom)
|
||||||
if world.ladxr_settings.superweapons:
|
#if ladxr_settings["superweapons"]:
|
||||||
patches.weapons.patchSuperWeapons(rom)
|
# patches.weapons.patchSuperWeapons(rom)
|
||||||
if world.ladxr_settings.textmode == 'fast':
|
if options["text_mode"] == Options.TextMode.option_fast:
|
||||||
patches.aesthetics.fastText(rom)
|
patches.aesthetics.fastText(rom)
|
||||||
if world.ladxr_settings.textmode == 'none':
|
#if ladxr_settings["textmode"] == 'none':
|
||||||
patches.aesthetics.fastText(rom)
|
# patches.aesthetics.fastText(rom)
|
||||||
patches.aesthetics.noText(rom)
|
# patches.aesthetics.noText(rom)
|
||||||
if not world.ladxr_settings.nagmessages:
|
if not options["nag_messages"]:
|
||||||
patches.aesthetics.removeNagMessages(rom)
|
patches.aesthetics.removeNagMessages(rom)
|
||||||
if world.ladxr_settings.lowhpbeep == 'slow':
|
if options["low_hp_beep"] == Options.LowHpBeep.option_slow:
|
||||||
patches.aesthetics.slowLowHPBeep(rom)
|
patches.aesthetics.slowLowHPBeep(rom)
|
||||||
if world.ladxr_settings.lowhpbeep == 'none':
|
if options["low_hp_beep"] == Options.LowHpBeep.option_none:
|
||||||
patches.aesthetics.removeLowHPBeep(rom)
|
patches.aesthetics.removeLowHPBeep(rom)
|
||||||
if 0 <= int(world.ladxr_settings.linkspalette):
|
if 0 <= options["link_palette"]:
|
||||||
patches.aesthetics.forceLinksPalette(rom, int(world.ladxr_settings.linkspalette))
|
patches.aesthetics.forceLinksPalette(rom, options["link_palette"])
|
||||||
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 world.ladxr_settings.steal == 'never':
|
#if ladxr_settings["steal"] == 'never':
|
||||||
rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
|
# rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
|
||||||
elif world.ladxr_settings.steal == 'always':
|
#elif ladxr_settings["steal"] == 'always':
|
||||||
rom.patch(4, 0x36F9, "FA4EDB", "3E0100")
|
# rom.patch(4, 0x36F9, "FA4EDB", "3E0100")
|
||||||
|
|
||||||
if world.ladxr_settings.hpmode == 'inverted':
|
#if ladxr_settings["hpmode"] == 'inverted':
|
||||||
patches.health.setStartHealth(rom, 9)
|
# patches.health.setStartHealth(rom, 9)
|
||||||
elif world.ladxr_settings.hpmode == '1':
|
#elif ladxr_settings["hpmode"] == '1':
|
||||||
patches.health.setStartHealth(rom, 1)
|
# patches.health.setStartHealth(rom, 1)
|
||||||
|
|
||||||
patches.inventory.songSelectAfterOcarinaSelect(rom)
|
patches.inventory.songSelectAfterOcarinaSelect(rom)
|
||||||
if world.ladxr_settings.quickswap == 'a':
|
if options["quickswap"] == 'a':
|
||||||
patches.core.quickswap(rom, 1)
|
patches.core.quickswap(rom, 1)
|
||||||
elif world.ladxr_settings.quickswap == 'b':
|
elif options["quickswap"] == 'b':
|
||||||
patches.core.quickswap(rom, 0)
|
patches.core.quickswap(rom, 0)
|
||||||
|
|
||||||
patches.core.addBootsControls(rom, world.options.boots_controls)
|
patches.core.addBootsControls(rom, options["boots_controls"])
|
||||||
|
|
||||||
|
random.seed(patch_data["seed"] + patch_data["player"])
|
||||||
|
hints.addHints(rom, random, patch_data["hint_texts"])
|
||||||
|
|
||||||
world_setup = world.ladxr_logic.world_setup
|
if patch_data["world_setup"]["goal"] == "raft":
|
||||||
|
|
||||||
JUNK_HINT = 0.33
|
|
||||||
RANDOM_HINT= 0.66
|
|
||||||
# USEFUL_HINT = 1.0
|
|
||||||
# TODO: filter events, filter unshuffled keys
|
|
||||||
all_items = world.multiworld.get_items()
|
|
||||||
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]
|
|
||||||
|
|
||||||
def gen_hint():
|
|
||||||
if not world.options.in_game_hints:
|
|
||||||
return 'Hints are disabled!'
|
|
||||||
chance = world.random.uniform(0, 1)
|
|
||||||
if chance < JUNK_HINT:
|
|
||||||
return None
|
|
||||||
elif chance < RANDOM_HINT:
|
|
||||||
location = world.random.choice(our_items).location
|
|
||||||
else: # USEFUL_HINT
|
|
||||||
location = world.random.choice(our_useful_items).location
|
|
||||||
|
|
||||||
if location.item.player == world.player:
|
|
||||||
name = "Your"
|
|
||||||
else:
|
|
||||||
name = f"{world.multiworld.player_name[location.item.player]}'s"
|
|
||||||
# filter out { and } since they cause issues with string.format later on
|
|
||||||
name = name.replace("{", "").replace("}", "")
|
|
||||||
|
|
||||||
if isinstance(location, LinksAwakeningLocation):
|
|
||||||
location_name = location.ladxr_item.metadata.name
|
|
||||||
else:
|
|
||||||
location_name = location.name
|
|
||||||
|
|
||||||
hint = f"{name} {location.item.name} is at {location_name}"
|
|
||||||
if location.player != world.player:
|
|
||||||
# filter out { and } since they cause issues with string.format later on
|
|
||||||
player_name = world.multiworld.player_name[location.player].replace("{", "").replace("}", "")
|
|
||||||
hint += f" in {player_name}'s world"
|
|
||||||
|
|
||||||
# Cap hint size at 85
|
|
||||||
# Realistically we could go bigger but let's be safe instead
|
|
||||||
hint = hint[:85]
|
|
||||||
|
|
||||||
return hint
|
|
||||||
|
|
||||||
hints.addHints(rom, world.random, gen_hint)
|
|
||||||
|
|
||||||
if world_setup.goal == "raft":
|
|
||||||
patches.goal.setRaftGoal(rom)
|
patches.goal.setRaftGoal(rom)
|
||||||
elif world_setup.goal in ("bingo", "bingo-full"):
|
elif patch_data["world_setup"]["goal"] in ("bingo", "bingo-full"):
|
||||||
patches.bingo.setBingoGoal(rom, world_setup.bingo_goals, world_setup.goal)
|
patches.bingo.setBingoGoal(rom, patch_data["world_setup"]["bingo_goals"], patch_data["world_setup"]["goal"])
|
||||||
elif world_setup.goal == "seashells":
|
elif patch_data["world_setup"]["goal"] == "seashells":
|
||||||
patches.goal.setSeashellGoal(rom, 20)
|
patches.goal.setSeashellGoal(rom, 20)
|
||||||
else:
|
else:
|
||||||
patches.goal.setRequiredInstrumentCount(rom, world_setup.goal)
|
patches.goal.setRequiredInstrumentCount(rom, patch_data["world_setup"]["goal"])
|
||||||
|
|
||||||
# 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, patch_data["world_setup"]["multichest"])
|
||||||
if world.ladxr_settings.overworld not in {"dungeondive", "random"}:
|
#if ladxr_settings["overworld"] not in {"dungeondive", "random"}:
|
||||||
patches.entrances.changeEntrances(rom, world_setup.entrance_mapping)
|
patches.entrances.changeEntrances(rom, patch_data["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("*"):
|
||||||
spot.item = spot.item[1:]
|
spot.item = spot.item[1:]
|
||||||
@@ -327,23 +275,22 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
# There are only 101 player name slots (99 + "The Server" + "another world"), so don't use more than that
|
# There are only 101 player name slots (99 + "The Server" + "another world"), so don't use more than that
|
||||||
mw = 100
|
mw = 100
|
||||||
spot.patch(rom, spot.item, multiworld=mw)
|
spot.patch(rom, spot.item, multiworld=mw)
|
||||||
patches.enemies.changeBosses(rom, world_setup.boss_mapping)
|
patches.enemies.changeBosses(rom, patch_data["world_setup"]["boss_mapping"])
|
||||||
patches.enemies.changeMiniBosses(rom, world_setup.miniboss_mapping)
|
patches.enemies.changeMiniBosses(rom, patch_data["world_setup"]["miniboss_mapping"])
|
||||||
|
|
||||||
if not args.romdebugmode:
|
if not args.romdebugmode:
|
||||||
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, world.multi_key, world.multiworld.seed_name, world.ladxr_settings,
|
patches.titleScreen.setRomInfo(rom, patch_data)
|
||||||
world.player_name, world.player)
|
if options["ap_title_screen"]:
|
||||||
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 world.options.text_shuffle:
|
if options["text_shuffle"]:
|
||||||
excluded_ids = [
|
excluded_ids = [
|
||||||
# Overworld owl statues
|
# Overworld owl statues
|
||||||
0x1B6, 0x1B7, 0x1B8, 0x1B9, 0x1BA, 0x1BB, 0x1BC, 0x1BD, 0x1BE, 0x22D,
|
0x1B6, 0x1B7, 0x1B8, 0x1B9, 0x1BA, 0x1BB, 0x1BC, 0x1BD, 0x1BE, 0x22D,
|
||||||
@@ -388,6 +335,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
excluded_texts = [ rom.texts[excluded_id] for excluded_id in excluded_ids]
|
excluded_texts = [ rom.texts[excluded_id] for excluded_id in excluded_ids]
|
||||||
buckets = defaultdict(list)
|
buckets = defaultdict(list)
|
||||||
# For each ROM bank, shuffle text within the bank
|
# For each ROM bank, shuffle text within the bank
|
||||||
|
random.seed(patch_data["seed"] + patch_data["player"])
|
||||||
for n, data in enumerate(rom.texts._PointerTable__data):
|
for n, data in enumerate(rom.texts._PointerTable__data):
|
||||||
# Don't muck up which text boxes are questions and which are statements
|
# Don't muck up which text boxes are questions and which are statements
|
||||||
if type(data) != int and data and data != b'\xFF' and data not in excluded_texts:
|
if type(data) != int and data and data != b'\xFF' and data not in excluded_texts:
|
||||||
@@ -395,20 +343,20 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
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()
|
||||||
world.random.shuffle(shuffled)
|
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 world.options.trendy_game != TrendyGame.option_normal:
|
if options["trendy_game"] != Options.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 world.options.trendy_game == TrendyGame.option_easy:
|
if options["trendy_game"] == Options.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
|
||||||
@@ -419,7 +367,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
# 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 world.options.trendy_game >= TrendyGame.option_harder:
|
if options["trendy_game"] >= Options.TrendyGame.option_harder:
|
||||||
"""
|
"""
|
||||||
Data_004_76A0::
|
Data_004_76A0::
|
||||||
db $FC, $00, $04, $00, $00
|
db $FC, $00, $04, $00, $00
|
||||||
@@ -428,17 +376,18 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
db $00, $04, $00, $FC, $00
|
db $00, $04, $00, $FC, $00
|
||||||
"""
|
"""
|
||||||
speeds = {
|
speeds = {
|
||||||
TrendyGame.option_harder: (3, 8),
|
Options.TrendyGame.option_harder: (3, 8),
|
||||||
TrendyGame.option_hardest: (3, 8),
|
Options.TrendyGame.option_hardest: (3, 8),
|
||||||
TrendyGame.option_impossible: (3, 16),
|
Options.TrendyGame.option_impossible: (3, 16),
|
||||||
}
|
}
|
||||||
def speed():
|
def speed():
|
||||||
return world.random.randint(*speeds[world.options.trendy_game])
|
random.seed(patch_data["seed"] + patch_data["player"])
|
||||||
|
return random.randint(*speeds[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 world.options.trendy_game >= TrendyGame.option_hardest:
|
if options["trendy_game"] >= Options.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()
|
||||||
@@ -462,11 +411,11 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
for channel in range(3):
|
for channel in range(3):
|
||||||
color[channel] = color[channel] * 31 // 0xbc
|
color[channel] = color[channel] * 31 // 0xbc
|
||||||
|
|
||||||
if world.options.warps != Warps.option_vanilla:
|
if options["warps"] != Options.Warps.option_vanilla:
|
||||||
patches.core.addWarpImprovements(rom, world.options.warps == Warps.option_improved_additional)
|
patches.core.addWarpImprovements(rom, options["warps"] == Options.Warps.option_improved_additional)
|
||||||
|
|
||||||
palette = world.options.palette
|
palette = options["palette"]
|
||||||
if palette != Palette.option_normal:
|
if palette != Options.Palette.option_normal:
|
||||||
ranges = {
|
ranges = {
|
||||||
# Object palettes
|
# Object palettes
|
||||||
# Overworld palettes
|
# Overworld palettes
|
||||||
@@ -496,22 +445,22 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
r,g,b = bin_to_rgb(packed)
|
r,g,b = bin_to_rgb(packed)
|
||||||
|
|
||||||
# 1 bit
|
# 1 bit
|
||||||
if palette == Palette.option_1bit:
|
if palette == Options.Palette.option_1bit:
|
||||||
r &= 0b10000
|
r &= 0b10000
|
||||||
g &= 0b10000
|
g &= 0b10000
|
||||||
b &= 0b10000
|
b &= 0b10000
|
||||||
# 2 bit
|
# 2 bit
|
||||||
elif palette == Palette.option_1bit:
|
elif palette == Options.Palette.option_1bit:
|
||||||
r &= 0b11000
|
r &= 0b11000
|
||||||
g &= 0b11000
|
g &= 0b11000
|
||||||
b &= 0b11000
|
b &= 0b11000
|
||||||
# Invert
|
# Invert
|
||||||
elif palette == Palette.option_inverted:
|
elif palette == Options.Palette.option_inverted:
|
||||||
r = 31 - r
|
r = 31 - r
|
||||||
g = 31 - g
|
g = 31 - g
|
||||||
b = 31 - b
|
b = 31 - b
|
||||||
# Pink
|
# Pink
|
||||||
elif palette == Palette.option_pink:
|
elif palette == Options.Palette.option_pink:
|
||||||
r = r // 2
|
r = r // 2
|
||||||
r += 16
|
r += 16
|
||||||
r = int(r)
|
r = int(r)
|
||||||
@@ -520,7 +469,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
b += 16
|
b += 16
|
||||||
b = int(b)
|
b = int(b)
|
||||||
b = clamp(b, 0, 0x1F)
|
b = clamp(b, 0, 0x1F)
|
||||||
elif palette == Palette.option_greyscale:
|
elif palette == Options.Palette.option_greyscale:
|
||||||
# gray=int(0.299*r+0.587*g+0.114*b)
|
# gray=int(0.299*r+0.587*g+0.114*b)
|
||||||
gray = (r + g + b) // 3
|
gray = (r + g + b) // 3
|
||||||
r = g = b = gray
|
r = g = b = gray
|
||||||
@@ -531,10 +480,10 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
|
|
||||||
SEED_LOCATION = 0x0134
|
SEED_LOCATION = 0x0134
|
||||||
# Patch over the title
|
# Patch over the title
|
||||||
assert(len(world.multi_key) == 12)
|
assert(len(multi_key) == 12)
|
||||||
rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(world.multi_key))
|
rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(multi_key))
|
||||||
|
|
||||||
for pymod in pymods:
|
for pymod in pymods:
|
||||||
pymod.postPatch(rom)
|
pymod.postPatch(rom)
|
||||||
|
|
||||||
return rom
|
return rom.save()
|
||||||
|
@@ -1,5 +1,7 @@
|
|||||||
from .locations.items import *
|
from .locations.items import *
|
||||||
from .utils import formatText
|
from .utils import formatText
|
||||||
|
from BaseClasses import ItemClassification
|
||||||
|
from ..Locations import LinksAwakeningLocation
|
||||||
|
|
||||||
|
|
||||||
hint_text_ids = [
|
hint_text_ids = [
|
||||||
@@ -49,14 +51,64 @@ useless_hint = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def addHints(rom, rnd, hint_generator):
|
def addHints(rom, rnd, hint_texts):
|
||||||
|
hint_texts_copy = hint_texts.copy()
|
||||||
text_ids = hint_text_ids.copy()
|
text_ids = hint_text_ids.copy()
|
||||||
rnd.shuffle(text_ids)
|
rnd.shuffle(text_ids)
|
||||||
for text_id in text_ids:
|
for text_id in text_ids:
|
||||||
hint = hint_generator()
|
hint = hint_texts_copy.pop()
|
||||||
if not hint:
|
if not hint:
|
||||||
hint = rnd.choice(hints).format(*rnd.choice(useless_hint))
|
hint = rnd.choice(hints).format(*rnd.choice(useless_hint))
|
||||||
rom.texts[text_id] = formatText(hint)
|
rom.texts[text_id] = formatText(hint)
|
||||||
|
|
||||||
for text_id in range(0x200, 0x20C, 2):
|
for text_id in range(0x200, 0x20C, 2):
|
||||||
rom.texts[text_id] = formatText("Read this book?", ask="YES NO")
|
rom.texts[text_id] = formatText("Read this book?", ask="YES NO")
|
||||||
|
|
||||||
|
|
||||||
|
def generate_hint_texts(world):
|
||||||
|
JUNK_HINT = 0.33
|
||||||
|
RANDOM_HINT= 0.66
|
||||||
|
# USEFUL_HINT = 1.0
|
||||||
|
# TODO: filter events, filter unshuffled keys
|
||||||
|
all_items = world.multiworld.get_items()
|
||||||
|
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]
|
||||||
|
hint_texts = []
|
||||||
|
def gen_hint():
|
||||||
|
chance = world.random.uniform(0, 1)
|
||||||
|
if chance < JUNK_HINT:
|
||||||
|
return None
|
||||||
|
elif chance < RANDOM_HINT:
|
||||||
|
location = world.random.choice(our_items).location
|
||||||
|
else: # USEFUL_HINT
|
||||||
|
location = world.random.choice(our_useful_items).location
|
||||||
|
|
||||||
|
if location.item.player == world.player:
|
||||||
|
name = "Your"
|
||||||
|
else:
|
||||||
|
name = f"{world.multiworld.player_name[location.item.player]}'s"
|
||||||
|
# filter out { and } since they cause issues with string.format later on
|
||||||
|
name = name.replace("{", "").replace("}", "")
|
||||||
|
|
||||||
|
if isinstance(location, LinksAwakeningLocation):
|
||||||
|
location_name = location.ladxr_item.metadata.name
|
||||||
|
else:
|
||||||
|
location_name = location.name
|
||||||
|
|
||||||
|
hint = f"{name} {location.item} is at {location_name}"
|
||||||
|
if location.player != world.player:
|
||||||
|
# filter out { and } since they cause issues with string.format later on
|
||||||
|
player_name = world.multiworld.player_name[location.player].replace("{", "").replace("}", "")
|
||||||
|
hint += f" in {player_name}'s world"
|
||||||
|
|
||||||
|
# Cap hint size at 85
|
||||||
|
# Realistically we could go bigger but let's be safe instead
|
||||||
|
hint = hint[:85]
|
||||||
|
return hint
|
||||||
|
for _ in hint_text_ids:
|
||||||
|
hint_texts.append(gen_hint())
|
||||||
|
return hint_texts
|
||||||
|
@@ -541,7 +541,7 @@ OAMData:
|
|||||||
rom.banks[0x38][0x1400+n*0x20:0x1410+n*0x20] = utils.createTileData(gfx_high)
|
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)
|
rom.banks[0x38][0x1410+n*0x20:0x1420+n*0x20] = utils.createTileData(gfx_low)
|
||||||
|
|
||||||
def addBootsControls(rom, boots_controls: BootsControls):
|
def addBootsControls(rom, boots_controls: int):
|
||||||
if boots_controls == BootsControls.option_vanilla:
|
if boots_controls == BootsControls.option_vanilla:
|
||||||
return
|
return
|
||||||
consts = {
|
consts = {
|
||||||
@@ -578,7 +578,7 @@ def addBootsControls(rom, boots_controls: BootsControls):
|
|||||||
jr z, .yesBoots
|
jr z, .yesBoots
|
||||||
ld a, [hl]
|
ld a, [hl]
|
||||||
"""
|
"""
|
||||||
}[boots_controls.value]
|
}[boots_controls]
|
||||||
|
|
||||||
# The new code fits exactly within Nintendo's poorly space optimzied code while having more features
|
# The new code fits exactly within Nintendo's poorly space optimzied code while having more features
|
||||||
boots_code = assembler.ASM("""
|
boots_code = assembler.ASM("""
|
||||||
|
@@ -42,7 +42,7 @@ MINIBOSS_ENTITIES = {
|
|||||||
"ARMOS_KNIGHT": [(4, 3, 0x88)],
|
"ARMOS_KNIGHT": [(4, 3, 0x88)],
|
||||||
}
|
}
|
||||||
MINIBOSS_ROOMS = {
|
MINIBOSS_ROOMS = {
|
||||||
0: 0x111, 1: 0x128, 2: 0x145, 3: 0x164, 4: 0x193, 5: 0x1C5, 6: 0x228, 7: 0x23F,
|
"0": 0x111, "1": 0x128, "2": 0x145, "3": 0x164, "4": 0x193, "5": 0x1C5, "6": 0x228, "7": 0x23F,
|
||||||
"c1": 0x30C, "c2": 0x303,
|
"c1": 0x30C, "c2": 0x303,
|
||||||
"moblin_cave": 0x2E1,
|
"moblin_cave": 0x2E1,
|
||||||
"armos_temple": 0x27F,
|
"armos_temple": 0x27F,
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
from ..backgroundEditor import BackgroundEditor
|
from ..backgroundEditor import BackgroundEditor
|
||||||
from .aesthetics import rgb_to_bin, bin_to_rgb, prepatch
|
from .aesthetics import rgb_to_bin, bin_to_rgb, prepatch
|
||||||
import copy
|
import copy
|
||||||
import pkgutil
|
|
||||||
CHAR_MAP = {'z': 0x3E, '-': 0x3F, '.': 0x39, ':': 0x42, '?': 0x3C, '!': 0x3D}
|
CHAR_MAP = {'z': 0x3E, '-': 0x3F, '.': 0x39, ':': 0x42, '?': 0x3C, '!': 0x3D}
|
||||||
|
|
||||||
def _encode(s):
|
def _encode(s):
|
||||||
@@ -18,17 +17,18 @@ def _encode(s):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def setRomInfo(rom, seed, seed_name, settings, player_name, player_id):
|
def setRomInfo(rom, patch_data):
|
||||||
|
seed_name = patch_data["seed_name"]
|
||||||
try:
|
try:
|
||||||
seednr = int(seed, 16)
|
seednr = int(patch_data["seed"], 16)
|
||||||
except:
|
except:
|
||||||
import hashlib
|
import hashlib
|
||||||
seednr = int(hashlib.md5(seed).hexdigest(), 16)
|
seednr = int(hashlib.md5(str(patch_data["seed"]).encode()).hexdigest(), 16)
|
||||||
|
|
||||||
if settings.race:
|
if patch_data["is_race"]:
|
||||||
seed_name = "Race"
|
seed_name = "Race"
|
||||||
if isinstance(settings.race, str):
|
if isinstance(patch_data["is_race"], str):
|
||||||
seed_name += " " + settings.race
|
seed_name += " " + patch_data["is_race"]
|
||||||
rom.patch(0x00, 0x07, "00", "01")
|
rom.patch(0x00, 0x07, "00", "01")
|
||||||
else:
|
else:
|
||||||
rom.patch(0x00, 0x07, "00", "52")
|
rom.patch(0x00, 0x07, "00", "52")
|
||||||
@@ -37,7 +37,7 @@ def setRomInfo(rom, seed, seed_name, settings, player_name, player_id):
|
|||||||
#line_2_hex = _encode(seed[16:])
|
#line_2_hex = _encode(seed[16:])
|
||||||
BASE_DRAWING_AREA = 0x98a0
|
BASE_DRAWING_AREA = 0x98a0
|
||||||
LINE_WIDTH = 0x20
|
LINE_WIDTH = 0x20
|
||||||
player_id_text = f"Player {player_id}:"
|
player_id_text = f"Player {patch_data['player']}:"
|
||||||
for n in (3, 4):
|
for n in (3, 4):
|
||||||
be = BackgroundEditor(rom, n)
|
be = BackgroundEditor(rom, n)
|
||||||
ba = BackgroundEditor(rom, n, attributes=True)
|
ba = BackgroundEditor(rom, n, attributes=True)
|
||||||
@@ -45,9 +45,9 @@ def setRomInfo(rom, seed, seed_name, settings, player_name, player_id):
|
|||||||
for n, v in enumerate(_encode(player_id_text)):
|
for n, v in enumerate(_encode(player_id_text)):
|
||||||
be.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 5 + 2 + n] = v
|
be.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 5 + 2 + n] = v
|
||||||
ba.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 5 + 2 + n] = 0x00
|
ba.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 5 + 2 + n] = 0x00
|
||||||
for n, v in enumerate(_encode(player_name)):
|
for n, v in enumerate(_encode(patch_data['player_name'])):
|
||||||
be.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 6 + 0x13 - len(player_name) + n] = v
|
be.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 6 + 0x13 - len(patch_data['player_name']) + n] = v
|
||||||
ba.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 6 + 0x13 - len(player_name) + n] = 0x00
|
ba.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 6 + 0x13 - len(patch_data['player_name']) + n] = 0x00
|
||||||
for n, v in enumerate(line_1_hex):
|
for n, v in enumerate(line_1_hex):
|
||||||
be.tiles[0x9a20 + n] = v
|
be.tiles[0x9a20 + n] = v
|
||||||
ba.tiles[0x9a20 + n] = 0x00
|
ba.tiles[0x9a20 + n] = 0x00
|
||||||
|
@@ -387,7 +387,7 @@ def patchVarious(rom, settings):
|
|||||||
|
|
||||||
# Boomerang trade guy
|
# Boomerang trade guy
|
||||||
# if settings.boomerang not in {'trade', 'gift'} or settings.overworld in {'normal', 'nodungeons'}:
|
# if settings.boomerang not in {'trade', 'gift'} or settings.overworld in {'normal', 'nodungeons'}:
|
||||||
if settings.tradequest:
|
if settings["tradequest"]:
|
||||||
# Update magnifier checks
|
# 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(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
|
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
|
||||||
|
@@ -7,9 +7,7 @@ h2b = binascii.unhexlify
|
|||||||
|
|
||||||
|
|
||||||
class ROM:
|
class ROM:
|
||||||
def __init__(self, filename, patches=None):
|
def __init__(self, data, patches=None):
|
||||||
data = open(Utils.user_path(filename), "rb").read()
|
|
||||||
|
|
||||||
if patches:
|
if patches:
|
||||||
for patch in patches:
|
for patch in patches:
|
||||||
data = bsdiff4.patch(data, patch)
|
data = bsdiff4.patch(data, patch)
|
||||||
@@ -64,18 +62,10 @@ class ROM:
|
|||||||
self.banks[0][0x14E] = checksum >> 8
|
self.banks[0][0x14E] = checksum >> 8
|
||||||
self.banks[0][0x14F] = checksum & 0xFF
|
self.banks[0][0x14F] = checksum & 0xFF
|
||||||
|
|
||||||
def save(self, file, *, name=None):
|
def save(self):
|
||||||
# don't pass the name to fixHeader
|
# don't pass the name to fixHeader
|
||||||
self.fixHeader()
|
self.fixHeader()
|
||||||
if isinstance(file, str):
|
return b"".join(self.banks)
|
||||||
f = open(file, "wb")
|
|
||||||
for bank in self.banks:
|
|
||||||
f.write(bank)
|
|
||||||
f.close()
|
|
||||||
print("Saved:", file)
|
|
||||||
else:
|
|
||||||
for bank in self.banks:
|
|
||||||
file.write(bank)
|
|
||||||
|
|
||||||
def readHexSeed(self):
|
def readHexSeed(self):
|
||||||
return self.banks[0x3E][0x2F00:0x2F10].hex().upper()
|
return self.banks[0x3E][0x2F00:0x2F10].hex().upper()
|
||||||
|
@@ -181,8 +181,8 @@ class IndoorRoomSpriteData(PointerTable):
|
|||||||
|
|
||||||
|
|
||||||
class ROMWithTables(ROM):
|
class ROMWithTables(ROM):
|
||||||
def __init__(self, filename, patches=None):
|
def __init__(self, data, patches=None):
|
||||||
super().__init__(filename, patches)
|
super().__init__(data, patches)
|
||||||
|
|
||||||
# Ability to patch any text in the game with different text
|
# Ability to patch any text in the game with different text
|
||||||
self.texts = Texts(self)
|
self.texts = Texts(self)
|
||||||
@@ -203,7 +203,7 @@ class ROMWithTables(ROM):
|
|||||||
|
|
||||||
self.itemNames = {}
|
self.itemNames = {}
|
||||||
|
|
||||||
def save(self, filename, *, name=None):
|
def save(self):
|
||||||
# Assert special handling of bank 9 expansion is fine
|
# Assert special handling of bank 9 expansion is fine
|
||||||
for i in range(0x3d42, 0x4000):
|
for i in range(0x3d42, 0x4000):
|
||||||
assert self.banks[9][i] == 0, self.banks[9][i]
|
assert self.banks[9][i] == 0, self.banks[9][i]
|
||||||
@@ -221,4 +221,4 @@ class ROMWithTables(ROM):
|
|||||||
self.room_sprite_data_indoor.store(self)
|
self.room_sprite_data_indoor.store(self)
|
||||||
self.background_tiles.store(self)
|
self.background_tiles.store(self)
|
||||||
self.background_attributes.store(self)
|
self.background_attributes.store(self)
|
||||||
super().save(filename, name=name)
|
return super().save()
|
||||||
|
@@ -425,46 +425,11 @@ class TrendyGame(Choice):
|
|||||||
default = option_normal
|
default = option_normal
|
||||||
|
|
||||||
|
|
||||||
class GfxMod(FreeText, LADXROption):
|
class GfxMod(DefaultOffToggle):
|
||||||
"""
|
"""
|
||||||
Sets the sprite for link, among other things
|
If enabled, the patcher will prompt the user for a modification file to change sprites in the game and optionally some text.
|
||||||
The option should be the same name as a with sprite (and optional name) file in data/sprites/ladx
|
|
||||||
"""
|
"""
|
||||||
display_name = "GFX Modification"
|
display_name = "GFX Modification"
|
||||||
ladxr_name = "gfxmod"
|
|
||||||
normal = ''
|
|
||||||
default = 'Link'
|
|
||||||
|
|
||||||
__spriteDir: str = Utils.local_path(os.path.join('data', 'sprites', 'ladx'))
|
|
||||||
__spriteFiles: typing.DefaultDict[str, typing.List[str]] = defaultdict(list)
|
|
||||||
|
|
||||||
extensions = [".bin", ".bdiff", ".png", ".bmp"]
|
|
||||||
|
|
||||||
for file in os.listdir(__spriteDir):
|
|
||||||
name, extension = os.path.splitext(file)
|
|
||||||
if extension in extensions:
|
|
||||||
__spriteFiles[name].append(file)
|
|
||||||
|
|
||||||
def __init__(self, value: str):
|
|
||||||
super().__init__(value)
|
|
||||||
|
|
||||||
def verify(self, world, player_name: str, plando_options) -> None:
|
|
||||||
if self.value == "Link" or self.value in GfxMod.__spriteFiles:
|
|
||||||
return
|
|
||||||
raise Exception(
|
|
||||||
f"LADX Sprite '{self.value}' not found. Possible sprites are: {['Link'] + list(GfxMod.__spriteFiles.keys())}")
|
|
||||||
|
|
||||||
def to_ladxr_option(self, all_options):
|
|
||||||
if self.value == -1 or self.value == "Link":
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
assert self.value in GfxMod.__spriteFiles
|
|
||||||
|
|
||||||
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]}")
|
|
||||||
|
|
||||||
return self.ladxr_name, self.__spriteDir + "/" + GfxMod.__spriteFiles[self.value][0]
|
|
||||||
|
|
||||||
|
|
||||||
class Palette(Choice):
|
class Palette(Choice):
|
||||||
|
@@ -3,19 +3,112 @@ import worlds.Files
|
|||||||
import hashlib
|
import hashlib
|
||||||
import Utils
|
import Utils
|
||||||
import os
|
import os
|
||||||
|
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"
|
LADX_HASH = "07c211479386825042efb4ad31bb525f"
|
||||||
|
|
||||||
class LADXDeltaPatch(worlds.Files.APDeltaPatch):
|
if TYPE_CHECKING:
|
||||||
|
from . import LinksAwakeningWorld
|
||||||
|
|
||||||
|
|
||||||
|
class LADXPatchExtensions(worlds.Files.APPatchExtension):
|
||||||
|
game = LINKS_AWAKENING
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_rom(caller: worlds.Files.APProcedurePatch, rom: bytes, data_file: str) -> bytes:
|
||||||
|
patch_data = json.loads(caller.get_file(data_file).decode("utf-8"))
|
||||||
|
# TODO local option overrides
|
||||||
|
rom_name = get_base_rom_path()
|
||||||
|
out_name = f"{patch_data['out_base']}{caller.result_file_ending}"
|
||||||
|
parser = get_parser()
|
||||||
|
args = parser.parse_args([rom_name, "-o", out_name, "--dump"])
|
||||||
|
return generator.generateRom(rom, args, patch_data)
|
||||||
|
|
||||||
|
@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"]:
|
||||||
|
return bsdiff4.patch(rom, pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4"))
|
||||||
|
return rom
|
||||||
|
|
||||||
|
class LADXProcedurePatch(worlds.Files.APProcedurePatch):
|
||||||
hash = LADX_HASH
|
hash = LADX_HASH
|
||||||
game = "Links Awakening DX"
|
game = LINKS_AWAKENING
|
||||||
patch_file_ending = ".apladx"
|
patch_file_ending: str = ".apladx"
|
||||||
result_file_ending: str = ".gbc"
|
result_file_ending: str = ".gbc"
|
||||||
|
|
||||||
|
procedure = [
|
||||||
|
("generate_rom", ["data.json"]),
|
||||||
|
("patch_title_screen", ["data.json"])
|
||||||
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_source_data(cls) -> bytes:
|
def get_source_data(cls) -> bytes:
|
||||||
return get_base_rom_bytes()
|
return get_base_rom_bytes()
|
||||||
|
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
"out_base": world.multiworld.get_out_file_name_base(patch.player),
|
||||||
|
"is_race": world.multiworld.is_race,
|
||||||
|
"seed": world.multiworld.seed,
|
||||||
|
"seed_name": world.multiworld.seed_name,
|
||||||
|
"multi_key": binascii.hexlify(world.multi_key).decode(),
|
||||||
|
"player": patch.player,
|
||||||
|
"player_name": patch.player_name,
|
||||||
|
"other_player_names": list(world.multiworld.player_name.values()),
|
||||||
|
"item_list": binascii.hexlify(item_list).decode(),
|
||||||
|
"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",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
patch.write_file("data.json", json.dumps(data_dict).encode('utf-8'))
|
||||||
|
|
||||||
|
|
||||||
def get_base_rom_bytes(file_name: str = "") -> bytes:
|
def get_base_rom_bytes(file_name: str = "") -> bytes:
|
||||||
base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
|
base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None)
|
||||||
if not base_rom_bytes:
|
if not base_rom_bytes:
|
||||||
|
@@ -1,16 +1,12 @@
|
|||||||
import binascii
|
import binascii
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
|
||||||
import tempfile
|
|
||||||
import typing
|
import typing
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import bsdiff4
|
|
||||||
|
|
||||||
import settings
|
import settings
|
||||||
from BaseClasses import CollectionState, Entrance, Item, ItemClassification, Location, Tutorial, MultiWorld
|
from BaseClasses import CollectionState, Entrance, Item, ItemClassification, Location, Tutorial
|
||||||
from Fill import fill_restrictive
|
from Fill import fill_restrictive
|
||||||
from worlds.AutoWorld import WebWorld, World
|
from worlds.AutoWorld import WebWorld, World
|
||||||
from .Common import *
|
from .Common import *
|
||||||
@@ -18,19 +14,17 @@ from . import ItemIconGuessing
|
|||||||
from .Items import (DungeonItemData, DungeonItemType, ItemName, LinksAwakeningItem, TradeItemData,
|
from .Items import (DungeonItemData, DungeonItemType, ItemName, LinksAwakeningItem, TradeItemData,
|
||||||
ladxr_item_to_la_item_name, links_awakening_items, links_awakening_items_by_name,
|
ladxr_item_to_la_item_name, links_awakening_items, links_awakening_items_by_name,
|
||||||
links_awakening_item_name_groups)
|
links_awakening_item_name_groups)
|
||||||
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 LADXRLogic
|
from .LADXR.logic import Logic as LADXRLogic
|
||||||
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,
|
||||||
links_awakening_location_name_groups)
|
links_awakening_location_name_groups)
|
||||||
from .Options import DungeonItemShuffle, ShuffleInstruments, LinksAwakeningOptions, ladx_option_groups
|
from .Options import DungeonItemShuffle, ShuffleInstruments, LinksAwakeningOptions, ladx_option_groups
|
||||||
from .Rom import LADXDeltaPatch, get_base_rom_path
|
from .Rom import LADXProcedurePatch, write_patch_data
|
||||||
|
|
||||||
DEVELOPER_MODE = False
|
DEVELOPER_MODE = False
|
||||||
|
|
||||||
@@ -40,7 +34,7 @@ class LinksAwakeningSettings(settings.Group):
|
|||||||
"""File name of the Link's Awakening DX rom"""
|
"""File name of the Link's Awakening DX rom"""
|
||||||
copy_to = "Legend of Zelda, The - Link's Awakening DX (USA, Europe) (SGB Enhanced).gbc"
|
copy_to = "Legend of Zelda, The - Link's Awakening DX (USA, Europe) (SGB Enhanced).gbc"
|
||||||
description = "LADX ROM File"
|
description = "LADX ROM File"
|
||||||
md5s = [LADXDeltaPatch.hash]
|
md5s = [LADXProcedurePatch.hash]
|
||||||
|
|
||||||
class RomStart(str):
|
class RomStart(str):
|
||||||
"""
|
"""
|
||||||
@@ -57,8 +51,16 @@ class LinksAwakeningSettings(settings.Group):
|
|||||||
class DisplayMsgs(settings.Bool):
|
class DisplayMsgs(settings.Bool):
|
||||||
"""Display message inside of Bizhawk"""
|
"""Display message inside of Bizhawk"""
|
||||||
|
|
||||||
|
class GfxModFile(settings.FilePath):
|
||||||
|
"""
|
||||||
|
Gfxmod file, get it from upstream: https://github.com/daid/LADXR/tree/master/gfx
|
||||||
|
Only .bin or .bdiff files
|
||||||
|
The same directory will be checked for a matching text modification file
|
||||||
|
"""
|
||||||
|
|
||||||
rom_file: RomFile = RomFile(RomFile.copy_to)
|
rom_file: RomFile = RomFile(RomFile.copy_to)
|
||||||
rom_start: typing.Union[RomStart, bool] = True
|
rom_start: typing.Union[RomStart, bool] = True
|
||||||
|
gfx_mod_file: GfxModFile = GfxModFile()
|
||||||
|
|
||||||
class LinksAwakeningWebWorld(WebWorld):
|
class LinksAwakeningWebWorld(WebWorld):
|
||||||
tutorials = [Tutorial(
|
tutorials = [Tutorial(
|
||||||
@@ -179,10 +181,10 @@ class LinksAwakeningWorld(World):
|
|||||||
|
|
||||||
assert(start)
|
assert(start)
|
||||||
|
|
||||||
menu_region = LinksAwakeningRegion("Menu", None, "Menu", self.player, self.multiworld)
|
menu_region = LinksAwakeningRegion("Menu", None, "Menu", self.player, self.multiworld)
|
||||||
menu_region.exits = [Entrance(self.player, "Start Game", menu_region)]
|
menu_region.exits = [Entrance(self.player, "Start Game", menu_region)]
|
||||||
menu_region.exits[0].connect(start)
|
menu_region.exits[0].connect(start)
|
||||||
|
|
||||||
self.multiworld.regions.append(menu_region)
|
self.multiworld.regions.append(menu_region)
|
||||||
|
|
||||||
# Place RAFT, other access events
|
# Place RAFT, other access events
|
||||||
@@ -190,14 +192,14 @@ class LinksAwakeningWorld(World):
|
|||||||
for loc in region.locations:
|
for loc in region.locations:
|
||||||
if loc.address is None:
|
if loc.address is None:
|
||||||
loc.place_locked_item(self.create_event(loc.ladxr_item.event))
|
loc.place_locked_item(self.create_event(loc.ladxr_item.event))
|
||||||
|
|
||||||
# Connect Windfish -> Victory
|
# Connect Windfish -> Victory
|
||||||
windfish = self.multiworld.get_region("Windfish", self.player)
|
windfish = self.multiworld.get_region("Windfish", self.player)
|
||||||
l = Location(self.player, "Windfish", parent=windfish)
|
l = Location(self.player, "Windfish", parent=windfish)
|
||||||
windfish.locations = [l]
|
windfish.locations = [l]
|
||||||
|
|
||||||
l.place_locked_item(self.create_event("An Alarm Clock"))
|
l.place_locked_item(self.create_event("An Alarm Clock"))
|
||||||
|
|
||||||
self.multiworld.completion_condition[self.player] = lambda state: state.has("An Alarm Clock", player=self.player)
|
self.multiworld.completion_condition[self.player] = lambda state: state.has("An Alarm Clock", player=self.player)
|
||||||
|
|
||||||
def create_item(self, item_name: str):
|
def create_item(self, item_name: str):
|
||||||
@@ -279,8 +281,8 @@ class LinksAwakeningWorld(World):
|
|||||||
event_location = Location(self.player, "Can Play Trendy Game", parent=trendy_region)
|
event_location = Location(self.player, "Can Play Trendy Game", parent=trendy_region)
|
||||||
trendy_region.locations.insert(0, event_location)
|
trendy_region.locations.insert(0, event_location)
|
||||||
event_location.place_locked_item(self.create_event("Can Play Trendy Game"))
|
event_location.place_locked_item(self.create_event("Can Play Trendy Game"))
|
||||||
|
|
||||||
self.dungeon_locations_by_dungeon = [[], [], [], [], [], [], [], [], []]
|
self.dungeon_locations_by_dungeon = [[], [], [], [], [], [], [], [], []]
|
||||||
for r in self.multiworld.get_regions(self.player):
|
for r in self.multiworld.get_regions(self.player):
|
||||||
# Set aside dungeon locations
|
# Set aside dungeon locations
|
||||||
if r.dungeon_index:
|
if r.dungeon_index:
|
||||||
@@ -354,7 +356,7 @@ class LinksAwakeningWorld(World):
|
|||||||
|
|
||||||
# set containing the list of all possible dungeon locations for the player
|
# set containing the list of all possible dungeon locations for the player
|
||||||
all_dungeon_locs = set()
|
all_dungeon_locs = set()
|
||||||
|
|
||||||
# Do dungeon specific things
|
# Do dungeon specific things
|
||||||
for dungeon_index in range(0, 9):
|
for dungeon_index in range(0, 9):
|
||||||
# set up allow-list for dungeon specific items
|
# set up allow-list for dungeon specific items
|
||||||
@@ -367,7 +369,7 @@ class LinksAwakeningWorld(World):
|
|||||||
# ...also set the rules for the dungeon
|
# ...also set the rules for the dungeon
|
||||||
for location in locs:
|
for location in locs:
|
||||||
orig_rule = location.item_rule
|
orig_rule = location.item_rule
|
||||||
# If an item is about to be placed on a dungeon location, it can go there iff
|
# If an item is about to be placed on a dungeon location, it can go there iff
|
||||||
# 1. it fits the general rules for that location (probably 'return True' for most places)
|
# 1. it fits the general rules for that location (probably 'return True' for most places)
|
||||||
# 2. Either
|
# 2. Either
|
||||||
# 2a. it's not a restricted dungeon item
|
# 2a. it's not a restricted dungeon item
|
||||||
@@ -421,7 +423,7 @@ class LinksAwakeningWorld(World):
|
|||||||
partial_all_state.sweep_for_advancements()
|
partial_all_state.sweep_for_advancements()
|
||||||
|
|
||||||
fill_restrictive(self.multiworld, partial_all_state, all_dungeon_locs_to_fill, all_dungeon_items_to_fill, lock=True, single_player_placement=True, allow_partial=False)
|
fill_restrictive(self.multiworld, partial_all_state, all_dungeon_locs_to_fill, all_dungeon_items_to_fill, lock=True, single_player_placement=True, allow_partial=False)
|
||||||
|
|
||||||
|
|
||||||
name_cache = {}
|
name_cache = {}
|
||||||
# Tries to associate an icon from another game with an icon we have
|
# Tries to associate an icon from another game with an icon we have
|
||||||
@@ -458,22 +460,16 @@ class LinksAwakeningWorld(World):
|
|||||||
for name in possibles:
|
for name in possibles:
|
||||||
if name in self.name_cache:
|
if name in self.name_cache:
|
||||||
return self.name_cache[name]
|
return self.name_cache[name]
|
||||||
|
|
||||||
return "TRADING_ITEM_LETTER"
|
return "TRADING_ITEM_LETTER"
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def stage_assert_generate(cls, multiworld: MultiWorld):
|
|
||||||
rom_file = get_base_rom_path()
|
|
||||||
if not os.path.exists(rom_file):
|
|
||||||
raise FileNotFoundError(rom_file)
|
|
||||||
|
|
||||||
def generate_output(self, output_directory: str):
|
def generate_output(self, output_directory: str):
|
||||||
# copy items back to locations
|
# copy items back to locations
|
||||||
for r in self.multiworld.get_regions(self.player):
|
for r in self.multiworld.get_regions(self.player):
|
||||||
for loc in r.locations:
|
for loc in r.locations:
|
||||||
if isinstance(loc, LinksAwakeningLocation):
|
if isinstance(loc, LinksAwakeningLocation):
|
||||||
assert(loc.item)
|
assert(loc.item)
|
||||||
|
|
||||||
# If we're a links awakening item, just use the item
|
# If we're a links awakening item, just use the item
|
||||||
if isinstance(loc.item, LinksAwakeningItem):
|
if isinstance(loc.item, LinksAwakeningItem):
|
||||||
loc.ladxr_item.item = loc.item.item_data.ladxr_id
|
loc.ladxr_item.item = loc.item.item_data.ladxr_id
|
||||||
@@ -499,31 +495,13 @@ class LinksAwakeningWorld(World):
|
|||||||
# Kind of kludge, make it possible for the location to differentiate between local and remote items
|
# Kind of kludge, make it possible for the location to differentiate between local and remote items
|
||||||
loc.ladxr_item.location_owner = self.player
|
loc.ladxr_item.location_owner = self.player
|
||||||
|
|
||||||
rom_name = Rom.get_base_rom_path()
|
|
||||||
out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.player_name}.gbc"
|
patch = LADXProcedurePatch(player=self.player, player_name=self.player_name)
|
||||||
out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc")
|
write_patch_data(self, patch)
|
||||||
|
out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}"
|
||||||
|
f"{patch.patch_file_ending}")
|
||||||
|
|
||||||
parser = get_parser()
|
patch.write(out_path)
|
||||||
args = parser.parse_args([rom_name, "-o", out_name, "--dump"])
|
|
||||||
|
|
||||||
rom = generator.generateRom(args, self)
|
|
||||||
|
|
||||||
with open(out_path, "wb") as handle:
|
|
||||||
rom.save(handle, name="LADXR")
|
|
||||||
|
|
||||||
# Write title screen after everything else is done - full gfxmods may stomp over the egg tiles
|
|
||||||
if self.options.ap_title_screen:
|
|
||||||
with tempfile.NamedTemporaryFile(delete=False) as title_patch:
|
|
||||||
title_patch.write(pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4"))
|
|
||||||
|
|
||||||
bsdiff4.file_patch_inplace(out_path, title_patch.name)
|
|
||||||
os.unlink(title_patch.name)
|
|
||||||
|
|
||||||
patch = LADXDeltaPatch(os.path.splitext(out_path)[0]+LADXDeltaPatch.patch_file_ending, player=self.player,
|
|
||||||
player_name=self.player_name, patched_path=out_path)
|
|
||||||
patch.write()
|
|
||||||
if not DEVELOPER_MODE:
|
|
||||||
os.unlink(out_path)
|
|
||||||
|
|
||||||
def generate_multi_key(self):
|
def generate_multi_key(self):
|
||||||
return bytearray(self.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')
|
||||||
|
Reference in New Issue
Block a user