mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-18 21:38:13 -07:00
Compare commits
9 Commits
webhost3.8
...
NewSoupVi-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
66927171bc | ||
|
|
74ed14b1c7 | ||
|
|
41f1ab054c | ||
|
|
8019687861 | ||
|
|
32803448df | ||
|
|
d665d8f944 | ||
|
|
726144a573 | ||
|
|
55f1c08291 | ||
|
|
9f2717bfc9 |
@@ -234,7 +234,7 @@ def generate_yaml(game: str):
|
|||||||
# Detect random-* keys and set their options accordingly
|
# Detect random-* keys and set their options accordingly
|
||||||
for key, val in options.copy().items():
|
for key, val in options.copy().items():
|
||||||
if key.startswith("random-"):
|
if key.startswith("random-"):
|
||||||
options[key[len("random-"):]] = "random"
|
options[key.removeprefix("random-")] = "random"
|
||||||
del options[key]
|
del options[key]
|
||||||
|
|
||||||
# Error checking
|
# Error checking
|
||||||
|
|||||||
@@ -303,6 +303,31 @@ generation (entrance randomization).
|
|||||||
An access rule is a function that returns `True` or `False` for a `Location` or `Entrance` based on the current `state`
|
An access rule is a function that returns `True` or `False` for a `Location` or `Entrance` based on the current `state`
|
||||||
(items that have been collected).
|
(items that have been collected).
|
||||||
|
|
||||||
|
The two possible ways to make a [CollectionRule](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/generic/Rules.py#L10) are:
|
||||||
|
- `def rule(state: CollectionState) -> bool:`
|
||||||
|
- `lambda state: ... boolean expression ...`
|
||||||
|
|
||||||
|
An access rule can be assigned through `set_rule(location, rule)`.
|
||||||
|
|
||||||
|
Access rules usually check for one of two things.
|
||||||
|
- Items that have been collected (e.g. `state.has("Sword", player)`)
|
||||||
|
- Locations, Regions or Entrances that have been reached (e.g. `state.can_reach_region("Boss Room")`)
|
||||||
|
|
||||||
|
Keep in mind that entrances and locations implicitly check for the accessibility of their parent region, so you do not need to check explicitly for it.
|
||||||
|
|
||||||
|
#### An important note on Entrance access rules:
|
||||||
|
When using `state.can_reach` within an entrance access condition, you must also use `multiworld.register_indirect_condition`.
|
||||||
|
|
||||||
|
For efficiency reasons, every time reachable regions are searched, every entrance is only checked once in a somewhat non-deterministic order.
|
||||||
|
This is fine when checking for items using `state.has`, because items do not change during a region sweep.
|
||||||
|
However, `state.can_reach` checks for the very same thing we are updating: Regions.
|
||||||
|
This can lead to non-deterministic behavior and, in the worst case, even generation failures.
|
||||||
|
Even doing `state.can_reach_location` or `state.can_reach_entrance` is problematic, as these functions call `state.can_reach_region` on the respective parent region.
|
||||||
|
|
||||||
|
**Therefore, it is considered unsafe to perform `state.can_reach` from within an access condition for an entrance**, unless you are checking for something that sits in the source region of the entrance.
|
||||||
|
You can use `multiworld.register_indirect_condition(region, entrance)` to explicitly tell the generator that, when a given region becomes accessible, it is necessary to re-check a specific entrance.
|
||||||
|
You **must** use `multiworld.register_indirect_condition` if you perform this kind of `can_reach` from an entrance access rule, unless you have a **very** good technical understanding of the relevant code and can reason why it will never lead to problems in your case.
|
||||||
|
|
||||||
### Item Rules
|
### Item Rules
|
||||||
|
|
||||||
An item rule is a function that returns `True` or `False` for a `Location` based on a single item. It can be used to
|
An item rule is a function that returns `True` or `False` for a `Location` based on a single item. It can be used to
|
||||||
@@ -629,7 +654,7 @@ def set_rules(self) -> None:
|
|||||||
|
|
||||||
Custom methods can be defined for your logic rules. The access rule that ultimately gets assigned to the Location or
|
Custom methods can be defined for your logic rules. The access rule that ultimately gets assigned to the Location or
|
||||||
Entrance should be
|
Entrance should be
|
||||||
a [`CollectionRule`](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/generic/Rules.py#L9).
|
a [`CollectionRule`](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/generic/Rules.py#L10).
|
||||||
Typically, this is done by defining a lambda expression on demand at the relevant bit, typically calling other
|
Typically, this is done by defining a lambda expression on demand at the relevant bit, typically calling other
|
||||||
functions, but this can also be achieved by defining a method with the appropriate format and assigning it directly.
|
functions, but this can also be achieved by defining a method with the appropriate format and assigning it directly.
|
||||||
For an example, see [The Messenger](/worlds/messenger/rules.py).
|
For an example, see [The Messenger](/worlds/messenger/rules.py).
|
||||||
|
|||||||
@@ -329,7 +329,7 @@ class WorldTestBase(unittest.TestCase):
|
|||||||
for n in range(len(locations) - 1, -1, -1):
|
for n in range(len(locations) - 1, -1, -1):
|
||||||
if locations[n].can_reach(state):
|
if locations[n].can_reach(state):
|
||||||
sphere.append(locations.pop(n))
|
sphere.append(locations.pop(n))
|
||||||
self.assertTrue(sphere or self.multiworld.worlds[1].options.accessibility == "minimal",
|
self.assertTrue(sphere or self.multiworld.accessibility[1] == "minimal",
|
||||||
f"Unreachable locations: {locations}")
|
f"Unreachable locations: {locations}")
|
||||||
if not sphere:
|
if not sphere:
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -60,18 +60,17 @@ class DOOM2World(World):
|
|||||||
# Item ratio that scales depending on episode count. These are the ratio for 3 episode. In DOOM1.
|
# Item ratio that scales depending on episode count. These are the ratio for 3 episode. In DOOM1.
|
||||||
# The ratio have been tweaked seem, and feel good.
|
# The ratio have been tweaked seem, and feel good.
|
||||||
items_ratio: Dict[str, float] = {
|
items_ratio: Dict[str, float] = {
|
||||||
"Armor": 39,
|
"Armor": 41,
|
||||||
"Mega Armor": 23,
|
"Mega Armor": 25,
|
||||||
"Berserk": 11,
|
"Berserk": 12,
|
||||||
"Invulnerability": 10,
|
"Invulnerability": 10,
|
||||||
"Partial invisibility": 18,
|
"Partial invisibility": 18,
|
||||||
"Supercharge": 26,
|
"Supercharge": 28,
|
||||||
"Medikit": 15,
|
"Medikit": 15,
|
||||||
"Box of bullets": 13,
|
"Box of bullets": 13,
|
||||||
"Box of rockets": 13,
|
"Box of rockets": 13,
|
||||||
"Box of shotgun shells": 13,
|
"Box of shotgun shells": 13,
|
||||||
"Energy cell pack": 10,
|
"Energy cell pack": 10
|
||||||
"Megasphere": 7
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, multiworld: MultiWorld, player: int):
|
def __init__(self, multiworld: MultiWorld, player: int):
|
||||||
@@ -234,7 +233,6 @@ class DOOM2World(World):
|
|||||||
self.create_ratioed_items("Invulnerability", itempool)
|
self.create_ratioed_items("Invulnerability", itempool)
|
||||||
self.create_ratioed_items("Partial invisibility", itempool)
|
self.create_ratioed_items("Partial invisibility", itempool)
|
||||||
self.create_ratioed_items("Supercharge", itempool)
|
self.create_ratioed_items("Supercharge", itempool)
|
||||||
self.create_ratioed_items("Megasphere", itempool)
|
|
||||||
|
|
||||||
while len(itempool) < self.location_count:
|
while len(itempool) < self.location_count:
|
||||||
itempool.append(self.create_item(self.get_filler_item_name()))
|
itempool.append(self.create_item(self.get_filler_item_name()))
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ Some steps also assume use of Windows, so may vary with your OS.
|
|||||||
|
|
||||||
## Installing the Archipelago software
|
## Installing the Archipelago software
|
||||||
|
|
||||||
The most recent public release of Archipelago can be found on GitHub:
|
The most recent public release of Archipelago can be found on the GitHub Releases page:
|
||||||
[Archipelago Lastest Release](https://github.com/ArchipelagoMW/Archipelago/releases/latest).
|
[Archipelago Releases Page](https://github.com/ArchipelagoMW/Archipelago/releases).
|
||||||
|
|
||||||
Run the exe file, and after accepting the license agreement you will be asked which components you would like to
|
Run the exe file, and after accepting the license agreement you will be asked which components you would like to
|
||||||
install.
|
install.
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ class HereticWorld(World):
|
|||||||
"Tome of Power": 16,
|
"Tome of Power": 16,
|
||||||
"Silver Shield": 10,
|
"Silver Shield": 10,
|
||||||
"Enchanted Shield": 5,
|
"Enchanted Shield": 5,
|
||||||
"Torch": 5,
|
|
||||||
"Morph Ovum": 3,
|
"Morph Ovum": 3,
|
||||||
"Mystic Urn": 2,
|
"Mystic Urn": 2,
|
||||||
"Chaos Device": 1,
|
"Chaos Device": 1,
|
||||||
@@ -243,7 +242,6 @@ class HereticWorld(World):
|
|||||||
self.create_ratioed_items("Mystic Urn", itempool)
|
self.create_ratioed_items("Mystic Urn", itempool)
|
||||||
self.create_ratioed_items("Ring of Invincibility", itempool)
|
self.create_ratioed_items("Ring of Invincibility", itempool)
|
||||||
self.create_ratioed_items("Shadowsphere", itempool)
|
self.create_ratioed_items("Shadowsphere", itempool)
|
||||||
self.create_ratioed_items("Torch", itempool)
|
|
||||||
self.create_ratioed_items("Timebomb of the Ancients", itempool)
|
self.create_ratioed_items("Timebomb of the Ancients", itempool)
|
||||||
self.create_ratioed_items("Tome of Power", itempool)
|
self.create_ratioed_items("Tome of Power", itempool)
|
||||||
self.create_ratioed_items("Silver Shield", itempool)
|
self.create_ratioed_items("Silver Shield", itempool)
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import importlib.machinery
|
|||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from .romTables import ROMWithTables
|
from .romTables import ROMWithTables
|
||||||
from . import assembler
|
from . import assembler
|
||||||
@@ -68,14 +67,10 @@ from BaseClasses import ItemClassification
|
|||||||
from ..Locations import LinksAwakeningLocation
|
from ..Locations import LinksAwakeningLocation
|
||||||
from ..Options import TrendyGame, Palette, MusicChangeCondition, BootsControls
|
from ..Options import TrendyGame, Palette, MusicChangeCondition, BootsControls
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .. import LinksAwakeningWorld
|
|
||||||
|
|
||||||
|
|
||||||
# Function to generate a final rom, this patches the rom with all required patches
|
# Function to generate a final rom, this patches the rom with all required patches
|
||||||
def generateRom(args, world: "LinksAwakeningWorld"):
|
def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, multiworld=None, player_name=None, player_names=[], player_id = 0):
|
||||||
rom_patches = []
|
rom_patches = []
|
||||||
player_names = list(world.multiworld.player_name.values())
|
|
||||||
|
|
||||||
rom = ROMWithTables(args.input_filename, rom_patches)
|
rom = ROMWithTables(args.input_filename, rom_patches)
|
||||||
rom.player_names = player_names
|
rom.player_names = player_names
|
||||||
@@ -89,10 +84,10 @@ 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 settings.gfxmod:
|
||||||
patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", world.ladxr_settings.gfxmod))
|
patches.aesthetics.gfxMod(rom, os.path.join("data", "sprites", "ladx", settings.gfxmod))
|
||||||
|
|
||||||
item_list = [item for item in world.ladxr_logic.iteminfo_list if not isinstance(item, KeyLocation)]
|
item_list = [item for item in logic.iteminfo_list if not isinstance(item, KeyLocation)]
|
||||||
|
|
||||||
assembler.resetConsts()
|
assembler.resetConsts()
|
||||||
assembler.const("INV_SIZE", 16)
|
assembler.const("INV_SIZE", 16)
|
||||||
@@ -121,7 +116,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 settings.hardmode != "none" else 0)
|
||||||
|
|
||||||
patches.core.cleanup(rom)
|
patches.core.cleanup(rom)
|
||||||
patches.save.singleSaveSlot(rom)
|
patches.save.singleSaveSlot(rom)
|
||||||
@@ -135,7 +130,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, auth, player_id, 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)
|
||||||
@@ -146,11 +141,10 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
|
|
||||||
from ..Options import ShuffleSmallKeys, ShuffleNightmareKeys
|
from ..Options import ShuffleSmallKeys, ShuffleNightmareKeys
|
||||||
|
|
||||||
if world.options.shuffle_small_keys != ShuffleSmallKeys.option_original_dungeon or\
|
if ap_settings["shuffle_small_keys"] != ShuffleSmallKeys.option_original_dungeon or ap_settings["shuffle_nightmare_keys"] != ShuffleNightmareKeys.option_original_dungeon:
|
||||||
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 settings.witch:
|
||||||
patches.witch.updateWitch(rom)
|
patches.witch.updateWitch(rom)
|
||||||
patches.softlock.fixAll(rom)
|
patches.softlock.fixAll(rom)
|
||||||
patches.maptweaks.tweakMap(rom)
|
patches.maptweaks.tweakMap(rom)
|
||||||
@@ -164,9 +158,9 @@ 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 settings.owlstatues in ("dungeon", "both"):
|
||||||
patches.owl.upgradeDungeonOwlStatues(rom)
|
patches.owl.upgradeDungeonOwlStatues(rom)
|
||||||
if world.ladxr_settings.owlstatues in ("overworld", "both"):
|
if 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)
|
||||||
@@ -176,110 +170,106 @@ 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)
|
||||||
if world.ladxr_settings.tradequest:
|
if settings.tradequest:
|
||||||
patches.tradeSequence.patchTradeSequence(rom, world.ladxr_settings.boomerang)
|
patches.tradeSequence.patchTradeSequence(rom, settings.boomerang)
|
||||||
else:
|
else:
|
||||||
# Monkey bridge patch, always have the bridge there.
|
# Monkey bridge patch, always have the bridge there.
|
||||||
rom.patch(0x00, 0x333D, assembler.ASM("bit 4, e\njr Z, $05"), b"", fill_nop=True)
|
rom.patch(0x00, 0x333D, assembler.ASM("bit 4, e\njr Z, $05"), b"", fill_nop=True)
|
||||||
patches.bowwow.fixBowwow(rom, everywhere=world.ladxr_settings.bowwow != 'normal')
|
patches.bowwow.fixBowwow(rom, everywhere=settings.bowwow != 'normal')
|
||||||
if world.ladxr_settings.bowwow != 'normal':
|
if settings.bowwow != 'normal':
|
||||||
patches.bowwow.bowwowMapPatches(rom)
|
patches.bowwow.bowwowMapPatches(rom)
|
||||||
patches.desert.desertAccess(rom)
|
patches.desert.desertAccess(rom)
|
||||||
if world.ladxr_settings.overworld == 'dungeondive':
|
if 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 settings.overworld == 'nodungeons':
|
||||||
patches.dungeon.patchNoDungeons(rom)
|
patches.dungeon.patchNoDungeons(rom)
|
||||||
elif world.ladxr_settings.overworld == 'random':
|
elif settings.overworld == 'random':
|
||||||
patches.overworld.patchOverworldTilesets(rom)
|
patches.overworld.patchOverworldTilesets(rom)
|
||||||
mapgen.store_map(rom, world.ladxr_logic.world.map)
|
mapgen.store_map(rom, 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 ap_settings['music_change_condition'] == MusicChangeCondition.option_always:
|
||||||
patches.aesthetics.noSwordMusic(rom)
|
patches.aesthetics.noSwordMusic(rom)
|
||||||
patches.aesthetics.reduceMessageLengths(rom, world.random)
|
patches.aesthetics.reduceMessageLengths(rom, rnd)
|
||||||
patches.aesthetics.allowColorDungeonSpritesEverywhere(rom)
|
patches.aesthetics.allowColorDungeonSpritesEverywhere(rom)
|
||||||
if world.ladxr_settings.music == 'random':
|
if settings.music == 'random':
|
||||||
patches.music.randomizeMusic(rom, world.random)
|
patches.music.randomizeMusic(rom, rnd)
|
||||||
elif world.ladxr_settings.music == 'off':
|
elif settings.music == 'off':
|
||||||
patches.music.noMusic(rom)
|
patches.music.noMusic(rom)
|
||||||
if world.ladxr_settings.noflash:
|
if settings.noflash:
|
||||||
patches.aesthetics.removeFlashingLights(rom)
|
patches.aesthetics.removeFlashingLights(rom)
|
||||||
if world.ladxr_settings.hardmode == "oracle":
|
if settings.hardmode == "oracle":
|
||||||
patches.hardMode.oracleMode(rom)
|
patches.hardMode.oracleMode(rom)
|
||||||
elif world.ladxr_settings.hardmode == "hero":
|
elif settings.hardmode == "hero":
|
||||||
patches.hardMode.heroMode(rom)
|
patches.hardMode.heroMode(rom)
|
||||||
elif world.ladxr_settings.hardmode == "ohko":
|
elif settings.hardmode == "ohko":
|
||||||
patches.hardMode.oneHitKO(rom)
|
patches.hardMode.oneHitKO(rom)
|
||||||
if world.ladxr_settings.superweapons:
|
if settings.superweapons:
|
||||||
patches.weapons.patchSuperWeapons(rom)
|
patches.weapons.patchSuperWeapons(rom)
|
||||||
if world.ladxr_settings.textmode == 'fast':
|
if settings.textmode == 'fast':
|
||||||
patches.aesthetics.fastText(rom)
|
patches.aesthetics.fastText(rom)
|
||||||
if world.ladxr_settings.textmode == 'none':
|
if 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 settings.nagmessages:
|
||||||
patches.aesthetics.removeNagMessages(rom)
|
patches.aesthetics.removeNagMessages(rom)
|
||||||
if world.ladxr_settings.lowhpbeep == 'slow':
|
if settings.lowhpbeep == 'slow':
|
||||||
patches.aesthetics.slowLowHPBeep(rom)
|
patches.aesthetics.slowLowHPBeep(rom)
|
||||||
if world.ladxr_settings.lowhpbeep == 'none':
|
if settings.lowhpbeep == 'none':
|
||||||
patches.aesthetics.removeLowHPBeep(rom)
|
patches.aesthetics.removeLowHPBeep(rom)
|
||||||
if 0 <= int(world.ladxr_settings.linkspalette):
|
if 0 <= int(settings.linkspalette):
|
||||||
patches.aesthetics.forceLinksPalette(rom, int(world.ladxr_settings.linkspalette))
|
patches.aesthetics.forceLinksPalette(rom, int(settings.linkspalette))
|
||||||
if args.romdebugmode:
|
if args.romdebugmode:
|
||||||
# The default rom has this build in, just need to set a flag and we get this save.
|
# The default rom has this build in, just need to set a flag and we get this save.
|
||||||
rom.patch(0, 0x0003, "00", "01")
|
rom.patch(0, 0x0003, "00", "01")
|
||||||
|
|
||||||
# Patch the sword check on the shopkeeper turning around.
|
# Patch the sword check on the shopkeeper turning around.
|
||||||
if world.ladxr_settings.steal == 'never':
|
if settings.steal == 'never':
|
||||||
rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
|
rom.patch(4, 0x36F9, "FA4EDB", "3E0000")
|
||||||
elif world.ladxr_settings.steal == 'always':
|
elif settings.steal == 'always':
|
||||||
rom.patch(4, 0x36F9, "FA4EDB", "3E0100")
|
rom.patch(4, 0x36F9, "FA4EDB", "3E0100")
|
||||||
|
|
||||||
if world.ladxr_settings.hpmode == 'inverted':
|
if settings.hpmode == 'inverted':
|
||||||
patches.health.setStartHealth(rom, 9)
|
patches.health.setStartHealth(rom, 9)
|
||||||
elif world.ladxr_settings.hpmode == '1':
|
elif 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 settings.quickswap == 'a':
|
||||||
patches.core.quickswap(rom, 1)
|
patches.core.quickswap(rom, 1)
|
||||||
elif world.ladxr_settings.quickswap == 'b':
|
elif settings.quickswap == 'b':
|
||||||
patches.core.quickswap(rom, 0)
|
patches.core.quickswap(rom, 0)
|
||||||
|
|
||||||
patches.core.addBootsControls(rom, world.options.boots_controls)
|
patches.core.addBootsControls(rom, ap_settings['boots_controls'])
|
||||||
|
|
||||||
|
|
||||||
world_setup = world.ladxr_logic.world_setup
|
world_setup = logic.world_setup
|
||||||
|
|
||||||
JUNK_HINT = 0.33
|
JUNK_HINT = 0.33
|
||||||
RANDOM_HINT= 0.66
|
RANDOM_HINT= 0.66
|
||||||
# USEFUL_HINT = 1.0
|
# USEFUL_HINT = 1.0
|
||||||
# TODO: filter events, filter unshuffled keys
|
# TODO: filter events, filter unshuffled keys
|
||||||
all_items = world.multiworld.get_items()
|
all_items = multiworld.get_items()
|
||||||
our_items = [item for item in all_items
|
our_items = [item for item in all_items if item.player == player_id and item.location and item.code is not None and item.location.show_in_spoiler]
|
||||||
if item.player == world.player
|
|
||||||
and item.location
|
|
||||||
and item.code is not None
|
|
||||||
and item.location.show_in_spoiler]
|
|
||||||
our_useful_items = [item for item in our_items if ItemClassification.progression in item.classification]
|
our_useful_items = [item for item in our_items if ItemClassification.progression in item.classification]
|
||||||
|
|
||||||
def gen_hint():
|
def gen_hint():
|
||||||
chance = world.random.uniform(0, 1)
|
chance = rnd.uniform(0, 1)
|
||||||
if chance < JUNK_HINT:
|
if chance < JUNK_HINT:
|
||||||
return None
|
return None
|
||||||
elif chance < RANDOM_HINT:
|
elif chance < RANDOM_HINT:
|
||||||
location = world.random.choice(our_items).location
|
location = rnd.choice(our_items).location
|
||||||
else: # USEFUL_HINT
|
else: # USEFUL_HINT
|
||||||
location = world.random.choice(our_useful_items).location
|
location = rnd.choice(our_useful_items).location
|
||||||
|
|
||||||
if location.item.player == world.player:
|
if location.item.player == player_id:
|
||||||
name = "Your"
|
name = "Your"
|
||||||
else:
|
else:
|
||||||
name = f"{world.multiworld.player_name[location.item.player]}'s"
|
name = f"{multiworld.player_name[location.item.player]}'s"
|
||||||
|
|
||||||
if isinstance(location, LinksAwakeningLocation):
|
if isinstance(location, LinksAwakeningLocation):
|
||||||
location_name = location.ladxr_item.metadata.name
|
location_name = location.ladxr_item.metadata.name
|
||||||
@@ -287,8 +277,8 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
location_name = location.name
|
location_name = location.name
|
||||||
|
|
||||||
hint = f"{name} {location.item} is at {location_name}"
|
hint = f"{name} {location.item} is at {location_name}"
|
||||||
if location.player != world.player:
|
if location.player != player_id:
|
||||||
hint += f" in {world.multiworld.player_name[location.player]}'s world"
|
hint += f" in {multiworld.player_name[location.player]}'s world"
|
||||||
|
|
||||||
# Cap hint size at 85
|
# Cap hint size at 85
|
||||||
# Realistically we could go bigger but let's be safe instead
|
# Realistically we could go bigger but let's be safe instead
|
||||||
@@ -296,7 +286,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
|
|
||||||
return hint
|
return hint
|
||||||
|
|
||||||
hints.addHints(rom, world.random, gen_hint)
|
hints.addHints(rom, rnd, gen_hint)
|
||||||
|
|
||||||
if world_setup.goal == "raft":
|
if world_setup.goal == "raft":
|
||||||
patches.goal.setRaftGoal(rom)
|
patches.goal.setRaftGoal(rom)
|
||||||
@@ -309,7 +299,7 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
|
|
||||||
# Patch the generated logic into the rom
|
# Patch the generated logic into the rom
|
||||||
patches.chest.setMultiChest(rom, world_setup.multichest)
|
patches.chest.setMultiChest(rom, world_setup.multichest)
|
||||||
if world.ladxr_settings.overworld not in {"dungeondive", "random"}:
|
if settings.overworld not in {"dungeondive", "random"}:
|
||||||
patches.entrances.changeEntrances(rom, world_setup.entrance_mapping)
|
patches.entrances.changeEntrances(rom, world_setup.entrance_mapping)
|
||||||
for spot in item_list:
|
for spot in item_list:
|
||||||
if spot.item and spot.item.startswith("*"):
|
if spot.item and spot.item.startswith("*"):
|
||||||
@@ -328,16 +318,15 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
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, auth, seed_name, settings, player_name, player_id)
|
||||||
world.player_name, world.player)
|
if ap_settings["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 ap_settings["text_shuffle"]:
|
||||||
buckets = defaultdict(list)
|
buckets = defaultdict(list)
|
||||||
# For each ROM bank, shuffle text within the bank
|
# For each ROM bank, shuffle text within the bank
|
||||||
for n, data in enumerate(rom.texts._PointerTable__data):
|
for n, data in enumerate(rom.texts._PointerTable__data):
|
||||||
@@ -347,20 +336,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)
|
rnd.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 ap_settings["trendy_game"] != TrendyGame.option_normal:
|
||||||
|
|
||||||
# TODO: if 0 or 4, 5, remove inaccurate conveyor tiles
|
# TODO: if 0 or 4, 5, remove inaccurate conveyor tiles
|
||||||
|
|
||||||
|
|
||||||
room_editor = RoomEditor(rom, 0x2A0)
|
room_editor = RoomEditor(rom, 0x2A0)
|
||||||
|
|
||||||
if world.options.trendy_game == TrendyGame.option_easy:
|
if ap_settings["trendy_game"] == TrendyGame.option_easy:
|
||||||
# Set physics flag on all objects
|
# Set physics flag on all objects
|
||||||
for i in range(0, 6):
|
for i in range(0, 6):
|
||||||
rom.banks[0x4][0x6F1E + i -0x4000] = 0x4
|
rom.banks[0x4][0x6F1E + i -0x4000] = 0x4
|
||||||
@@ -371,7 +360,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 int(ap_settings["trendy_game"]) >= TrendyGame.option_harder:
|
||||||
"""
|
"""
|
||||||
Data_004_76A0::
|
Data_004_76A0::
|
||||||
db $FC, $00, $04, $00, $00
|
db $FC, $00, $04, $00, $00
|
||||||
@@ -385,12 +374,12 @@ def generateRom(args, world: "LinksAwakeningWorld"):
|
|||||||
TrendyGame.option_impossible: (3, 16),
|
TrendyGame.option_impossible: (3, 16),
|
||||||
}
|
}
|
||||||
def speed():
|
def speed():
|
||||||
return world.random.randint(*speeds[world.options.trendy_game])
|
return rnd.randint(*speeds[ap_settings["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 int(ap_settings["trendy_game"]) >= TrendyGame.option_hardest:
|
||||||
rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed()
|
rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed()
|
||||||
rom.banks[0x4][0x76A3-0x4000] = speed()
|
rom.banks[0x4][0x76A3-0x4000] = speed()
|
||||||
rom.banks[0x4][0x76A5-0x4000] = speed()
|
rom.banks[0x4][0x76A5-0x4000] = speed()
|
||||||
@@ -414,10 +403,10 @@ 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.warp_improvements:
|
if ap_settings["warp_improvements"]:
|
||||||
patches.core.addWarpImprovements(rom, world.options.additional_warp_points)
|
patches.core.addWarpImprovements(rom, ap_settings["additional_warp_points"])
|
||||||
|
|
||||||
palette = world.options.palette
|
palette = ap_settings["palette"]
|
||||||
if palette != Palette.option_normal:
|
if palette != Palette.option_normal:
|
||||||
ranges = {
|
ranges = {
|
||||||
# Object palettes
|
# Object palettes
|
||||||
@@ -483,8 +472,8 @@ 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(auth) == 12)
|
||||||
rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(world.multi_key))
|
rom.patch(0x00, SEED_LOCATION, None, binascii.hexlify(auth))
|
||||||
|
|
||||||
for pymod in pymods:
|
for pymod in pymods:
|
||||||
pymod.postPatch(rom)
|
pymod.postPatch(rom)
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
import os.path
|
import os.path
|
||||||
import typing
|
import typing
|
||||||
import logging
|
import logging
|
||||||
from Options import Choice, Toggle, DefaultOnToggle, Range, FreeText, PerGameCommonOptions, OptionGroup
|
from Options import Choice, Option, Toggle, DefaultOnToggle, Range, FreeText
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import Utils
|
import Utils
|
||||||
|
|
||||||
@@ -16,7 +14,7 @@ class LADXROption:
|
|||||||
def to_ladxr_option(self, all_options):
|
def to_ladxr_option(self, all_options):
|
||||||
if not self.ladxr_name:
|
if not self.ladxr_name:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
return (self.ladxr_name, self.name_lookup[self.value].replace("_", ""))
|
return (self.ladxr_name, self.name_lookup[self.value].replace("_", ""))
|
||||||
|
|
||||||
|
|
||||||
@@ -34,10 +32,9 @@ class Logic(Choice, LADXROption):
|
|||||||
option_hard = 2
|
option_hard = 2
|
||||||
option_glitched = 3
|
option_glitched = 3
|
||||||
option_hell = 4
|
option_hell = 4
|
||||||
|
|
||||||
default = option_normal
|
default = option_normal
|
||||||
|
|
||||||
|
|
||||||
class TradeQuest(DefaultOffToggle, LADXROption):
|
class TradeQuest(DefaultOffToggle, LADXROption):
|
||||||
"""
|
"""
|
||||||
[On] adds the trade items to the pool (the trade locations will always be local items)
|
[On] adds the trade items to the pool (the trade locations will always be local items)
|
||||||
@@ -46,14 +43,12 @@ class TradeQuest(DefaultOffToggle, LADXROption):
|
|||||||
display_name = "Trade Quest"
|
display_name = "Trade Quest"
|
||||||
ladxr_name = "tradequest"
|
ladxr_name = "tradequest"
|
||||||
|
|
||||||
|
|
||||||
class TextShuffle(DefaultOffToggle):
|
class TextShuffle(DefaultOffToggle):
|
||||||
"""
|
"""
|
||||||
[On] Shuffles all the text in the game
|
[On] Shuffles all the text in the game
|
||||||
[Off] (default) doesn't shuffle them.
|
[Off] (default) doesn't shuffle them.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Rooster(DefaultOnToggle, LADXROption):
|
class Rooster(DefaultOnToggle, LADXROption):
|
||||||
"""
|
"""
|
||||||
[On] Adds the rooster to the item pool.
|
[On] Adds the rooster to the item pool.
|
||||||
@@ -62,7 +57,6 @@ class Rooster(DefaultOnToggle, LADXROption):
|
|||||||
display_name = "Rooster"
|
display_name = "Rooster"
|
||||||
ladxr_name = "rooster"
|
ladxr_name = "rooster"
|
||||||
|
|
||||||
|
|
||||||
class Boomerang(Choice):
|
class Boomerang(Choice):
|
||||||
"""
|
"""
|
||||||
[Normal] requires Magnifying Lens to get the boomerang.
|
[Normal] requires Magnifying Lens to get the boomerang.
|
||||||
@@ -73,7 +67,6 @@ class Boomerang(Choice):
|
|||||||
gift = 1
|
gift = 1
|
||||||
default = gift
|
default = gift
|
||||||
|
|
||||||
|
|
||||||
class EntranceShuffle(Choice, LADXROption):
|
class EntranceShuffle(Choice, LADXROption):
|
||||||
"""
|
"""
|
||||||
[WARNING] Experimental, may fail to fill
|
[WARNING] Experimental, may fail to fill
|
||||||
@@ -82,20 +75,19 @@ class EntranceShuffle(Choice, LADXROption):
|
|||||||
If random start location and/or dungeon shuffle is enabled, then these will be shuffled with all the non-connector entrance pool.
|
If random start location and/or dungeon shuffle is enabled, then these will be shuffled with all the non-connector entrance pool.
|
||||||
Note, some entrances can lead into water, use the warp-to-home from the save&quit menu to escape this."""
|
Note, some entrances can lead into water, use the warp-to-home from the save&quit menu to escape this."""
|
||||||
|
|
||||||
# [Advanced] Simple, but two-way connector caves are shuffled in their own pool as well.
|
#[Advanced] Simple, but two-way connector caves are shuffled in their own pool as well.
|
||||||
# [Expert] Advanced, but caves/houses without items are also shuffled into the Simple entrance pool.
|
#[Expert] Advanced, but caves/houses without items are also shuffled into the Simple entrance pool.
|
||||||
# [Insanity] Expert, but the Raft Minigame hut and Mamu's cave are added to the non-connector pool.
|
#[Insanity] Expert, but the Raft Minigame hut and Mamu's cave are added to the non-connector pool.
|
||||||
|
|
||||||
option_none = 0
|
option_none = 0
|
||||||
option_simple = 1
|
option_simple = 1
|
||||||
# option_advanced = 2
|
#option_advanced = 2
|
||||||
# option_expert = 3
|
#option_expert = 3
|
||||||
# option_insanity = 4
|
#option_insanity = 4
|
||||||
default = option_none
|
default = option_none
|
||||||
display_name = "Experimental Entrance Shuffle"
|
display_name = "Experimental Entrance Shuffle"
|
||||||
ladxr_name = "entranceshuffle"
|
ladxr_name = "entranceshuffle"
|
||||||
|
|
||||||
|
|
||||||
class DungeonShuffle(DefaultOffToggle, LADXROption):
|
class DungeonShuffle(DefaultOffToggle, LADXROption):
|
||||||
"""
|
"""
|
||||||
[WARNING] Experimental, may fail to fill
|
[WARNING] Experimental, may fail to fill
|
||||||
@@ -104,14 +96,12 @@ class DungeonShuffle(DefaultOffToggle, LADXROption):
|
|||||||
display_name = "Experimental Dungeon Shuffle"
|
display_name = "Experimental Dungeon Shuffle"
|
||||||
ladxr_name = "dungeonshuffle"
|
ladxr_name = "dungeonshuffle"
|
||||||
|
|
||||||
|
|
||||||
class APTitleScreen(DefaultOnToggle):
|
class APTitleScreen(DefaultOnToggle):
|
||||||
"""
|
"""
|
||||||
Enables AP specific title screen and disables the intro cutscene
|
Enables AP specific title screen and disables the intro cutscene
|
||||||
"""
|
"""
|
||||||
display_name = "AP Title Screen"
|
display_name = "AP Title Screen"
|
||||||
|
|
||||||
|
|
||||||
class BossShuffle(Choice):
|
class BossShuffle(Choice):
|
||||||
none = 0
|
none = 0
|
||||||
shuffle = 1
|
shuffle = 1
|
||||||
@@ -125,12 +115,10 @@ class DungeonItemShuffle(Choice):
|
|||||||
option_own_world = 2
|
option_own_world = 2
|
||||||
option_any_world = 3
|
option_any_world = 3
|
||||||
option_different_world = 4
|
option_different_world = 4
|
||||||
# option_delete = 5
|
#option_delete = 5
|
||||||
# option_start_with = 6
|
#option_start_with = 6
|
||||||
alias_true = 3
|
alias_true = 3
|
||||||
alias_false = 0
|
alias_false = 0
|
||||||
ladxr_item: str
|
|
||||||
|
|
||||||
|
|
||||||
class ShuffleNightmareKeys(DungeonItemShuffle):
|
class ShuffleNightmareKeys(DungeonItemShuffle):
|
||||||
"""
|
"""
|
||||||
@@ -144,7 +132,6 @@ class ShuffleNightmareKeys(DungeonItemShuffle):
|
|||||||
display_name = "Shuffle Nightmare Keys"
|
display_name = "Shuffle Nightmare Keys"
|
||||||
ladxr_item = "NIGHTMARE_KEY"
|
ladxr_item = "NIGHTMARE_KEY"
|
||||||
|
|
||||||
|
|
||||||
class ShuffleSmallKeys(DungeonItemShuffle):
|
class ShuffleSmallKeys(DungeonItemShuffle):
|
||||||
"""
|
"""
|
||||||
Shuffle Small Keys
|
Shuffle Small Keys
|
||||||
@@ -156,8 +143,6 @@ class ShuffleSmallKeys(DungeonItemShuffle):
|
|||||||
"""
|
"""
|
||||||
display_name = "Shuffle Small Keys"
|
display_name = "Shuffle Small Keys"
|
||||||
ladxr_item = "KEY"
|
ladxr_item = "KEY"
|
||||||
|
|
||||||
|
|
||||||
class ShuffleMaps(DungeonItemShuffle):
|
class ShuffleMaps(DungeonItemShuffle):
|
||||||
"""
|
"""
|
||||||
Shuffle Dungeon Maps
|
Shuffle Dungeon Maps
|
||||||
@@ -170,7 +155,6 @@ class ShuffleMaps(DungeonItemShuffle):
|
|||||||
display_name = "Shuffle Maps"
|
display_name = "Shuffle Maps"
|
||||||
ladxr_item = "MAP"
|
ladxr_item = "MAP"
|
||||||
|
|
||||||
|
|
||||||
class ShuffleCompasses(DungeonItemShuffle):
|
class ShuffleCompasses(DungeonItemShuffle):
|
||||||
"""
|
"""
|
||||||
Shuffle Dungeon Compasses
|
Shuffle Dungeon Compasses
|
||||||
@@ -183,7 +167,6 @@ class ShuffleCompasses(DungeonItemShuffle):
|
|||||||
display_name = "Shuffle Compasses"
|
display_name = "Shuffle Compasses"
|
||||||
ladxr_item = "COMPASS"
|
ladxr_item = "COMPASS"
|
||||||
|
|
||||||
|
|
||||||
class ShuffleStoneBeaks(DungeonItemShuffle):
|
class ShuffleStoneBeaks(DungeonItemShuffle):
|
||||||
"""
|
"""
|
||||||
Shuffle Owl Beaks
|
Shuffle Owl Beaks
|
||||||
@@ -196,7 +179,6 @@ class ShuffleStoneBeaks(DungeonItemShuffle):
|
|||||||
display_name = "Shuffle Stone Beaks"
|
display_name = "Shuffle Stone Beaks"
|
||||||
ladxr_item = "STONE_BEAK"
|
ladxr_item = "STONE_BEAK"
|
||||||
|
|
||||||
|
|
||||||
class ShuffleInstruments(DungeonItemShuffle):
|
class ShuffleInstruments(DungeonItemShuffle):
|
||||||
"""
|
"""
|
||||||
Shuffle Instruments
|
Shuffle Instruments
|
||||||
@@ -213,7 +195,6 @@ class ShuffleInstruments(DungeonItemShuffle):
|
|||||||
option_vanilla = 100
|
option_vanilla = 100
|
||||||
alias_false = 100
|
alias_false = 100
|
||||||
|
|
||||||
|
|
||||||
class Goal(Choice, LADXROption):
|
class Goal(Choice, LADXROption):
|
||||||
"""
|
"""
|
||||||
The Goal of the game
|
The Goal of the game
|
||||||
@@ -226,7 +207,7 @@ class Goal(Choice, LADXROption):
|
|||||||
option_instruments = 1
|
option_instruments = 1
|
||||||
option_seashells = 2
|
option_seashells = 2
|
||||||
option_open = 3
|
option_open = 3
|
||||||
|
|
||||||
default = option_instruments
|
default = option_instruments
|
||||||
|
|
||||||
def to_ladxr_option(self, all_options):
|
def to_ladxr_option(self, all_options):
|
||||||
@@ -235,7 +216,6 @@ class Goal(Choice, LADXROption):
|
|||||||
else:
|
else:
|
||||||
return LADXROption.to_ladxr_option(self, all_options)
|
return LADXROption.to_ladxr_option(self, all_options)
|
||||||
|
|
||||||
|
|
||||||
class InstrumentCount(Range, LADXROption):
|
class InstrumentCount(Range, LADXROption):
|
||||||
"""
|
"""
|
||||||
Sets the number of instruments required to open the Egg
|
Sets the number of instruments required to open the Egg
|
||||||
@@ -246,7 +226,6 @@ class InstrumentCount(Range, LADXROption):
|
|||||||
range_end = 8
|
range_end = 8
|
||||||
default = 8
|
default = 8
|
||||||
|
|
||||||
|
|
||||||
class NagMessages(DefaultOffToggle, LADXROption):
|
class NagMessages(DefaultOffToggle, LADXROption):
|
||||||
"""
|
"""
|
||||||
Controls if nag messages are shown when rocks and crystals are touched. Useful for glitches, annoying for everyone else.
|
Controls if nag messages are shown when rocks and crystals are touched. Useful for glitches, annoying for everyone else.
|
||||||
@@ -254,7 +233,6 @@ class NagMessages(DefaultOffToggle, LADXROption):
|
|||||||
display_name = "Nag Messages"
|
display_name = "Nag Messages"
|
||||||
ladxr_name = "nagmessages"
|
ladxr_name = "nagmessages"
|
||||||
|
|
||||||
|
|
||||||
class MusicChangeCondition(Choice):
|
class MusicChangeCondition(Choice):
|
||||||
"""
|
"""
|
||||||
Controls how the music changes.
|
Controls how the music changes.
|
||||||
@@ -265,8 +243,6 @@ class MusicChangeCondition(Choice):
|
|||||||
option_sword = 0
|
option_sword = 0
|
||||||
option_always = 1
|
option_always = 1
|
||||||
default = option_always
|
default = option_always
|
||||||
|
|
||||||
|
|
||||||
# Setting('hpmode', 'Gameplay', 'm', 'Health mode', options=[('default', '', 'Normal'), ('inverted', 'i', 'Inverted'), ('1', '1', 'Start with 1 heart'), ('low', 'l', 'Low max')], default='default',
|
# Setting('hpmode', 'Gameplay', 'm', 'Health mode', options=[('default', '', 'Normal'), ('inverted', 'i', 'Inverted'), ('1', '1', 'Start with 1 heart'), ('low', 'l', 'Low max')], default='default',
|
||||||
# description="""
|
# description="""
|
||||||
# [Normal} health works as you would expect.
|
# [Normal} health works as you would expect.
|
||||||
@@ -295,7 +271,6 @@ class Bowwow(Choice):
|
|||||||
swordless = 1
|
swordless = 1
|
||||||
default = normal
|
default = normal
|
||||||
|
|
||||||
|
|
||||||
class Overworld(Choice, LADXROption):
|
class Overworld(Choice, LADXROption):
|
||||||
"""
|
"""
|
||||||
[Dungeon Dive] Create a different overworld where all the dungeons are directly accessible and almost no chests are located in the overworld.
|
[Dungeon Dive] Create a different overworld where all the dungeons are directly accessible and almost no chests are located in the overworld.
|
||||||
@@ -309,10 +284,9 @@ class Overworld(Choice, LADXROption):
|
|||||||
# option_shuffled = 3
|
# option_shuffled = 3
|
||||||
default = option_normal
|
default = option_normal
|
||||||
|
|
||||||
|
#Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False,
|
||||||
# Setting('superweapons', 'Special', 'q', 'Enable super weapons', default=False,
|
|
||||||
# description='All items will be more powerful, faster, harder, bigger stronger. You name it.'),
|
# description='All items will be more powerful, faster, harder, bigger stronger. You name it.'),
|
||||||
# Setting('quickswap', 'User options', 'Q', 'Quickswap', options=[('none', '', 'Disabled'), ('a', 'a', 'Swap A button'), ('b', 'b', 'Swap B button')], default='none',
|
#Setting('quickswap', 'User options', 'Q', 'Quickswap', options=[('none', '', 'Disabled'), ('a', 'a', 'Swap A button'), ('b', 'b', 'Swap B button')], default='none',
|
||||||
# description='Adds that the select button swaps with either A or B. The item is swapped with the top inventory slot. The map is not available when quickswap is enabled.',
|
# description='Adds that the select button swaps with either A or B. The item is swapped with the top inventory slot. The map is not available when quickswap is enabled.',
|
||||||
# aesthetic=True),
|
# aesthetic=True),
|
||||||
# Setting('textmode', 'User options', 'f', 'Text mode', options=[('fast', '', 'Fast'), ('default', 'd', 'Normal'), ('none', 'n', 'No-text')], default='fast',
|
# Setting('textmode', 'User options', 'f', 'Text mode', options=[('fast', '', 'Fast'), ('default', 'd', 'Normal'), ('none', 'n', 'No-text')], default='fast',
|
||||||
@@ -355,7 +329,7 @@ class BootsControls(Choice):
|
|||||||
option_bracelet = 1
|
option_bracelet = 1
|
||||||
option_press_a = 2
|
option_press_a = 2
|
||||||
option_press_b = 3
|
option_press_b = 3
|
||||||
|
|
||||||
|
|
||||||
class LinkPalette(Choice, LADXROption):
|
class LinkPalette(Choice, LADXROption):
|
||||||
"""
|
"""
|
||||||
@@ -378,7 +352,6 @@ class LinkPalette(Choice, LADXROption):
|
|||||||
def to_ladxr_option(self, all_options):
|
def to_ladxr_option(self, all_options):
|
||||||
return self.ladxr_name, str(self.value)
|
return self.ladxr_name, str(self.value)
|
||||||
|
|
||||||
|
|
||||||
class TrendyGame(Choice):
|
class TrendyGame(Choice):
|
||||||
"""
|
"""
|
||||||
[Easy] All of the items hold still for you
|
[Easy] All of the items hold still for you
|
||||||
@@ -397,7 +370,6 @@ class TrendyGame(Choice):
|
|||||||
option_impossible = 5
|
option_impossible = 5
|
||||||
default = option_normal
|
default = option_normal
|
||||||
|
|
||||||
|
|
||||||
class GfxMod(FreeText, LADXROption):
|
class GfxMod(FreeText, LADXROption):
|
||||||
"""
|
"""
|
||||||
Sets the sprite for link, among other things
|
Sets the sprite for link, among other things
|
||||||
@@ -408,7 +380,7 @@ class GfxMod(FreeText, LADXROption):
|
|||||||
normal = ''
|
normal = ''
|
||||||
default = 'Link'
|
default = 'Link'
|
||||||
|
|
||||||
__spriteDir: str = Utils.local_path(os.path.join('data', 'sprites', 'ladx'))
|
__spriteDir: str = Utils.local_path(os.path.join('data', 'sprites','ladx'))
|
||||||
__spriteFiles: typing.DefaultDict[str, typing.List[str]] = defaultdict(list)
|
__spriteFiles: typing.DefaultDict[str, typing.List[str]] = defaultdict(list)
|
||||||
|
|
||||||
extensions = [".bin", ".bdiff", ".png", ".bmp"]
|
extensions = [".bin", ".bdiff", ".png", ".bmp"]
|
||||||
@@ -417,15 +389,16 @@ class GfxMod(FreeText, LADXROption):
|
|||||||
name, extension = os.path.splitext(file)
|
name, extension = os.path.splitext(file)
|
||||||
if extension in extensions:
|
if extension in extensions:
|
||||||
__spriteFiles[name].append(file)
|
__spriteFiles[name].append(file)
|
||||||
|
|
||||||
def __init__(self, value: str):
|
def __init__(self, value: str):
|
||||||
super().__init__(value)
|
super().__init__(value)
|
||||||
|
|
||||||
|
|
||||||
def verify(self, world, player_name: str, plando_options) -> None:
|
def verify(self, world, player_name: str, plando_options) -> None:
|
||||||
if self.value == "Link" or self.value in GfxMod.__spriteFiles:
|
if self.value == "Link" or self.value in GfxMod.__spriteFiles:
|
||||||
return
|
return
|
||||||
raise Exception(
|
raise Exception(f"LADX Sprite '{self.value}' not found. Possible sprites are: {['Link'] + list(GfxMod.__spriteFiles.keys())}")
|
||||||
f"LADX Sprite '{self.value}' not found. Possible sprites are: {['Link'] + list(GfxMod.__spriteFiles.keys())}")
|
|
||||||
|
|
||||||
def to_ladxr_option(self, all_options):
|
def to_ladxr_option(self, all_options):
|
||||||
if self.value == -1 or self.value == "Link":
|
if self.value == -1 or self.value == "Link":
|
||||||
@@ -434,12 +407,10 @@ class GfxMod(FreeText, LADXROption):
|
|||||||
assert self.value in GfxMod.__spriteFiles
|
assert self.value in GfxMod.__spriteFiles
|
||||||
|
|
||||||
if len(GfxMod.__spriteFiles[self.value]) > 1:
|
if len(GfxMod.__spriteFiles[self.value]) > 1:
|
||||||
logger.warning(
|
logger.warning(f"{self.value} does not uniquely identify a file. Possible matches: {GfxMod.__spriteFiles[self.value]}. Using {GfxMod.__spriteFiles[self.value][0]}")
|
||||||
f"{self.value} does not uniquely identify a file. Possible matches: {GfxMod.__spriteFiles[self.value]}. Using {GfxMod.__spriteFiles[self.value][0]}")
|
|
||||||
|
|
||||||
return self.ladxr_name, self.__spriteDir + "/" + GfxMod.__spriteFiles[self.value][0]
|
return self.ladxr_name, self.__spriteDir + "/" + GfxMod.__spriteFiles[self.value][0]
|
||||||
|
|
||||||
|
|
||||||
class Palette(Choice):
|
class Palette(Choice):
|
||||||
"""
|
"""
|
||||||
Sets the palette for the game.
|
Sets the palette for the game.
|
||||||
@@ -459,7 +430,6 @@ class Palette(Choice):
|
|||||||
option_pink = 4
|
option_pink = 4
|
||||||
option_inverted = 5
|
option_inverted = 5
|
||||||
|
|
||||||
|
|
||||||
class Music(Choice, LADXROption):
|
class Music(Choice, LADXROption):
|
||||||
"""
|
"""
|
||||||
[Vanilla] Regular Music
|
[Vanilla] Regular Music
|
||||||
@@ -471,6 +441,7 @@ class Music(Choice, LADXROption):
|
|||||||
option_shuffled = 1
|
option_shuffled = 1
|
||||||
option_off = 2
|
option_off = 2
|
||||||
|
|
||||||
|
|
||||||
def to_ladxr_option(self, all_options):
|
def to_ladxr_option(self, all_options):
|
||||||
s = ""
|
s = ""
|
||||||
if self.value == self.option_shuffled:
|
if self.value == self.option_shuffled:
|
||||||
@@ -479,95 +450,55 @@ class Music(Choice, LADXROption):
|
|||||||
s = "off"
|
s = "off"
|
||||||
return self.ladxr_name, s
|
return self.ladxr_name, s
|
||||||
|
|
||||||
|
|
||||||
class WarpImprovements(DefaultOffToggle):
|
class WarpImprovements(DefaultOffToggle):
|
||||||
"""
|
"""
|
||||||
[On] Adds remake style warp screen to the game. Choose your warp destination on the map after jumping in a portal and press B to select.
|
[On] Adds remake style warp screen to the game. Choose your warp destination on the map after jumping in a portal and press B to select.
|
||||||
[Off] No change
|
[Off] No change
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class AdditionalWarpPoints(DefaultOffToggle):
|
class AdditionalWarpPoints(DefaultOffToggle):
|
||||||
"""
|
"""
|
||||||
[On] (requires warp improvements) Adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower
|
[On] (requires warp improvements) Adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower
|
||||||
[Off] No change
|
[Off] No change
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
ladx_option_groups = [
|
links_awakening_options: typing.Dict[str, typing.Type[Option]] = {
|
||||||
OptionGroup("Goal Options", [
|
'logic': Logic,
|
||||||
Goal,
|
|
||||||
InstrumentCount,
|
|
||||||
]),
|
|
||||||
OptionGroup("Shuffles", [
|
|
||||||
ShuffleInstruments,
|
|
||||||
ShuffleNightmareKeys,
|
|
||||||
ShuffleSmallKeys,
|
|
||||||
ShuffleMaps,
|
|
||||||
ShuffleCompasses,
|
|
||||||
ShuffleStoneBeaks
|
|
||||||
]),
|
|
||||||
OptionGroup("Warp Points", [
|
|
||||||
WarpImprovements,
|
|
||||||
AdditionalWarpPoints,
|
|
||||||
]),
|
|
||||||
OptionGroup("Miscellaneous", [
|
|
||||||
TradeQuest,
|
|
||||||
Rooster,
|
|
||||||
TrendyGame,
|
|
||||||
NagMessages,
|
|
||||||
BootsControls
|
|
||||||
]),
|
|
||||||
OptionGroup("Experimental", [
|
|
||||||
DungeonShuffle,
|
|
||||||
EntranceShuffle
|
|
||||||
]),
|
|
||||||
OptionGroup("Visuals & Sound", [
|
|
||||||
LinkPalette,
|
|
||||||
Palette,
|
|
||||||
TextShuffle,
|
|
||||||
APTitleScreen,
|
|
||||||
GfxMod,
|
|
||||||
Music,
|
|
||||||
MusicChangeCondition
|
|
||||||
])
|
|
||||||
]
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LinksAwakeningOptions(PerGameCommonOptions):
|
|
||||||
logic: Logic
|
|
||||||
# 'heartpiece': DefaultOnToggle, # description='Includes heart pieces in the item pool'),
|
# 'heartpiece': DefaultOnToggle, # description='Includes heart pieces in the item pool'),
|
||||||
# 'seashells': DefaultOnToggle, # description='Randomizes the secret sea shells hiding in the ground/trees. (chest are always randomized)'),
|
# 'seashells': DefaultOnToggle, # description='Randomizes the secret sea shells hiding in the ground/trees. (chest are always randomized)'),
|
||||||
# 'heartcontainers': DefaultOnToggle, # description='Includes boss heart container drops in the item pool'),
|
# 'heartcontainers': DefaultOnToggle, # description='Includes boss heart container drops in the item pool'),
|
||||||
# 'instruments': DefaultOffToggle, # description='Instruments are placed on random locations, dungeon goal will just contain a random item.'),
|
# 'instruments': DefaultOffToggle, # description='Instruments are placed on random locations, dungeon goal will just contain a random item.'),
|
||||||
tradequest: TradeQuest # description='Trade quest items are randomized, each NPC takes its normal trade quest item, but gives a random item'),
|
'tradequest': TradeQuest, # description='Trade quest items are randomized, each NPC takes its normal trade quest item, but gives a random item'),
|
||||||
# 'witch': DefaultOnToggle, # description='Adds both the toadstool and the reward for giving the toadstool to the witch to the item pool'),
|
# 'witch': DefaultOnToggle, # description='Adds both the toadstool and the reward for giving the toadstool to the witch to the item pool'),
|
||||||
rooster: Rooster # description='Adds the rooster to the item pool. Without this option, the rooster spot is still a check giving an item. But you will never find the rooster. Any rooster spot is accessible without rooster by other means.'),
|
'rooster': Rooster, # description='Adds the rooster to the item pool. Without this option, the rooster spot is still a check giving an item. But you will never find the rooster. Any rooster spot is accessible without rooster by other means.'),
|
||||||
# 'boomerang': Boomerang,
|
# 'boomerang': Boomerang,
|
||||||
# 'randomstartlocation': DefaultOffToggle, # 'Randomize where your starting house is located'),
|
# 'randomstartlocation': DefaultOffToggle, # 'Randomize where your starting house is located'),
|
||||||
experimental_dungeon_shuffle: DungeonShuffle # 'Randomizes the dungeon that each dungeon entrance leads to'),
|
'experimental_dungeon_shuffle': DungeonShuffle, # 'Randomizes the dungeon that each dungeon entrance leads to'),
|
||||||
experimental_entrance_shuffle: EntranceShuffle
|
'experimental_entrance_shuffle': EntranceShuffle,
|
||||||
# 'bossshuffle': BossShuffle,
|
# 'bossshuffle': BossShuffle,
|
||||||
# 'minibossshuffle': BossShuffle,
|
# 'minibossshuffle': BossShuffle,
|
||||||
goal: Goal
|
'goal': Goal,
|
||||||
instrument_count: InstrumentCount
|
'instrument_count': InstrumentCount,
|
||||||
# 'itempool': ItemPool,
|
# 'itempool': ItemPool,
|
||||||
# 'bowwow': Bowwow,
|
# 'bowwow': Bowwow,
|
||||||
# 'overworld': Overworld,
|
# 'overworld': Overworld,
|
||||||
link_palette: LinkPalette
|
'link_palette': LinkPalette,
|
||||||
warp_improvements: WarpImprovements
|
'warp_improvements': WarpImprovements,
|
||||||
additional_warp_points: AdditionalWarpPoints
|
'additional_warp_points': AdditionalWarpPoints,
|
||||||
trendy_game: TrendyGame
|
'trendy_game': TrendyGame,
|
||||||
gfxmod: GfxMod
|
'gfxmod': GfxMod,
|
||||||
palette: Palette
|
'palette': Palette,
|
||||||
text_shuffle: TextShuffle
|
'text_shuffle': TextShuffle,
|
||||||
shuffle_nightmare_keys: ShuffleNightmareKeys
|
'shuffle_nightmare_keys': ShuffleNightmareKeys,
|
||||||
shuffle_small_keys: ShuffleSmallKeys
|
'shuffle_small_keys': ShuffleSmallKeys,
|
||||||
shuffle_maps: ShuffleMaps
|
'shuffle_maps': ShuffleMaps,
|
||||||
shuffle_compasses: ShuffleCompasses
|
'shuffle_compasses': ShuffleCompasses,
|
||||||
shuffle_stone_beaks: ShuffleStoneBeaks
|
'shuffle_stone_beaks': ShuffleStoneBeaks,
|
||||||
music: Music
|
'music': Music,
|
||||||
shuffle_instruments: ShuffleInstruments
|
'shuffle_instruments': ShuffleInstruments,
|
||||||
music_change_condition: MusicChangeCondition
|
'music_change_condition': MusicChangeCondition,
|
||||||
nag_messages: NagMessages
|
'nag_messages': NagMessages,
|
||||||
ap_title_screen: APTitleScreen
|
'ap_title_screen': APTitleScreen,
|
||||||
boots_controls: BootsControls
|
'boots_controls': BootsControls,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import settings
|
|
||||||
import worlds.Files
|
import worlds.Files
|
||||||
import hashlib
|
import hashlib
|
||||||
import Utils
|
import Utils
|
||||||
@@ -32,7 +32,7 @@ def get_base_rom_bytes(file_name: str = "") -> bytes:
|
|||||||
|
|
||||||
|
|
||||||
def get_base_rom_path(file_name: str = "") -> str:
|
def get_base_rom_path(file_name: str = "") -> str:
|
||||||
options = settings.get_settings()
|
options = Utils.get_options()
|
||||||
if not file_name:
|
if not file_name:
|
||||||
file_name = options["ladx_options"]["rom_file"]
|
file_name = options["ladx_options"]["rom_file"]
|
||||||
if not os.path.exists(file_name):
|
if not os.path.exists(file_name):
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import binascii
|
import binascii
|
||||||
import dataclasses
|
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -18,13 +17,13 @@ from .LADXR import generator
|
|||||||
from .LADXR.itempool import ItemPool as LADXRItemPool
|
from .LADXR.itempool import ItemPool as LADXRItemPool
|
||||||
from .LADXR.locations.constants import CHEST_ITEMS
|
from .LADXR.locations.constants import CHEST_ITEMS
|
||||||
from .LADXR.locations.instrument import Instrument
|
from .LADXR.locations.instrument import Instrument
|
||||||
from .LADXR.logic import Logic as LADXRLogic
|
from .LADXR.logic import Logic as LAXDRLogic
|
||||||
from .LADXR.main import get_parser
|
from .LADXR.main import get_parser
|
||||||
from .LADXR.settings import Settings as LADXRSettings
|
from .LADXR.settings import Settings as LADXRSettings
|
||||||
from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup
|
from .LADXR.worldSetup import WorldSetup as LADXRWorldSetup
|
||||||
from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion,
|
from .Locations import (LinksAwakeningLocation, LinksAwakeningRegion,
|
||||||
create_regions_from_ladxr, get_locations_to_id)
|
create_regions_from_ladxr, get_locations_to_id)
|
||||||
from .Options import DungeonItemShuffle, ShuffleInstruments, LinksAwakeningOptions, ladx_option_groups
|
from .Options import DungeonItemShuffle, links_awakening_options, ShuffleInstruments
|
||||||
from .Rom import LADXDeltaPatch, get_base_rom_path
|
from .Rom import LADXDeltaPatch, get_base_rom_path
|
||||||
|
|
||||||
DEVELOPER_MODE = False
|
DEVELOPER_MODE = False
|
||||||
@@ -65,7 +64,7 @@ class LinksAwakeningWebWorld(WebWorld):
|
|||||||
["zig"]
|
["zig"]
|
||||||
)]
|
)]
|
||||||
theme = "dirt"
|
theme = "dirt"
|
||||||
option_groups = ladx_option_groups
|
|
||||||
|
|
||||||
class LinksAwakeningWorld(World):
|
class LinksAwakeningWorld(World):
|
||||||
"""
|
"""
|
||||||
@@ -74,9 +73,8 @@ class LinksAwakeningWorld(World):
|
|||||||
"""
|
"""
|
||||||
game = LINKS_AWAKENING # name of the game/world
|
game = LINKS_AWAKENING # name of the game/world
|
||||||
web = LinksAwakeningWebWorld()
|
web = LinksAwakeningWebWorld()
|
||||||
|
|
||||||
options_dataclass = LinksAwakeningOptions
|
option_definitions = links_awakening_options # options the player can set
|
||||||
options: LinksAwakeningOptions
|
|
||||||
settings: typing.ClassVar[LinksAwakeningSettings]
|
settings: typing.ClassVar[LinksAwakeningSettings]
|
||||||
topology_present = True # show path to required location checks in spoiler
|
topology_present = True # show path to required location checks in spoiler
|
||||||
|
|
||||||
@@ -104,11 +102,7 @@ class LinksAwakeningWorld(World):
|
|||||||
|
|
||||||
prefill_dungeon_items = None
|
prefill_dungeon_items = None
|
||||||
|
|
||||||
ladxr_settings: LADXRSettings
|
player_options = None
|
||||||
ladxr_logic: LADXRLogic
|
|
||||||
ladxr_itempool: LADXRItemPool
|
|
||||||
|
|
||||||
multi_key: bytearray
|
|
||||||
|
|
||||||
rupees = {
|
rupees = {
|
||||||
ItemName.RUPEES_20: 20,
|
ItemName.RUPEES_20: 20,
|
||||||
@@ -119,13 +113,17 @@ class LinksAwakeningWorld(World):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def convert_ap_options_to_ladxr_logic(self):
|
def convert_ap_options_to_ladxr_logic(self):
|
||||||
self.ladxr_settings = LADXRSettings(dataclasses.asdict(self.options))
|
self.player_options = {
|
||||||
|
option: getattr(self.multiworld, option)[self.player] for option in self.option_definitions
|
||||||
|
}
|
||||||
|
|
||||||
self.ladxr_settings.validate()
|
self.laxdr_options = LADXRSettings(self.player_options)
|
||||||
|
|
||||||
|
self.laxdr_options.validate()
|
||||||
world_setup = LADXRWorldSetup()
|
world_setup = LADXRWorldSetup()
|
||||||
world_setup.randomize(self.ladxr_settings, self.random)
|
world_setup.randomize(self.laxdr_options, self.multiworld.random)
|
||||||
self.ladxr_logic = LADXRLogic(configuration_options=self.ladxr_settings, world_setup=world_setup)
|
self.ladxr_logic = LAXDRLogic(configuration_options=self.laxdr_options, world_setup=world_setup)
|
||||||
self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.ladxr_settings, self.random).toDict()
|
self.ladxr_itempool = LADXRItemPool(self.ladxr_logic, self.laxdr_options, self.multiworld.random).toDict()
|
||||||
|
|
||||||
def create_regions(self) -> None:
|
def create_regions(self) -> None:
|
||||||
# Initialize
|
# Initialize
|
||||||
@@ -182,8 +180,8 @@ class LinksAwakeningWorld(World):
|
|||||||
# For any and different world, set item rule instead
|
# For any and different world, set item rule instead
|
||||||
|
|
||||||
for dungeon_item_type in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]:
|
for dungeon_item_type in ["maps", "compasses", "small_keys", "nightmare_keys", "stone_beaks", "instruments"]:
|
||||||
option_name = "shuffle_" + dungeon_item_type
|
option = "shuffle_" + dungeon_item_type
|
||||||
option: DungeonItemShuffle = getattr(self.options, option_name)
|
option = self.player_options[option]
|
||||||
|
|
||||||
dungeon_item_types[option.ladxr_item] = option.value
|
dungeon_item_types[option.ladxr_item] = option.value
|
||||||
|
|
||||||
@@ -191,11 +189,11 @@ class LinksAwakeningWorld(World):
|
|||||||
num_items = 8 if dungeon_item_type == "instruments" else 9
|
num_items = 8 if dungeon_item_type == "instruments" else 9
|
||||||
|
|
||||||
if option.value == DungeonItemShuffle.option_own_world:
|
if option.value == DungeonItemShuffle.option_own_world:
|
||||||
self.options.local_items.value |= {
|
self.multiworld.local_items[self.player].value |= {
|
||||||
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
|
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
|
||||||
}
|
}
|
||||||
elif option.value == DungeonItemShuffle.option_different_world:
|
elif option.value == DungeonItemShuffle.option_different_world:
|
||||||
self.options.non_local_items.value |= {
|
self.multiworld.non_local_items[self.player].value |= {
|
||||||
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
|
ladxr_item_to_la_item_name[f"{option.ladxr_item}{i}"] for i in range(1, num_items + 1)
|
||||||
}
|
}
|
||||||
# option_original_dungeon = 0
|
# option_original_dungeon = 0
|
||||||
@@ -217,7 +215,7 @@ class LinksAwakeningWorld(World):
|
|||||||
else:
|
else:
|
||||||
item = self.create_item(item_name)
|
item = self.create_item(item_name)
|
||||||
|
|
||||||
if not self.options.tradequest and isinstance(item.item_data, TradeItemData):
|
if not self.multiworld.tradequest[self.player] and isinstance(item.item_data, TradeItemData):
|
||||||
location = self.multiworld.get_location(item.item_data.vanilla_location, self.player)
|
location = self.multiworld.get_location(item.item_data.vanilla_location, self.player)
|
||||||
location.place_locked_item(item)
|
location.place_locked_item(item)
|
||||||
location.show_in_spoiler = False
|
location.show_in_spoiler = False
|
||||||
@@ -289,7 +287,7 @@ class LinksAwakeningWorld(World):
|
|||||||
if item.player == self.player
|
if item.player == self.player
|
||||||
and item.item_data.ladxr_id in start_loc.ladxr_item.OPTIONS and not item.location]
|
and item.item_data.ladxr_id in start_loc.ladxr_item.OPTIONS and not item.location]
|
||||||
if possible_start_items:
|
if possible_start_items:
|
||||||
index = self.random.choice(possible_start_items)
|
index = self.multiworld.random.choice(possible_start_items)
|
||||||
start_item = self.multiworld.itempool.pop(index)
|
start_item = self.multiworld.itempool.pop(index)
|
||||||
start_loc.place_locked_item(start_item)
|
start_loc.place_locked_item(start_item)
|
||||||
|
|
||||||
@@ -338,7 +336,7 @@ class LinksAwakeningWorld(World):
|
|||||||
# Get the list of locations and shuffle
|
# Get the list of locations and shuffle
|
||||||
all_dungeon_locs_to_fill = sorted(all_dungeon_locs)
|
all_dungeon_locs_to_fill = sorted(all_dungeon_locs)
|
||||||
|
|
||||||
self.random.shuffle(all_dungeon_locs_to_fill)
|
self.multiworld.random.shuffle(all_dungeon_locs_to_fill)
|
||||||
|
|
||||||
# Get the list of items and sort by priority
|
# Get the list of items and sort by priority
|
||||||
def priority(item):
|
def priority(item):
|
||||||
@@ -467,19 +465,34 @@ class LinksAwakeningWorld(World):
|
|||||||
loc.ladxr_item.location_owner = self.player
|
loc.ladxr_item.location_owner = self.player
|
||||||
|
|
||||||
rom_name = Rom.get_base_rom_path()
|
rom_name = Rom.get_base_rom_path()
|
||||||
out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.player_name}.gbc"
|
out_name = f"AP-{self.multiworld.seed_name}-P{self.player}-{self.multiworld.player_name[self.player]}.gbc"
|
||||||
out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc")
|
out_path = os.path.join(output_directory, f"{self.multiworld.get_out_file_name_base(self.player)}.gbc")
|
||||||
|
|
||||||
parser = get_parser()
|
parser = get_parser()
|
||||||
args = parser.parse_args([rom_name, "-o", out_name, "--dump"])
|
args = parser.parse_args([rom_name, "-o", out_name, "--dump"])
|
||||||
|
|
||||||
rom = generator.generateRom(args, self)
|
name_for_rom = self.multiworld.player_name[self.player]
|
||||||
|
|
||||||
|
all_names = [self.multiworld.player_name[i + 1] for i in range(len(self.multiworld.player_name))]
|
||||||
|
|
||||||
|
rom = generator.generateRom(
|
||||||
|
args,
|
||||||
|
self.laxdr_options,
|
||||||
|
self.player_options,
|
||||||
|
self.multi_key,
|
||||||
|
self.multiworld.seed_name,
|
||||||
|
self.ladxr_logic,
|
||||||
|
rnd=self.multiworld.per_slot_randoms[self.player],
|
||||||
|
player_name=name_for_rom,
|
||||||
|
player_names=all_names,
|
||||||
|
player_id = self.player,
|
||||||
|
multiworld=self.multiworld)
|
||||||
|
|
||||||
with open(out_path, "wb") as handle:
|
with open(out_path, "wb") as handle:
|
||||||
rom.save(handle, name="LADXR")
|
rom.save(handle, name="LADXR")
|
||||||
|
|
||||||
# Write title screen after everything else is done - full gfxmods may stomp over the egg tiles
|
# Write title screen after everything else is done - full gfxmods may stomp over the egg tiles
|
||||||
if self.options.ap_title_screen:
|
if self.player_options["ap_title_screen"]:
|
||||||
with tempfile.NamedTemporaryFile(delete=False) as title_patch:
|
with tempfile.NamedTemporaryFile(delete=False) as title_patch:
|
||||||
title_patch.write(pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4"))
|
title_patch.write(pkgutil.get_data(__name__, "LADXR/patches/title_screen.bdiff4"))
|
||||||
|
|
||||||
@@ -487,16 +500,16 @@ class LinksAwakeningWorld(World):
|
|||||||
os.unlink(title_patch.name)
|
os.unlink(title_patch.name)
|
||||||
|
|
||||||
patch = LADXDeltaPatch(os.path.splitext(out_path)[0]+LADXDeltaPatch.patch_file_ending, player=self.player,
|
patch = LADXDeltaPatch(os.path.splitext(out_path)[0]+LADXDeltaPatch.patch_file_ending, player=self.player,
|
||||||
player_name=self.player_name, patched_path=out_path)
|
player_name=self.multiworld.player_name[self.player], patched_path=out_path)
|
||||||
patch.write()
|
patch.write()
|
||||||
if not DEVELOPER_MODE:
|
if not DEVELOPER_MODE:
|
||||||
os.unlink(out_path)
|
os.unlink(out_path)
|
||||||
|
|
||||||
def generate_multi_key(self):
|
def generate_multi_key(self):
|
||||||
return bytearray(self.random.getrandbits(8) for _ in range(10)) + self.player.to_bytes(2, 'big')
|
return bytearray(self.multiworld.random.getrandbits(8) for _ in range(10)) + self.player.to_bytes(2, 'big')
|
||||||
|
|
||||||
def modify_multidata(self, multidata: dict):
|
def modify_multidata(self, multidata: dict):
|
||||||
multidata["connect_names"][binascii.hexlify(self.multi_key).decode()] = multidata["connect_names"][self.player_name]
|
multidata["connect_names"][binascii.hexlify(self.multi_key).decode()] = multidata["connect_names"][self.multiworld.player_name[self.player]]
|
||||||
|
|
||||||
def collect(self, state, item: Item) -> bool:
|
def collect(self, state, item: Item) -> bool:
|
||||||
change = super().collect(state, item)
|
change = super().collect(state, item)
|
||||||
|
|||||||
@@ -231,8 +231,6 @@ class MessengerRules:
|
|||||||
self.is_aerobatic,
|
self.is_aerobatic,
|
||||||
"Autumn Hills Seal - Trip Saws":
|
"Autumn Hills Seal - Trip Saws":
|
||||||
self.has_wingsuit,
|
self.has_wingsuit,
|
||||||
"Autumn Hills Seal - Double Swing Saws":
|
|
||||||
self.has_vertical,
|
|
||||||
# forlorn temple
|
# forlorn temple
|
||||||
"Forlorn Temple Seal - Rocket Maze":
|
"Forlorn Temple Seal - Rocket Maze":
|
||||||
self.has_vertical,
|
self.has_vertical,
|
||||||
@@ -432,8 +430,6 @@ class MessengerHardRules(MessengerRules):
|
|||||||
{
|
{
|
||||||
"Autumn Hills Seal - Spike Ball Darts":
|
"Autumn Hills Seal - Spike Ball Darts":
|
||||||
lambda state: self.has_vertical(state) and self.has_windmill(state) or self.is_aerobatic(state),
|
lambda state: self.has_vertical(state) and self.has_windmill(state) or self.is_aerobatic(state),
|
||||||
"Autumn Hills Seal - Double Swing Saws":
|
|
||||||
lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state),
|
|
||||||
"Bamboo Creek - Claustro":
|
"Bamboo Creek - Claustro":
|
||||||
self.has_wingsuit,
|
self.has_wingsuit,
|
||||||
"Bamboo Creek Seal - Spike Ball Pits":
|
"Bamboo Creek Seal - Spike Ball Pits":
|
||||||
|
|||||||
@@ -553,7 +553,7 @@ NOVA|73-4|Happy Otaku Pack Vol.19|True|6|8|10|
|
|||||||
Heaven's Gradius|73-5|Happy Otaku Pack Vol.19|True|6|8|10|
|
Heaven's Gradius|73-5|Happy Otaku Pack Vol.19|True|6|8|10|
|
||||||
Ray Tuning|74-0|CHUNITHM COURSE MUSE|True|6|8|10|
|
Ray Tuning|74-0|CHUNITHM COURSE MUSE|True|6|8|10|
|
||||||
World Vanquisher|74-1|CHUNITHM COURSE MUSE|True|6|8|10|11
|
World Vanquisher|74-1|CHUNITHM COURSE MUSE|True|6|8|10|11
|
||||||
Tsukuyomi Ni Naru|74-2|CHUNITHM COURSE MUSE|False|5|7|9|
|
Territory Battles|74-2|CHUNITHM COURSE MUSE|True|5|7|9|
|
||||||
The wheel to the right|74-3|CHUNITHM COURSE MUSE|True|5|7|9|11
|
The wheel to the right|74-3|CHUNITHM COURSE MUSE|True|5|7|9|11
|
||||||
Climax|74-4|CHUNITHM COURSE MUSE|True|4|8|11|11
|
Climax|74-4|CHUNITHM COURSE MUSE|True|4|8|11|11
|
||||||
Spider's Thread|74-5|CHUNITHM COURSE MUSE|True|5|8|10|12
|
Spider's Thread|74-5|CHUNITHM COURSE MUSE|True|5|8|10|12
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import Dict, TYPE_CHECKING
|
from typing import Dict, List, TYPE_CHECKING
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
from BaseClasses import CollectionState
|
from BaseClasses import CollectionState
|
||||||
from worlds.generic.Rules import forbid_item
|
from worlds.generic.Rules import forbid_item
|
||||||
@@ -78,7 +78,7 @@ def all_skull_dials_available(state: CollectionState, player: int) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def get_rules_lookup(player: int):
|
def get_rules_lookup(player: int):
|
||||||
rules_lookup: Dict[str, Dict[str, Callable[[CollectionState], bool]]] = {
|
rules_lookup: Dict[str, List[Callable[[CollectionState], bool]]] = {
|
||||||
"entrances": {
|
"entrances": {
|
||||||
"To Office Elevator From Underground Blue Tunnels": lambda state: state.has("Key for Office Elevator", player),
|
"To Office Elevator From Underground Blue Tunnels": lambda state: state.has("Key for Office Elevator", player),
|
||||||
"To Office Elevator From Office": lambda state: state.has("Key for Office Elevator", player),
|
"To Office Elevator From Office": lambda state: state.has("Key for Office Elevator", player),
|
||||||
@@ -195,15 +195,6 @@ def set_rules(world: "ShiversWorld") -> None:
|
|||||||
for location_name, rule in rules_lookup["lightning"].items():
|
for location_name, rule in rules_lookup["lightning"].items():
|
||||||
multiworld.get_location(location_name, player).access_rule = rule
|
multiworld.get_location(location_name, player).access_rule = rule
|
||||||
|
|
||||||
# Register indirect conditions
|
|
||||||
multiworld.register_indirect_condition(world.get_region("Burial"), world.get_entrance("To Slide Room"))
|
|
||||||
multiworld.register_indirect_condition(world.get_region("Egypt"), world.get_entrance("To Slide Room"))
|
|
||||||
multiworld.register_indirect_condition(world.get_region("Gods Room"), world.get_entrance("To Slide Room"))
|
|
||||||
multiworld.register_indirect_condition(world.get_region("Prehistoric"), world.get_entrance("To Slide Room"))
|
|
||||||
multiworld.register_indirect_condition(world.get_region("Tar River"), world.get_entrance("To Slide Room"))
|
|
||||||
multiworld.register_indirect_condition(world.get_region("Werewolf"), world.get_entrance("To Slide Room"))
|
|
||||||
multiworld.register_indirect_condition(world.get_region("Prehistoric"), world.get_entrance("To Tar River From Lobby"))
|
|
||||||
|
|
||||||
# forbid cloth in janitor closet and oil in tar river
|
# forbid cloth in janitor closet and oil in tar river
|
||||||
forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Bottom DUPE", player)
|
forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Bottom DUPE", player)
|
||||||
forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Top DUPE", player)
|
forbid_item(multiworld.get_location("Accessible: Storage: Janitor Closet", player), "Cloth Pot Top DUPE", player)
|
||||||
@@ -235,3 +226,7 @@ def set_rules(world: "ShiversWorld") -> None:
|
|||||||
|
|
||||||
# Set completion condition
|
# Set completion condition
|
||||||
multiworld.completion_condition[player] = lambda state: (first_nine_ixupi_capturable(state, player) and lightning_capturable(state, player))
|
multiworld.completion_condition[player] = lambda state: (first_nine_ixupi_capturable(state, player) and lightning_capturable(state, player))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ You can also use the Universal Tracker (by Faris and qwint) to find a complete l
|
|||||||
## What should I know regarding logic?
|
## What should I know regarding logic?
|
||||||
In general:
|
In general:
|
||||||
- Nighttime is not considered in logic. Every check in the game is obtainable during the day.
|
- Nighttime is not considered in logic. Every check in the game is obtainable during the day.
|
||||||
- Bushes are not considered in logic. It is assumed that the player will find a way past them, whether it is with a sword, a bomb, fire, luring an enemy, etc. There is also an option in the in-game randomizer settings menu to clear some of the early bushes.
|
|
||||||
- The Cathedral is accessible during the day by using the Hero's Laurels to reach the Overworld fuse near the Swamp entrance.
|
- The Cathedral is accessible during the day by using the Hero's Laurels to reach the Overworld fuse near the Swamp entrance.
|
||||||
- The Secret Legend chest at the Cathedral can be obtained during the day by opening the Holy Cross door from the outside.
|
- The Secret Legend chest at the Cathedral can be obtained during the day by opening the Holy Cross door from the outside.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user