Ejemplo n.º 1
0
def generateRom(options, seed, logic, multiworld=None):
    print("Loading: %s" % (options.input_filename))
    rom = ROMWithTables(options.input_filename)

    if options.gfxmod:
        for gfx in options.gfxmod:
            patches.aesthetics.gfxMod(rom, gfx)

    expanded_inventory = options.witch or options.boomerang == 'gift'
    assembler.resetConsts()
    if expanded_inventory:
        assembler.const("wHasFlippers", 0xDB3E)
        assembler.const("wHasMedicine", 0xDB3F)
        assembler.const("wTradeSequenceItem", 0xDB40)
        assembler.const("wSeashellsCount", 0xDB41)
    else:
        assembler.const("wHasFlippers", 0xDB0C)
        assembler.const("wHasMedicine", 0xDB0D)
        assembler.const("wTradeSequenceItem", 0xDB0E)
        assembler.const("wSeashellsCount", 0xDB0F)
    assembler.const(
        "wGoldenLeaves",
        0xDB42)  # New memory location where to store the golden leaf counter
    assembler.const(
        "wCollectedTunics", 0xDB6D
    )  # Memory location where to store which tunic options are available
    assembler.const("wCustomMessage", 0xC0A0)

    assembler.const("wLinkState", 0xDE10)
    # We store the link info in unused color dungeon flags, so it gets preserved in the savegame.
    assembler.const("wLinkSyncSequenceNumber", 0xDDF6)
    assembler.const("wLinkStatusBits", 0xDDF7)
    assembler.const("wLinkGiveItem", 0xDDF8)
    assembler.const("wLinkGiveItemFrom", 0xDDF9)
    assembler.const("wLinkSendItemRoomHigh", 0xDDFA)
    assembler.const("wLinkSendItemRoomLow", 0xDDFB)
    assembler.const("wLinkSendItemTarget", 0xDDFC)
    assembler.const("wLinkSendItemItem", 0xDDFD)

    patches.core.cleanup(rom)
    patches.phone.patchPhone(rom)
    patches.core.bugfixWrittingWrongRoomStatus(rom)
    patches.core.bugfixBossroomTopPush(rom)
    patches.core.bugfixPowderBagSprite(rom)
    patches.owl.removeOwlEvents(rom)
    patches.bank3e.addBank3E(rom, seed)
    patches.bank3f.addBank3F(rom)
    patches.core.removeGhost(rom)
    patches.core.alwaysAllowSecretBook(rom)
    patches.core.warpHome(rom)
    patches.core.injectMainLoop(rom)
    if options.keysanity:
        patches.inventory.advancedInventorySubscreen(rom)
    if expanded_inventory:
        patches.inventory.moreSlots(rom)
    if options.witch:
        patches.witch.updateWitch(rom)
    patches.softlock.fixAll(rom)
    patches.maptweaks.tweakMap(rom)
    patches.chest.fixChests(rom)
    patches.shop.fixShop(rom)
    patches.trendy.fixTrendy(rom)
    patches.droppedKey.fixDroppedKey(rom)
    patches.madBatter.upgradeMadBatter(rom)
    patches.tunicFairy.upgradeTunicFairy(rom)
    patches.health.upgradeHealthContainers(rom)
    if options.owlstatues in ("dungeon", "both"):
        patches.owl.upgradeDungeonOwlStatues(rom)
    if options.owlstatues in ("overworld", "both"):
        patches.owl.upgradeOverworldOwlStatues(rom)
    patches.goldenLeaf.fixGoldenLeaf(rom)
    patches.heartPiece.fixHeartPiece(rom)
    patches.seashell.fixSeashell(rom)
    patches.seashell.upgradeMansion(rom)
    patches.songs.upgradeMarin(rom)
    patches.songs.upgradeManbo(rom)
    patches.songs.upgradeMamu(rom)
    patches.bowwow.fixBowwow(rom, everywhere=options.bowwow != 'normal')
    if options.bowwow == 'swordless':
        patches.bowwow.swordlessBowwowMapPatches(rom)
    patches.desert.desertAccess(rom)
    # patches.reduceRNG.slowdownThreeOfAKind(rom)
    patches.aesthetics.noSwordMusic(rom)
    patches.aesthetics.reduceMessageLengths(rom)
    if options.hardMode:
        patches.hardMode.enableHardMode(rom)
    if options.textmode == 'fast':
        patches.aesthetics.fastText(rom)
    if options.textmode == 'none':
        patches.aesthetics.fastText(rom)
        patches.aesthetics.noText(rom)
    if options.removeNagMessages:
        patches.aesthetics.removeNagMessages(rom)
    if options.lowhpbeep == 'slow':
        patches.aesthetics.slowLowHPBeep(rom)
    if options.lowhpbeep == 'none':
        patches.aesthetics.removeLowHPBeep(rom)
    if options.linkspalette is not None:
        patches.aesthetics.forceLinksPalette(rom, options.linkspalette)
    if options.romdebugmode:
        # The default rom has this build in, just need to set a flag and we get this save.
        rom.patch(0, 0x0003, "00", "01")

    # Patch the sword check on the shopkeeper turning around.
    if options.steal == 'never':
        rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
    elif options.steal == 'always':
        rom.patch(4, 0x36F9, "FA4EDB", "3E0100")

    if options.hpmode == 'inverted':
        patches.health.setStartHealth(rom, 9)
    elif options.hpmode == '1':
        patches.health.setStartHealth(rom, 1)

    if options.goal != "random" and options.goal is not None:
        patches.goal.setRequiredInstrumentCount(rom, int(options.goal))
    if options.goal == "raft":
        patches.goal.setRaftGoal(rom)

    patches.inventory.selectToSwitchSongs(rom)
    if options.quickswap == 'a':
        patches.core.quickswap(rom, 1)
    elif options.quickswap == 'b':
        patches.core.quickswap(rom, 0)

    # Monkey bridge patch, always have the bridge there.
    rom.patch(0x00,
              0x333D,
              assembler.ASM("bit 4, e\njr Z, $05"),
              b"",
              fill_nop=True)

    if multiworld is None:
        hints.addHints(rom, random, logic.iteminfo_list)

        # Patch the generated logic into the rom
        if logic.entranceMapping:
            patches.dungeonEntrances.changeEntrances(rom,
                                                     logic.entranceMapping)
        for spot in logic.iteminfo_list:
            spot.patch(rom, spot.item)
        patches.enemies.changeBosses(rom, logic.bossMapping)
    else:
        # Set a unique ID in the rom for multiworld
        for n in range(4):
            rom.patch(0x00, 0x0051 + n, "00", "%02x" % (seed[n]))
        rom.patch(0x00, 0x0055, "00", "%02x" % (multiworld))

        # Patch the generated logic into the rom
        if logic.worlds[multiworld].entranceMapping:
            patches.dungeonEntrances.changeEntrances(
                rom, logic.worlds[multiworld].entranceMapping)
        for spot in logic.iteminfo_list:
            if spot.world == multiworld:
                spot.patch(rom, spot.item)
        patches.enemies.changeBosses(rom, logic.worlds[multiworld].bossMapping)

    patches.titleScreen.setRomInfo(
        rom,
        binascii.hexlify(seed).decode("ascii").upper(), options)

    return rom
Ejemplo n.º 2
0
def generateRom(options, seed, logic, *, rnd=None, multiworld=None):
    print("Loading: %s" % (options.input_filename))
    rom = ROMWithTables(options.input_filename)

    pymods = []
    if options.pymod:
        for pymod in options.pymod:
            spec = importlib.util.spec_from_loader(
                pymod, importlib.machinery.SourceFileLoader(pymod, pymod))
            module = importlib.util.module_from_spec(spec)
            spec.loader.exec_module(module)
            pymods.append(module)
    for pymod in pymods:
        pymod.prePatch(rom)

    if options.gfxmod:
        for gfx in options.gfxmod:
            patches.aesthetics.gfxMod(rom, gfx)

    expanded_inventory = options.witch or options.boomerang == 'gift'
    assembler.resetConsts()
    if expanded_inventory:
        assembler.const("INV_SIZE", 16)
        assembler.const("wHasFlippers", 0xDB3E)
        assembler.const("wHasMedicine", 0xDB3F)
        assembler.const("wTradeSequenceItem", 0xDB40)
        assembler.const("wSeashellsCount", 0xDB41)
    else:
        assembler.const("INV_SIZE", 12)
        assembler.const("wHasFlippers", 0xDB0C)
        assembler.const("wHasMedicine", 0xDB0D)
        assembler.const("wTradeSequenceItem", 0xDB0E)
        assembler.const("wSeashellsCount", 0xDB0F)
    assembler.const(
        "wGoldenLeaves",
        0xDB42)  # New memory location where to store the golden leaf counter
    assembler.const(
        "wCollectedTunics", 0xDB6D
    )  # Memory location where to store which tunic options are available
    assembler.const("wCustomMessage", 0xC0A0)

    # We store the link info in unused color dungeon flags, so it gets preserved in the savegame.
    assembler.const("wLinkSyncSequenceNumber", 0xDDF6)
    assembler.const("wLinkStatusBits", 0xDDF7)
    assembler.const("wLinkGiveItem", 0xDDF8)
    assembler.const("wLinkGiveItemFrom", 0xDDF9)
    assembler.const("wLinkSendItemRoomHigh", 0xDDFA)
    assembler.const("wLinkSendItemRoomLow", 0xDDFB)
    assembler.const("wLinkSendItemTarget", 0xDDFC)
    assembler.const("wLinkSendItemItem", 0xDDFD)

    assembler.const("wZolSpawnCount", 0xDE10)
    assembler.const("wCuccoSpawnCount", 0xDE11)

    #assembler.const("HARDWARE_LINK", 1)
    assembler.const("HARD_MODE", 1 if options.hardMode else 0)

    patches.core.cleanup(rom)
    if multiworld is not None:
        patches.save.singleSaveSlot(rom)
    patches.phone.patchPhone(rom)
    patches.core.bugfixWrittingWrongRoomStatus(rom)
    patches.core.bugfixBossroomTopPush(rom)
    patches.core.bugfixPowderBagSprite(rom)
    patches.owl.removeOwlEvents(rom)
    patches.bank3e.addBank3E(rom, seed)
    patches.bank3f.addBank3F(rom)
    patches.core.removeGhost(rom)
    patches.core.alwaysAllowSecretBook(rom)
    patches.core.injectMainLoop(rom)
    if options.dungeon_items in ('localnightmarekey', 'keysanity'):
        patches.inventory.advancedInventorySubscreen(rom)
    if expanded_inventory:
        patches.inventory.moreSlots(rom)
    if options.witch:
        patches.witch.updateWitch(rom)
    patches.softlock.fixAll(rom)
    patches.maptweaks.tweakMap(rom)
    patches.chest.fixChests(rom)
    patches.shop.fixShop(rom)
    patches.trendy.fixTrendy(rom)
    patches.droppedKey.fixDroppedKey(rom)
    patches.madBatter.upgradeMadBatter(rom)
    patches.tunicFairy.upgradeTunicFairy(rom)
    patches.tarin.updateTarin(rom)
    patches.fishingMinigame.updateFinishingMinigame(rom)
    patches.health.upgradeHealthContainers(rom)
    if options.owlstatues in ("dungeon", "both"):
        patches.owl.upgradeDungeonOwlStatues(rom)
    if options.owlstatues in ("overworld", "both"):
        patches.owl.upgradeOverworldOwlStatues(rom)
    patches.goldenLeaf.fixGoldenLeaf(rom)
    patches.heartPiece.fixHeartPiece(rom)
    patches.seashell.fixSeashell(rom)
    patches.instrument.fixInstruments(rom)
    patches.seashell.upgradeMansion(rom)
    patches.songs.upgradeMarin(rom)
    patches.songs.upgradeManbo(rom)
    patches.songs.upgradeMamu(rom)
    patches.bowwow.fixBowwow(rom, everywhere=options.bowwow != 'normal')
    if options.bowwow != 'normal':
        patches.bowwow.bowwowMapPatches(rom)
    patches.desert.desertAccess(rom)
    if options.overworld == 'dungeondive':
        patches.overworld.patchOverworldTilesets(rom)
        patches.overworld.createDungeonOnlyOverworld(rom)
    # patches.reduceRNG.slowdownThreeOfAKind(rom)
    patches.reduceRNG.fixHorseHeads(rom)
    patches.bomb.onlyDropBombsWhenHaveBombs(rom)
    patches.aesthetics.noSwordMusic(rom)
    patches.aesthetics.reduceMessageLengths(rom, rnd)
    patches.aesthetics.allowColorDungeonSpritesEverywhere(rom)
    if options.music == 'random':
        patches.music.randomizeMusic(rom, rnd)
    elif options.music == 'off':
        patches.music.noMusic(rom)
    if options.removeFlashingLights:
        patches.aesthetics.removeFlashingLights(rom)
    if options.hardMode:
        patches.hardMode.enableHardMode(rom)
    if options.textmode == 'fast':
        patches.aesthetics.fastText(rom)
    if options.textmode == 'none':
        patches.aesthetics.fastText(rom)
        patches.aesthetics.noText(rom)
    if options.removeNagMessages:
        patches.aesthetics.removeNagMessages(rom)
    if options.lowhpbeep == 'slow':
        patches.aesthetics.slowLowHPBeep(rom)
    if options.lowhpbeep == 'none':
        patches.aesthetics.removeLowHPBeep(rom)
    if options.linkspalette is not None:
        patches.aesthetics.forceLinksPalette(rom, options.linkspalette)
    if options.romdebugmode:
        # The default rom has this build in, just need to set a flag and we get this save.
        rom.patch(0, 0x0003, "00", "01")

    # Patch the sword check on the shopkeeper turning around.
    if options.steal == 'never':
        rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
    elif options.steal == 'always':
        rom.patch(4, 0x36F9, "FA4EDB", "3E0100")

    if options.hpmode == 'inverted':
        patches.health.setStartHealth(rom, 9)
    elif options.hpmode == '1':
        patches.health.setStartHealth(rom, 1)

    if options.goal == "raft":
        patches.goal.setRaftGoal(rom)
    elif options.goal == "seashells":
        patches.goal.setSeashellGoal(rom, 20)
    elif options.goal != "random" and options.goal is not None:
        patches.goal.setRequiredInstrumentCount(rom, int(options.goal))

    patches.inventory.songSelectAfterOcarinaSelect(rom)
    if options.quickswap == 'a':
        patches.core.quickswap(rom, 1)
    elif options.quickswap == 'b':
        patches.core.quickswap(rom, 0)

    # Monkey bridge patch, always have the bridge there.
    rom.patch(0x00,
              0x333D,
              assembler.ASM("bit 4, e\njr Z, $05"),
              b"",
              fill_nop=True)

    if multiworld is None:
        hints.addHints(rom, rnd, logic.iteminfo_list)

        world_setup = logic.world_setup
        item_list = logic.iteminfo_list
    else:
        patches.multiworld.addMultiworldShop(rom, multiworld,
                                             options.multiworld)

        # Set a unique ID in the rom for multiworld
        for n in range(4):
            rom.patch(0x00, 0x0051 + n, "00", "%02x" % (seed[n]))
        rom.patch(0x00, 0x0055, "00", "%02x" % (multiworld))
        rom.patch(0x00, 0x0056, "00",
                  "01")  # Set the Bizhawk connector version.

        world_setup = logic.worlds[multiworld].world_setup
        item_list = [
            spot for spot in logic.iteminfo_list if spot.world == multiworld
        ]

    # Patch the generated logic into the rom
    patches.chest.setMultiChest(rom, world_setup.multichest)
    if options.overworld != "dungeondive":
        patches.entrances.changeEntrances(rom, world_setup.entrance_mapping)
    for spot in item_list:
        if spot.item.startswith("*"):
            spot.item = spot.item[1:]
        spot.patch(rom, spot.item)
    patches.enemies.changeBosses(rom, world_setup.boss_mapping)
    patches.enemies.changeMiniBosses(rom, world_setup.miniboss_mapping)

    if not options.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,
        binascii.hexlify(seed).decode("ascii").upper(), options)
    patches.endscreen.updateEndScreen(rom)
    patches.aesthetics.updateSpriteData(rom)
    if options.doubletrouble:
        patches.enemies.doubleTrouble(rom)
    for pymod in pymods:
        pymod.postPatch(rom)
    return rom
Ejemplo n.º 3
0
def main(mainargs=None):
    import argparse
    import sys

    parser = argparse.ArgumentParser(description='Randomize!')
    parser.add_argument('input_filename',
                        metavar='input rom',
                        type=str,
                        help="Rom file to use as input.")
    parser.add_argument(
        '-o',
        '--output',
        dest="output_filename",
        metavar='output rom',
        type=str,
        required=False,
        help="Output filename to use. If not specified [seed].gbc is used.")
    parser.add_argument('--dump',
                        dest="dump",
                        type=str,
                        nargs="*",
                        help="Dump the logic of the given rom (spoilers!)")
    parser.add_argument(
        '--spoilerformat',
        dest="spoilerformat",
        choices=["none", "console", "text", "json"],
        default="none",
        help="Sets the output format for the generated seed's spoiler log")
    parser.add_argument(
        '--spoilerfilename',
        dest="spoiler_filename",
        type=str,
        required=False,
        help=
        "Output filename to use for the spoiler log.  If not specified, LADXR_[seed].txt/json is used."
    )
    parser.add_argument(
        '--test',
        dest="test",
        action="store_true",
        help="Test the logic of the given rom, without showing anything.")
    parser.add_argument('-s',
                        '--seed',
                        dest="seed",
                        type=str,
                        required=False,
                        help="Generate the specified seed")
    parser.add_argument(
        '--romdebugmode',
        dest="romdebugmode",
        action="store_true",
        help=
        "Patch the rom so that debug mode is enabled, this creates a default save with most items and unlocks some debug features."
    )
    parser.add_argument('--exportmap',
                        dest="exportmap",
                        action="store_true",
                        help="Export the map (many graphical mistakes)")
    parser.add_argument('--emptyplan',
                        dest="emptyplan",
                        type=str,
                        required=False,
                        help="Write an unfilled plan file")
    parser.add_argument(
        '--timeout',
        type=float,
        required=False,
        help="Timeout generating the seed after the specified number of seconds"
    )
    parser.add_argument(
        '--logdirectory',
        dest="log_directory",
        type=str,
        required=False,
        help=
        "Directory to write the JSON log file. Generated independently from the spoiler log and omitted by default."
    )

    # Flags that effect gameplay
    parser.add_argument('--plan',
                        dest="plan",
                        metavar='plandomizer',
                        type=str,
                        required=False,
                        help="Read an item placement plan")
    parser.add_argument(
        '--race',
        dest="race",
        nargs="?",
        default=False,
        const=True,
        help=
        "Enable race mode. This generates a rom from which the spoiler log cannot be dumped and the seed cannot be extracted."
    )
    parser.add_argument(
        '--logic',
        dest="logic",
        choices=["casual", "normal", "hard", "glitched", "hell"],
        help="Which level of logic is required.")
    parser.add_argument('--multiworld',
                        dest="multiworld",
                        type=int,
                        required=False,
                        help="Generates multiple roms for a multiworld setup.")
    parser.add_argument(
        '--multiworld-config',
        dest="multiworld_config",
        action="append",
        required=False,
        help=
        "Set configuration for a multiworld player, supply multiple times for settings per player"
    )
    parser.add_argument(
        '--forwardfactor',
        dest="forwardfactor",
        type=float,
        required=False,
        help=
        "Forward item weight adjustment factor, lower values generate more rear heavy seeds while higher values generate front heavy seeds. Default is 0.5."
    )
    parser.add_argument('--heartpiece',
                        dest="heartpiece",
                        action="store_true",
                        help="Enables randomization of heart pieces.")
    parser.add_argument(
        '--seashells',
        dest="seashells",
        action="store_true",
        help=
        "Enables seashells mode, which randomizes the secret sea shells hiding in the ground/trees. (chest are always randomized)"
    )
    parser.add_argument(
        '--heartcontainers',
        dest="heartcontainers",
        action="store_true",
        help=
        "Enables heartcontainer mode, which randomizes the heart containers dropped by bosses."
    )
    parser.add_argument('--instruments',
                        dest="instruments",
                        action="store_true",
                        help="Shuffle the instruments in the item pool.")
    parser.add_argument(
        '--owlstatues',
        dest="owlstatues",
        choices=['none', 'dungeon', 'overworld', 'both'],
        default='none',
        help=
        "Give the owl statues in dungeons or on the overworld items as well, instead of showing the normal hints"
    )
    parser.add_argument(
        '--dungeon-items',
        dest="dungeon_items",
        choices=['standard', 'localkeys', 'localnightmarekey', 'keysanity'],
        default='standard',
        help=
        "Sets what gets done with dungeon items, if they are in their own dungeon or not."
    )
    parser.add_argument('--randomstartlocation',
                        dest="randomstartlocation",
                        action="store_true",
                        help="Place your starting house at a random location.")
    parser.add_argument(
        '--dungeonshuffle',
        dest="dungeonshuffle",
        action="store_true",
        help="Enable dungeon shuffle, puts dungeons on different spots.")
    parser.add_argument(
        '--entranceshuffle',
        dest="entranceshuffle",
        choices=["none", "simple", "advanced", "expert", "insanity"],
        default="none",
        help="Enable entrance shuffle, shuffles around overworld entrances.")
    parser.add_argument(
        '--boss',
        dest="boss",
        choices=["default", "shuffle", "random"],
        default="default",
        help="Enable boss shuffle, swaps around dungeon bosses.")
    parser.add_argument('--miniboss',
                        dest="miniboss",
                        choices=["default", "shuffle", "random"],
                        default="default",
                        help="Shuffle the minibosses or just randomize them.")
    parser.add_argument('--doubletrouble',
                        dest="doubletrouble",
                        action="store_true",
                        help="...")
    parser.add_argument('--witch',
                        dest="witch",
                        action="store_true",
                        help="Enables witch and toadstool in the item pool.")
    parser.add_argument(
        '--hpmode',
        dest="hpmode",
        choices=['default', 'inverted', '1'],
        default='default',
        help=
        "Set the HP gamplay mode. Inverted causes health containers to take HP instead of give it and you start with more health. 1 sets your starting health to just 1 hearth."
    )
    parser.add_argument(
        '--boomerang',
        dest="boomerang",
        choices=['default', 'trade', 'gift'],
        default='default',
        help=
        "Put the boomerang and the trade with the boomerang in the item pool")
    parser.add_argument('--steal',
                        dest="steal",
                        choices=['never', 'always', 'default'],
                        default='always',
                        help="Configure when to allow stealing from the shop.")
    parser.add_argument(
        '--hard-mode',
        dest="hardMode",
        action="store_true",
        help=
        "Make the game a bit harder, less health from drops, bombs damage yourself, and less iframes."
    )
    parser.add_argument(
        '--goal',
        dest="goal",
        type=goal,
        default='8',
        help=
        "Configure the instrument goal for this rom: any number between -1 (open egg) and 8, a range (e.g. 4-7), 'random', or 'raft' / 'seashells' for special goals."
    )
    parser.add_argument(
        '--accessibility',
        dest="accessibility_rule",
        choices=['all', 'goal'],
        help=
        "Switches between making sure all locations are reachable or only the goal is reachable"
    )
    parser.add_argument(
        '--bowwow',
        dest="bowwow",
        choices=['normal', 'always', 'swordless'],
        default='normal',
        help=
        "Enables 'good boy mode', where BowWow is allowed on all screens and can damage bosses and more enemies."
    )
    parser.add_argument(
        '--pool',
        dest="itempool",
        choices=['normal', 'casual', 'pain', 'keyup'],
        default='normal',
        help="Sets up different item pools, for easier or harder gameplay.")
    parser.add_argument(
        '--overworld',
        dest="overworld",
        choices=['normal', 'dungeondive'],
        default='normal',
        help=
        "Allows switching to the dungeondive overworld, where there are only dungeons."
    )
    parser.add_argument('--pymod',
                        dest="pymod",
                        action='append',
                        help="Load python code mods.")

    # Just aestetic flags
    parser.add_argument('--gfxmod',
                        dest="gfxmod",
                        action='append',
                        help="Load graphical mods.")
    parser.add_argument(
        '--remove-flashing-lights',
        dest="removeFlashingLights",
        action="store_true",
        help=
        "Remove the flashing light effects from mamu, the shopkeeper and madbatter."
    )
    parser.add_argument(
        '--quickswap',
        dest="quickswap",
        choices=['none', 'a', 'b'],
        default='none',
        help=
        "Configure quickswap for A or B button (select key swaps, no longer opens map)"
    )
    parser.add_argument(
        '--textmode',
        dest="textmode",
        choices=['default', 'fast', 'none'],
        default='default',
        help=
        "Default just keeps text normal, fast makes text appear twice as fast, and none removes all text from the game."
    )
    parser.add_argument(
        '--nag-messages',
        dest="removeNagMessages",
        action="store_false",
        help=
        "Enable the nag messages on touching stones and crystals. By default they are removed."
    )
    parser.add_argument('--lowhpbeep',
                        dest="lowhpbeep",
                        choices=['default', 'slow', 'none'],
                        default='slow',
                        help="Slows or disables the low health beeping sound")
    parser.add_argument('--linkspalette',
                        dest="linkspalette",
                        type=int,
                        default=None,
                        help="Force the palette of link")
    parser.add_argument('--music',
                        dest="music",
                        choices=['default', 'random', 'off'],
                        default='default',
                        help="Randomizes or disable the music")

    args = parser.parse_args(mainargs)
    if args.multiworld is not None:
        args.multiworld_options = [args] * args.multiworld
        if args.multiworld_config is not None:
            for index, settings_string in enumerate(args.multiworld_config):
                args.multiworld_options[index] = parser.parse_args(
                    [args.input_filename] + shlex.split(settings_string),
                    namespace=argparse.Namespace(**vars(args)))

    if args.timeout is not None:
        import threading
        import time
        import os

        def timeoutFunction():
            time.sleep(args.timeout)
            print("TIMEOUT")
            sys.stdout.flush()
            os._exit(1)

        threading.Thread(target=timeoutFunction, daemon=True).start()

    if args.exportmap:
        import mapexport
        print("Loading: %s" % (args.input_filename))
        rom = ROMWithTables(args.input_filename)
        mapexport.MapExport(rom)
        sys.exit(0)

    if args.emptyplan:
        import locations.items
        f = open(args.emptyplan, "wt")
        f.write(";Plandomizer data\n;Items: %s\n" % (", ".join(
            map(lambda n: getattr(locations.items, n),
                filter(lambda n: not n.startswith("__"), dir(
                    locations.items))))))
        f.write(";Modify the item pool:\n")
        f.write(";Pool:SWORD:+5\n")
        f.write(";Pool:RUPEES_50:-5\n")
        import worldSetup
        iteminfo_list = logic.Logic(
            args, world_setup=worldSetup.WorldSetup()).iteminfo_list
        for ii in sorted(iteminfo_list,
                         key=lambda n:
                         (n.location.dungeon
                          if n.location.dungeon else -1, repr(n.metadata))):
            if len(ii.OPTIONS) > 1:
                f.write(";%r\n" % (ii.metadata))
                f.write("Location:%s: \n" % (ii.nameId))
        sys.exit(0)

    if args.dump is not None or args.test:
        print("Loading: %s" % (args.input_filename))
        roms = [ROMWithTables(f) for f in [args.input_filename] + args.dump]

        if args.spoilerformat == "none":
            args.spoilerformat = "console"

        try:
            log = spoilerLog.SpoilerLog(args, roms)
            log.output(args.spoiler_filename)
            sys.exit(0)
        except spoilerLog.RaceRomException:
            print("Cannot read spoiler log for race rom")
            sys.exit(1)

    if args.seed:
        try:
            args.seed = binascii.unhexlify(args.seed)
        except binascii.Error:
            args.seed = args.seed.encode("ascii")

    retry_count = 0
    while True:
        try:
            r = randomizer.Randomizer(args, seed=args.seed)
            seed = binascii.hexlify(r.seed).decode("ascii").upper()
            break
        except randomizer.Error:
            if args.seed is not None:
                print("Specified seed does not produce a valid result.")
                sys.exit(1)
            retry_count += 1
            if retry_count > 100:
                print("Randomization keeps failing, abort!")
                sys.exit(1)
            print("Failed, trying again: %d" % (retry_count))

    print("Seed: %s" % (seed))
Ejemplo n.º 4
0
def main(mainargs=None):
    import argparse
    import sys

    parser = argparse.ArgumentParser(description='Randomize!')
    parser.add_argument('input_filename', metavar='input rom', type=str,
        help="Rom file to use as input.")
    parser.add_argument('-o', '--output', dest="output_filename", metavar='output rom', type=str, required=False,
        help="Output filename to use. If not specified [seed].gbc is used.")
    parser.add_argument('--dump', dest="dump", action="store_true",
        help="Dump the logic of the given rom (spoilers!)")
    parser.add_argument('--spoilerformat', dest="spoilerformat", choices=["none", "console", "text", "json"], default="none",
        help="Sets the output format for the generated seed's spoiler log")
    parser.add_argument('--spoilerfilename', dest="spoiler_filename", type=str, required=False,
        help="Output filename to use for the spoiler log.  If not specified, LADXR_[seed].txt/json is used.")
    parser.add_argument('--test', dest="test", action="store_true",
        help="Test the logic of the given rom, without showing anything.")
    parser.add_argument('-s', '--seed', dest="seed", type=str, required=False,
        help="Generate the specified seed")
    parser.add_argument('--romdebugmode', dest="romdebugmode", action="store_true",
        help="Patch the rom so that debug mode is enabled, this creates a default save with most items and unlocks some debug features.")
    parser.add_argument('--exportmap', dest="exportmap", action="store_true",
        help="Export the map (many graphical mistakes)")
    parser.add_argument('--emptyplan', dest="emptyplan", type=str, required=False,
        help="Write an unfilled plan file")
    parser.add_argument('--timeout', type=float, required=False,
        help="Timeout generating the seed after the specified number of seconds")

    # Flags that effect gameplay
    parser.add_argument('--plan', dest="plan", metavar='plandomizer', type=str, required=False,
        help="Read an item placement plan")
    parser.add_argument('--race', dest="race", action="store_true",
        help="Enable race mode. This generates a rom from which the spoiler log cannot be dumped and the seed cannot be extracted.")
    parser.add_argument('--logic', dest="logic", choices=["normal", "hard", "glitched", "hell"],
        help="Which level of logic is required.")
    parser.add_argument('--multiworld', dest="multiworld", type=int, required=False,
        help="Generates multiple roms for a multiworld setup.")
    parser.add_argument('--multiworld-config', dest="multiworld_config", action="append", required=False,
        help="Set configuration for a multiworld player, supply multiple times for settings per player")
    parser.add_argument('--forwardfactor', dest="forwardfactor", type=float, required=False,
        help="Forward item weight adjustment factor, lower values generate more rear heavy seeds while higher values generate front heavy seeds. Default is 0.5.")
    parser.add_argument('--heartpiece', dest="heartpiece", action="store_true",
        help="Enables randomization of heart pieces.")
    parser.add_argument('--seashells', dest="seashells", action="store_true",
        help="Enables seashells mode, which randomizes the secret sea shells hiding in the ground/trees. (chest are always randomized)")
    parser.add_argument('--heartcontainers', dest="heartcontainers", action="store_true",
        help="Enables heartcontainer mode, which randomizes the heart containers dropped by bosses.")
    parser.add_argument('--owlstatues', dest="owlstatues", choices=['none', 'dungeon', 'overworld', 'both'],
        help="Give the owl statues in dungeons or on the overworld items as well, instead of showing the normal hints")
    parser.add_argument('--keysanity', dest="keysanity", action="store_true",
        help="Enables keysanity mode, which shuffles all dungeon items outside dungeons as well.")
    parser.add_argument('--dungeonshuffle', dest="dungeonshuffle", action="store_true",
        help="Enable dungeon shuffle, puts dungeons on different spots.")
    parser.add_argument('--bossshuffle', dest="bossshuffle", action="store_true",
        help="Enable boss shuffle, swaps around dungeon bosses.")
    parser.add_argument('--witch', dest="witch", action="store_true",
        help="Enables witch and toadstool in the item pool.")
    parser.add_argument('--hpmode', dest="hpmode", choices=['default', 'inverted', '1'], default='default',
        help="Set the HP gamplay mode. Inverted causes health containers to take HP instead of give it and you start with more health. 1 sets your starting health to just 1 hearth.")
    parser.add_argument('--boomerang', dest="boomerang", choices=['default', 'trade', 'gift'], default='default',
        help="Put the boomerang and the trade with the boomerang in the item pool")
    parser.add_argument('--steal', dest="steal", choices=['never', 'always', 'default'], default='always',
        help="Configure when to allow stealing from the shop.")
    parser.add_argument('--hard-mode', dest="hardMode", action="store_true",
        help="Make the game a bit harder, less health from drops, bombs damage yourself, and less iframes.")
    parser.add_argument('--goal', dest="goal", choices=['-1', '0', '1', '2', '3', '4', '5', '6', '7', '8', 'random', 'raft'],
        help="Configure the instrument goal for this rom, anything between 0 and 8.")
    parser.add_argument('--bowwow', dest="bowwow", choices=['normal', 'always', 'swordless'], default='normal',
        help="Enables 'good boy mode', where BowWow is allowed on all screens and can damage bosses and more enemies.")
    parser.add_argument('--pool', dest="itempool", choices=['normal', 'casual', 'pain', 'keyup'], default='normal',
        help="Sets up different item pools, for easier or harder gameplay.")

    # Just aestetic flags
    parser.add_argument('--gfxmod', dest="gfxmod", action='append',
        help="Load graphical mods.")
    parser.add_argument('--quickswap', dest="quickswap", choices=['none', 'a', 'b'], default='none',
        help="Configure quickswap for A or B button (select key swaps, no longer opens map)")
    parser.add_argument('--textmode', dest="textmode", choices=['default', 'fast', 'none'], default='default',
        help="Default just keeps text normal, fast makes text appear twice as fast, and none removes all text from the game.")
    parser.add_argument('--nag-messages', dest="removeNagMessages", action="store_false",
        help="Enable the nag messages on touching stones and crystals. By default they are removed.")
    parser.add_argument('--lowhpbeep', dest="lowhpbeep", choices=['default', 'slow', 'none'], default='slow',
        help="Slows or disables the low health beeping sound")
    parser.add_argument('--linkspalette', dest="linkspalette", type=int, default=None,
        help="Force the palette of link")

    args = parser.parse_args(mainargs)
    if args.multiworld is not None:
        args.multiworld_options = [args] * args.multiworld
        if args.multiworld_config is not None:
            for index, settings_string in enumerate(args.multiworld_config):
                args.multiworld_options[index] = parser.parse_args([args.input_filename] + shlex.split(settings_string))

    if args.timeout is not None:
        import threading
        import time
        import os
        def timeoutFunction():
            time.sleep(args.timeout)
            print("TIMEOUT")
            sys.stdout.flush()
            os._exit(1)
        threading.Thread(target=timeoutFunction, daemon=True).start()

    if args.exportmap:
        import mapexport
        print("Loading: %s" % (args.input_filename))
        rom = ROMWithTables(args.input_filename)
        mapexport.MapExport(rom)
        sys.exit(0)

    if args.emptyplan:
        import checkMetadata
        import locations.items
        f = open(args.emptyplan, "wt")
        f.write(";Plandomizer data\n;Items: %s\n" % (", ".join(map(lambda n: getattr(locations.items, n), filter(lambda n: not n.startswith("__"), dir(locations.items))))))
        for key in dir(locations.items):
            f.write("")
        for name, data in sorted(checkMetadata.checkMetadataTable.items(), key=lambda n: str(n[1])):
            if name is not "None":
                f.write(";%s\n" % (data))
                f.write("%s: \n" % (name))
        sys.exit(0)

    if args.dump or args.test:
        print("Loading: %s" % (args.input_filename))
        rom = ROMWithTables(args.input_filename)

        if args.spoilerformat == "none":
            args.spoilerformat = "console"

        try:
            log = spoilerLog.SpoilerLog(args, rom)
            log.output(args.spoiler_filename)
            sys.exit(0)
        except spoilerLog.RaceRomException:
            print("Cannot read spoiler log for race rom")
            sys.exit(1)

    if args.seed:
        try:
            args.seed = binascii.unhexlify(args.seed)
        except binascii.Error:
            args.seed = args.seed.encode("ascii")

    retry_count = 0
    while True:
        try:
            r = randomizer.Randomizer(args, seed=args.seed)
            seed = binascii.hexlify(r.seed).decode("ascii").upper()
            break
        except randomizer.Error:
            if args.seed is not None:
                print("Specified seed does not produce a valid result.")
                sys.exit(1)
            retry_count += 1
            if retry_count > 100:
                print("Randomization keeps failing, abort!")
                sys.exit(1)
            print("Failed, trying again: %d" % (retry_count))

    print("Seed: %s" % (seed))