LADX: generate without rom (#4278)
This commit is contained in:
		| @@ -2,13 +2,15 @@ import binascii | ||||
| import importlib.util | ||||
| import importlib.machinery | ||||
| import os | ||||
| import pkgutil | ||||
| import random | ||||
| import pickle | ||||
| import Utils | ||||
| import settings | ||||
| from collections import defaultdict | ||||
| from typing import TYPE_CHECKING | ||||
| from typing import Dict | ||||
|  | ||||
| from .romTables import ROMWithTables | ||||
| from . import assembler | ||||
| from . import mapgen | ||||
| from . import patches | ||||
| from .patches import overworld as _ | ||||
| from .patches import dungeon as _ | ||||
| @@ -57,27 +59,20 @@ from .patches import tradeSequence as _ | ||||
| from . import hints | ||||
|  | ||||
| from .patches import bank34 | ||||
| from .utils import formatText | ||||
| from .roomEditor import RoomEditor, Object | ||||
| from .patches.aesthetics import rgb_to_bin, bin_to_rgb | ||||
|  | ||||
| from .locations.keyLocation import KeyLocation | ||||
|  | ||||
| from BaseClasses import ItemClassification | ||||
| from ..Locations import LinksAwakeningLocation | ||||
| from ..Options import TrendyGame, Palette, MusicChangeCondition, Warps | ||||
|  | ||||
| if TYPE_CHECKING: | ||||
|     from .. import LinksAwakeningWorld | ||||
|  | ||||
| from .. import Options | ||||
|  | ||||
| # 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 = [] | ||||
|     player_names = list(world.multiworld.player_name.values()) | ||||
|  | ||||
|     rom = ROMWithTables(args.input_filename, rom_patches) | ||||
|     rom.player_names = player_names | ||||
|     rom = ROMWithTables(base_rom, rom_patches) | ||||
|     rom.player_names = patch_data["other_player_names"] | ||||
|     pymods = [] | ||||
|     if args.pymod: | ||||
|         for pymod in args.pymod: | ||||
| @@ -88,10 +83,13 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|     for pymod in pymods: | ||||
|         pymod.prePatch(rom) | ||||
|  | ||||
|     if world.ladxr_settings.gfxmod: | ||||
|         patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", world.ladxr_settings.gfxmod)) | ||||
|  | ||||
|     item_list = [item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)] | ||||
|     if options["gfxmod"]: | ||||
|         user_settings = settings.get_settings() | ||||
|         try: | ||||
|             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.const("INV_SIZE", 16) | ||||
| @@ -121,7 +119,7 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|     assembler.const("wLinkSpawnDelay", 0xDE13) | ||||
|  | ||||
|     #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.save.singleSaveSlot(rom) | ||||
| @@ -135,7 +133,7 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|     patches.core.easyColorDungeonAccess(rom) | ||||
|     patches.owl.removeOwlEvents(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.bank34.addBank34(rom, item_list) | ||||
|     patches.core.removeGhost(rom) | ||||
| @@ -144,19 +142,17 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|     patches.core.alwaysAllowSecretBook(rom) | ||||
|     patches.core.injectMainLoop(rom) | ||||
|  | ||||
|     from ..Options import ShuffleSmallKeys, ShuffleNightmareKeys | ||||
|  | ||||
|     if world.options.shuffle_small_keys != ShuffleSmallKeys.option_original_dungeon or\ | ||||
|             world.options.shuffle_nightmare_keys != ShuffleNightmareKeys.option_original_dungeon: | ||||
|     if options["shuffle_small_keys"] != Options.ShuffleSmallKeys.option_original_dungeon or\ | ||||
|             options["shuffle_nightmare_keys"] != Options.ShuffleNightmareKeys.option_original_dungeon: | ||||
|         patches.inventory.advancedInventorySubscreen(rom) | ||||
|     patches.inventory.moreSlots(rom) | ||||
|     if world.ladxr_settings.witch: | ||||
|         patches.witch.updateWitch(rom) | ||||
|     # if ladxr_settings["witch"]: | ||||
|     patches.witch.updateWitch(rom) | ||||
|     patches.softlock.fixAll(rom) | ||||
|     if not world.ladxr_settings.rooster: | ||||
|     if not options["rooster"]: | ||||
|         patches.maptweaks.tweakMap(rom) | ||||
|         patches.maptweaks.tweakBirdKeyRoom(rom) | ||||
|     if world.ladxr_settings.overworld == "openmabe": | ||||
|     if options["overworld"] == Options.Overworld.option_open_mabe: | ||||
|         patches.maptweaks.openMabe(rom) | ||||
|     patches.chest.fixChests(rom) | ||||
|     patches.shop.fixShop(rom) | ||||
| @@ -168,10 +164,10 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|     patches.tarin.updateTarin(rom) | ||||
|     patches.fishingMinigame.updateFinishingMinigame(rom) | ||||
|     patches.health.upgradeHealthContainers(rom) | ||||
|     if world.ladxr_settings.owlstatues in ("dungeon", "both"): | ||||
|         patches.owl.upgradeDungeonOwlStatues(rom) | ||||
|     if world.ladxr_settings.owlstatues in ("overworld", "both"): | ||||
|         patches.owl.upgradeOverworldOwlStatues(rom) | ||||
|     # if ladxr_settings["owlstatues"] in ("dungeon", "both"): | ||||
|     #    patches.owl.upgradeDungeonOwlStatues(rom) | ||||
|     # if ladxr_settings["owlstatues"] in ("overworld", "both"): | ||||
|     #    patches.owl.upgradeOverworldOwlStatues(rom) | ||||
|     patches.goldenLeaf.fixGoldenLeaf(rom) | ||||
|     patches.heartPiece.fixHeartPiece(rom) | ||||
|     patches.seashell.fixSeashell(rom) | ||||
| @@ -180,143 +176,95 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|     patches.songs.upgradeMarin(rom) | ||||
|     patches.songs.upgradeManbo(rom) | ||||
|     patches.songs.upgradeMamu(rom) | ||||
|     patches.tradeSequence.patchTradeSequence(rom, world.ladxr_settings) | ||||
|     patches.bowwow.fixBowwow(rom, everywhere=world.ladxr_settings.bowwow != 'normal') | ||||
|     if world.ladxr_settings.bowwow != 'normal': | ||||
|         patches.bowwow.bowwowMapPatches(rom) | ||||
|  | ||||
|     patches.tradeSequence.patchTradeSequence(rom, options) | ||||
|     patches.bowwow.fixBowwow(rom, everywhere=False) | ||||
|     # if ladxr_settings["bowwow"] != 'normal': | ||||
|     #    patches.bowwow.bowwowMapPatches(rom) | ||||
|     patches.desert.desertAccess(rom) | ||||
|     if world.ladxr_settings.overworld == 'dungeondive': | ||||
|         patches.overworld.patchOverworldTilesets(rom) | ||||
|         patches.overworld.createDungeonOnlyOverworld(rom) | ||||
|     elif world.ladxr_settings.overworld == 'nodungeons': | ||||
|         patches.dungeon.patchNoDungeons(rom) | ||||
|     elif world.ladxr_settings.overworld == 'random': | ||||
|         patches.overworld.patchOverworldTilesets(rom) | ||||
|         mapgen.store_map(rom, world.ladxr_logic.world.map) | ||||
|     # if ladxr_settings["overworld"] == 'dungeondive': | ||||
|     #    patches.overworld.patchOverworldTilesets(rom) | ||||
|     #    patches.overworld.createDungeonOnlyOverworld(rom) | ||||
|     # elif ladxr_settings["overworld"] == 'nodungeons': | ||||
|     #    patches.dungeon.patchNoDungeons(rom) | ||||
|     #elif world.ladxr_settings["overworld"] == 'random': | ||||
|     #    patches.overworld.patchOverworldTilesets(rom) | ||||
|     #    mapgen.store_map(rom, world.ladxr_logic.world.map) | ||||
|     #if settings.dungeon_items == 'keysy': | ||||
|     #    patches.dungeon.removeKeyDoors(rom) | ||||
|     # patches.reduceRNG.slowdownThreeOfAKind(rom) | ||||
|     patches.reduceRNG.fixHorseHeads(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.reduceMessageLengths(rom, world.random) | ||||
|     patches.aesthetics.reduceMessageLengths(rom, random) | ||||
|     patches.aesthetics.allowColorDungeonSpritesEverywhere(rom) | ||||
|     if world.ladxr_settings.music == 'random': | ||||
|         patches.music.randomizeMusic(rom, world.random) | ||||
|     elif world.ladxr_settings.music == 'off': | ||||
|     if options["music"] == Options.Music.option_shuffled: | ||||
|         patches.music.randomizeMusic(rom, random) | ||||
|     elif options["music"] == Options.Music.option_off: | ||||
|         patches.music.noMusic(rom) | ||||
|     if world.ladxr_settings.noflash: | ||||
|     if options["no_flash"]: | ||||
|         patches.aesthetics.removeFlashingLights(rom) | ||||
|     if world.ladxr_settings.hardmode == "oracle": | ||||
|     if options["hard_mode"] == Options.HardMode.option_oracle: | ||||
|         patches.hardMode.oracleMode(rom) | ||||
|     elif world.ladxr_settings.hardmode == "hero": | ||||
|     elif options["hard_mode"] == Options.HardMode.option_hero: | ||||
|         patches.hardMode.heroMode(rom) | ||||
|     elif world.ladxr_settings.hardmode == "ohko": | ||||
|     elif options["hard_mode"] == Options.HardMode.option_ohko: | ||||
|         patches.hardMode.oneHitKO(rom) | ||||
|     if world.ladxr_settings.superweapons: | ||||
|         patches.weapons.patchSuperWeapons(rom) | ||||
|     if world.ladxr_settings.textmode == 'fast': | ||||
|     #if ladxr_settings["superweapons"]: | ||||
|     #    patches.weapons.patchSuperWeapons(rom) | ||||
|     if options["text_mode"] == Options.TextMode.option_fast: | ||||
|         patches.aesthetics.fastText(rom) | ||||
|     if world.ladxr_settings.textmode == 'none': | ||||
|         patches.aesthetics.fastText(rom) | ||||
|         patches.aesthetics.noText(rom) | ||||
|     if not world.ladxr_settings.nagmessages: | ||||
|     #if ladxr_settings["textmode"] == 'none': | ||||
|     #    patches.aesthetics.fastText(rom) | ||||
|     #    patches.aesthetics.noText(rom) | ||||
|     if not options["nag_messages"]: | ||||
|         patches.aesthetics.removeNagMessages(rom) | ||||
|     if world.ladxr_settings.lowhpbeep == 'slow': | ||||
|     if options["low_hp_beep"] == Options.LowHpBeep.option_slow: | ||||
|         patches.aesthetics.slowLowHPBeep(rom) | ||||
|     if world.ladxr_settings.lowhpbeep == 'none': | ||||
|     if options["low_hp_beep"] == Options.LowHpBeep.option_none: | ||||
|         patches.aesthetics.removeLowHPBeep(rom) | ||||
|     if 0 <= int(world.ladxr_settings.linkspalette): | ||||
|         patches.aesthetics.forceLinksPalette(rom, int(world.ladxr_settings.linkspalette)) | ||||
|     if 0 <= options["link_palette"]: | ||||
|         patches.aesthetics.forceLinksPalette(rom, options["link_palette"]) | ||||
|     if args.romdebugmode: | ||||
|         # The default rom has this build in, just need to set a flag and we get this save. | ||||
|         rom.patch(0, 0x0003, "00", "01") | ||||
|  | ||||
|     # Patch the sword check on the shopkeeper turning around. | ||||
|     if world.ladxr_settings.steal == 'never': | ||||
|         rom.patch(4, 0x36F9, "FA4EDB", "3E0000") | ||||
|     elif world.ladxr_settings.steal == 'always': | ||||
|         rom.patch(4, 0x36F9, "FA4EDB", "3E0100") | ||||
|     #if ladxr_settings["steal"] == 'never': | ||||
|     #    rom.patch(4, 0x36F9, "FA4EDB", "3E0000") | ||||
|     #elif ladxr_settings["steal"] == 'always': | ||||
|     #    rom.patch(4, 0x36F9, "FA4EDB", "3E0100") | ||||
|  | ||||
|     if world.ladxr_settings.hpmode == 'inverted': | ||||
|         patches.health.setStartHealth(rom, 9) | ||||
|     elif world.ladxr_settings.hpmode == '1': | ||||
|         patches.health.setStartHealth(rom, 1) | ||||
|     #if ladxr_settings["hpmode"] == 'inverted': | ||||
|     #    patches.health.setStartHealth(rom, 9) | ||||
|     #elif ladxr_settings["hpmode"] == '1': | ||||
|     #    patches.health.setStartHealth(rom, 1) | ||||
|  | ||||
|     patches.inventory.songSelectAfterOcarinaSelect(rom) | ||||
|     if world.ladxr_settings.quickswap == 'a': | ||||
|     if options["quickswap"] == 'a': | ||||
|         patches.core.quickswap(rom, 1) | ||||
|     elif world.ladxr_settings.quickswap == 'b': | ||||
|     elif options["quickswap"] == 'b': | ||||
|         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 | ||||
|  | ||||
|     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": | ||||
|     if patch_data["world_setup"]["goal"] == "raft": | ||||
|         patches.goal.setRaftGoal(rom) | ||||
|     elif world_setup.goal in ("bingo", "bingo-full"): | ||||
|         patches.bingo.setBingoGoal(rom, world_setup.bingo_goals, world_setup.goal) | ||||
|     elif world_setup.goal == "seashells": | ||||
|     elif patch_data["world_setup"]["goal"] in ("bingo", "bingo-full"): | ||||
|         patches.bingo.setBingoGoal(rom, patch_data["world_setup"]["bingo_goals"], patch_data["world_setup"]["goal"]) | ||||
|     elif patch_data["world_setup"]["goal"] == "seashells": | ||||
|         patches.goal.setSeashellGoal(rom, 20) | ||||
|     else: | ||||
|         patches.goal.setRequiredInstrumentCount(rom, world_setup.goal) | ||||
|         patches.goal.setRequiredInstrumentCount(rom, patch_data["world_setup"]["goal"]) | ||||
|  | ||||
|     # Patch the generated logic into the rom | ||||
|     patches.chest.setMultiChest(rom, world_setup.multichest) | ||||
|     if world.ladxr_settings.overworld not in {"dungeondive", "random"}: | ||||
|         patches.entrances.changeEntrances(rom, world_setup.entrance_mapping) | ||||
|     patches.chest.setMultiChest(rom, patch_data["world_setup"]["multichest"]) | ||||
|     #if ladxr_settings["overworld"] not in {"dungeondive", "random"}: | ||||
|     patches.entrances.changeEntrances(rom, patch_data["world_setup"]["entrance_mapping"]) | ||||
|     for spot in item_list: | ||||
|         if spot.item and spot.item.startswith("*"): | ||||
|             spot.item = spot.item[1:] | ||||
| @@ -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 | ||||
|                 mw = 100 | ||||
|         spot.patch(rom, spot.item, multiworld=mw) | ||||
|     patches.enemies.changeBosses(rom, world_setup.boss_mapping) | ||||
|     patches.enemies.changeMiniBosses(rom, world_setup.miniboss_mapping) | ||||
|     patches.enemies.changeBosses(rom, patch_data["world_setup"]["boss_mapping"]) | ||||
|     patches.enemies.changeMiniBosses(rom, patch_data["world_setup"]["miniboss_mapping"]) | ||||
|  | ||||
|     if not args.romdebugmode: | ||||
|         patches.core.addFrameCounter(rom, len(item_list)) | ||||
|  | ||||
|     patches.core.warpHome(rom)  # Needs to be done after setting the start location. | ||||
|     patches.titleScreen.setRomInfo(rom, world.multi_key, world.multiworld.seed_name, world.ladxr_settings, | ||||
|                                    world.player_name, world.player) | ||||
|     if world.options.ap_title_screen: | ||||
|     patches.titleScreen.setRomInfo(rom, patch_data) | ||||
|     if options["ap_title_screen"]: | ||||
|         patches.titleScreen.setTitleGraphics(rom) | ||||
|     patches.endscreen.updateEndScreen(rom) | ||||
|     patches.aesthetics.updateSpriteData(rom) | ||||
|     if args.doubletrouble: | ||||
|         patches.enemies.doubleTrouble(rom) | ||||
|  | ||||
|     if world.options.text_shuffle: | ||||
|     if options["text_shuffle"]: | ||||
|         excluded_ids = [ | ||||
|             # Overworld owl statues | ||||
|             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] | ||||
|         buckets = defaultdict(list) | ||||
|         # 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): | ||||
|             # 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: | ||||
| @@ -395,20 +343,20 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|         for bucket in buckets.values(): | ||||
|             # For each bucket, make a copy and shuffle | ||||
|             shuffled = bucket.copy() | ||||
|             world.random.shuffle(shuffled) | ||||
|             random.shuffle(shuffled) | ||||
|             # Then put new text in | ||||
|             for bucket_idx, (orig_idx, data) in enumerate(bucket): | ||||
|                 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 | ||||
|  | ||||
|  | ||||
|         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 | ||||
|             for i in range(0, 6): | ||||
|                 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) | ||||
|             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:: | ||||
|                     db   $FC, $00, $04, $00, $00 | ||||
| @@ -428,17 +376,18 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|                     db   $00, $04, $00, $FC, $00 | ||||
|                 """ | ||||
|                 speeds = { | ||||
|                     TrendyGame.option_harder: (3, 8), | ||||
|                     TrendyGame.option_hardest: (3, 8), | ||||
|                     TrendyGame.option_impossible: (3, 16), | ||||
|                     Options.TrendyGame.option_harder: (3, 8), | ||||
|                     Options.TrendyGame.option_hardest: (3, 8), | ||||
|                     Options.TrendyGame.option_impossible: (3, 16), | ||||
|                 } | ||||
|                 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][0x76A2-0x4000] = speed() | ||||
|                 rom.banks[0x4][0x76A6-0x4000] = 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][0x76A3-0x4000] = speed() | ||||
|                     rom.banks[0x4][0x76A5-0x4000] = speed() | ||||
| @@ -462,11 +411,11 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|             for channel in range(3): | ||||
|                 color[channel] = color[channel] * 31 // 0xbc | ||||
|  | ||||
|     if world.options.warps != Warps.option_vanilla: | ||||
|         patches.core.addWarpImprovements(rom, world.options.warps == Warps.option_improved_additional) | ||||
|     if options["warps"] != Options.Warps.option_vanilla: | ||||
|         patches.core.addWarpImprovements(rom, options["warps"] == Options.Warps.option_improved_additional) | ||||
|  | ||||
|     palette = world.options.palette | ||||
|     if palette != Palette.option_normal: | ||||
|     palette = options["palette"] | ||||
|     if palette != Options.Palette.option_normal: | ||||
|         ranges = { | ||||
|             # Object palettes | ||||
|             # Overworld palettes | ||||
| @@ -496,22 +445,22 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|                 r,g,b = bin_to_rgb(packed) | ||||
|  | ||||
|                 # 1 bit | ||||
|                 if palette == Palette.option_1bit: | ||||
|                 if palette == Options.Palette.option_1bit: | ||||
|                     r &= 0b10000 | ||||
|                     g &= 0b10000 | ||||
|                     b &= 0b10000 | ||||
|                 # 2 bit | ||||
|                 elif palette == Palette.option_1bit: | ||||
|                 elif palette == Options.Palette.option_1bit: | ||||
|                     r &= 0b11000 | ||||
|                     g &= 0b11000 | ||||
|                     b &= 0b11000 | ||||
|                 # Invert | ||||
|                 elif palette == Palette.option_inverted: | ||||
|                 elif palette == Options.Palette.option_inverted: | ||||
|                     r = 31 - r | ||||
|                     g = 31 - g | ||||
|                     b = 31 - b | ||||
|                 # Pink | ||||
|                 elif palette == Palette.option_pink: | ||||
|                 elif palette == Options.Palette.option_pink: | ||||
|                     r = r // 2 | ||||
|                     r += 16 | ||||
|                     r = int(r) | ||||
| @@ -520,7 +469,7 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|                     b += 16 | ||||
|                     b = int(b) | ||||
|                     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 = (r + g + b) // 3 | ||||
|                     r = g = b = gray | ||||
| @@ -531,10 +480,10 @@ def generateRom(args, world: "LinksAwakeningWorld"): | ||||
|  | ||||
|     SEED_LOCATION = 0x0134 | ||||
|     # Patch over the title | ||||
|     assert(len(world.multi_key) == 12) | ||||
|     rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(world.multi_key)) | ||||
|     assert(len(multi_key) == 12) | ||||
|     rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(multi_key)) | ||||
|  | ||||
|     for pymod in pymods: | ||||
|         pymod.postPatch(rom) | ||||
|  | ||||
|     return rom | ||||
|     return rom.save() | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| from .locations.items import * | ||||
| from .utils import formatText | ||||
| from BaseClasses import ItemClassification | ||||
| from ..Locations import LinksAwakeningLocation | ||||
|  | ||||
|  | ||||
| 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() | ||||
|     rnd.shuffle(text_ids) | ||||
|     for text_id in text_ids: | ||||
|         hint = hint_generator() | ||||
|         hint = hint_texts_copy.pop() | ||||
|         if not hint: | ||||
|             hint = rnd.choice(hints).format(*rnd.choice(useless_hint)) | ||||
|         rom.texts[text_id] = formatText(hint) | ||||
|  | ||||
|     for text_id in range(0x200, 0x20C, 2): | ||||
|         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][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: | ||||
|         return | ||||
|     consts = { | ||||
| @@ -578,7 +578,7 @@ def addBootsControls(rom, boots_controls: BootsControls): | ||||
|         jr  z, .yesBoots | ||||
|         ld   a, [hl] | ||||
|         """ | ||||
|     }[boots_controls.value] | ||||
|     }[boots_controls] | ||||
|      | ||||
|     # The new code fits exactly within Nintendo's poorly space optimzied code while having more features | ||||
|     boots_code = assembler.ASM(""" | ||||
|   | ||||
| @@ -42,7 +42,7 @@ MINIBOSS_ENTITIES = { | ||||
|     "ARMOS_KNIGHT":     [(4, 3, 0x88)], | ||||
| } | ||||
| 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, | ||||
|     "moblin_cave": 0x2E1, | ||||
|     "armos_temple": 0x27F, | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| from ..backgroundEditor import BackgroundEditor | ||||
| from .aesthetics import rgb_to_bin, bin_to_rgb, prepatch | ||||
| import copy | ||||
| import pkgutil | ||||
| CHAR_MAP = {'z': 0x3E, '-': 0x3F, '.': 0x39, ':': 0x42, '?': 0x3C, '!': 0x3D} | ||||
|  | ||||
| def _encode(s): | ||||
| @@ -18,17 +17,18 @@ def _encode(s): | ||||
|     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: | ||||
|         seednr = int(seed, 16) | ||||
|         seednr = int(patch_data["seed"], 16) | ||||
|     except: | ||||
|         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" | ||||
|         if isinstance(settings.race, str): | ||||
|             seed_name += " " + settings.race | ||||
|         if isinstance(patch_data["is_race"], str): | ||||
|             seed_name += " " + patch_data["is_race"] | ||||
|         rom.patch(0x00, 0x07, "00", "01") | ||||
|     else: | ||||
|         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:]) | ||||
|     BASE_DRAWING_AREA = 0x98a0 | ||||
|     LINE_WIDTH = 0x20 | ||||
|     player_id_text = f"Player {player_id}:" | ||||
|     player_id_text = f"Player {patch_data['player']}:" | ||||
|     for n in (3, 4): | ||||
|         be = BackgroundEditor(rom, n) | ||||
|         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)): | ||||
|             be.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 5 + 2 + n] = v | ||||
|             ba.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 5 + 2 + n] = 0x00 | ||||
|         for n, v in enumerate(_encode(player_name)): | ||||
|             be.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 6 + 0x13 - len(player_name) + n] = v | ||||
|             ba.tiles[BASE_DRAWING_AREA + LINE_WIDTH * 6 + 0x13 - len(player_name) + n] = 0x00 | ||||
|         for n, v in enumerate(_encode(patch_data['player_name'])): | ||||
|             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(patch_data['player_name']) + n] = 0x00 | ||||
|         for n, v in enumerate(line_1_hex): | ||||
|             be.tiles[0x9a20 + n] = v | ||||
|             ba.tiles[0x9a20 + n] = 0x00 | ||||
|   | ||||
| @@ -387,7 +387,7 @@ def patchVarious(rom, settings): | ||||
|  | ||||
|     # Boomerang trade guy | ||||
|     # if settings.boomerang not in {'trade', 'gift'} or settings.overworld in {'normal', 'nodungeons'}: | ||||
|     if settings.tradequest: | ||||
|     if settings["tradequest"]: | ||||
|         # Update magnifier checks | ||||
|         rom.patch(0x19, 0x05EC, ASM("ld a, [wTradeSequenceItem]\ncp $0E\njp nz, $7E61"), ASM("ld a, [wTradeSequenceItem2]\nand $20\njp z, $7E61"))  # show the guy | ||||
|         rom.patch(0x00, 0x3199, ASM("ld a, [wTradeSequenceItem]\ncp $0E\njr nz, $06"), ASM("ld a, [wTradeSequenceItem2]\nand $20\njr z, $06"))  # load the proper room layout | ||||
|   | ||||
| @@ -7,9 +7,7 @@ h2b = binascii.unhexlify | ||||
|  | ||||
|  | ||||
| class ROM: | ||||
|     def __init__(self, filename, patches=None): | ||||
|         data = open(Utils.user_path(filename), "rb").read() | ||||
|  | ||||
|     def __init__(self, data, patches=None): | ||||
|         if patches: | ||||
|             for patch in patches: | ||||
|                 data = bsdiff4.patch(data, patch) | ||||
| @@ -64,18 +62,10 @@ class ROM: | ||||
|         self.banks[0][0x14E] = checksum >> 8 | ||||
|         self.banks[0][0x14F] = checksum & 0xFF | ||||
|  | ||||
|     def save(self, file, *, name=None): | ||||
|     def save(self): | ||||
|         # don't pass the name to fixHeader | ||||
|         self.fixHeader() | ||||
|         if isinstance(file, str): | ||||
|             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) | ||||
|         return b"".join(self.banks) | ||||
|  | ||||
|     def readHexSeed(self): | ||||
|         return self.banks[0x3E][0x2F00:0x2F10].hex().upper() | ||||
|   | ||||
| @@ -181,8 +181,8 @@ class IndoorRoomSpriteData(PointerTable): | ||||
|  | ||||
|  | ||||
| class ROMWithTables(ROM): | ||||
|     def __init__(self, filename, patches=None): | ||||
|         super().__init__(filename, patches) | ||||
|     def __init__(self, data, patches=None): | ||||
|         super().__init__(data, patches) | ||||
|  | ||||
|         # Ability to patch any text in the game with different text | ||||
|         self.texts = Texts(self) | ||||
| @@ -203,7 +203,7 @@ class ROMWithTables(ROM): | ||||
|  | ||||
|         self.itemNames = {} | ||||
|  | ||||
|     def save(self, filename, *, name=None): | ||||
|     def save(self): | ||||
|         # Assert special handling of bank 9 expansion is fine | ||||
|         for i in range(0x3d42, 0x4000): | ||||
|             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.background_tiles.store(self) | ||||
|         self.background_attributes.store(self) | ||||
|         super().save(filename, name=name) | ||||
|         return super().save() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 threeandthreee
					threeandthreee