Skip to content

Commit

Permalink
Merge branch 'ladx/generate-without-rom' into ladx/override-options
Browse files Browse the repository at this point in the history
  • Loading branch information
threeandthreee committed Dec 5, 2024
2 parents 0a1d8f3 + 11f7099 commit 36508bb
Show file tree
Hide file tree
Showing 25 changed files with 693 additions and 343 deletions.
2 changes: 1 addition & 1 deletion worlds/hk/Options.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import typing
import re
from dataclasses import dataclass, make_dataclass
from dataclasses import make_dataclass

from .ExtractedData import logic_options, starts, pool_options
from .Rules import cost_terms
Expand Down
4 changes: 2 additions & 2 deletions worlds/hk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ def _add(item_name: str, location_name: str, randomized: bool):

for shop, locations in self.created_multi_locations.items():
for _ in range(len(locations), getattr(self.options, shop_to_option[shop]).value):
loc = self.create_location(shop)
self.create_location(shop)
unfilled_locations += 1

# Balance the pool
Expand All @@ -356,7 +356,7 @@ def _add(item_name: str, location_name: str, randomized: bool):
if shops:
for _ in range(additional_shop_items):
shop = self.random.choice(shops)
loc = self.create_location(shop)
self.create_location(shop)
unfilled_locations += 1
if len(self.created_multi_locations[shop]) >= 16:
shops.remove(shop)
Expand Down
136 changes: 134 additions & 2 deletions worlds/ladx/Items.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DungeonItemData(ItemData):
@property
def dungeon_index(self):
return int(self.ladxr_id[-1])

@property
def dungeon_item_type(self):
s = self.ladxr_id[:-1]
Expand Down Expand Up @@ -174,7 +174,7 @@ class ItemName:
TRADING_ITEM_SCALE = "Scale"
TRADING_ITEM_MAGNIFYING_GLASS = "Magnifying Glass"

trade_item_prog = ItemClassification.progression
trade_item_prog = ItemClassification.progression

links_awakening_items = [
ItemData(ItemName.POWER_BRACELET, "POWER_BRACELET", ItemClassification.progression),
Expand Down Expand Up @@ -303,3 +303,135 @@ class ItemName:
links_awakening_items_by_name = {
item.item_name : item for item in links_awakening_items
}

links_awakening_item_name_groups: typing.Dict[str, typing.Set[str]] = {
"Instruments": {
"Full Moon Cello",
"Conch Horn",
"Sea Lily's Bell",
"Surf Harp",
"Wind Marimba",
"Coral Triangle",
"Organ of Evening Calm",
"Thunder Drum",
},
"Entrance Keys": {
"Tail Key",
"Angler Key",
"Face Key",
"Bird Key",
"Slime Key",
},
"Nightmare Keys": {
"Nightmare Key (Angler's Tunnel)",
"Nightmare Key (Bottle Grotto)",
"Nightmare Key (Catfish's Maw)",
"Nightmare Key (Color Dungeon)",
"Nightmare Key (Eagle's Tower)",
"Nightmare Key (Face Shrine)",
"Nightmare Key (Key Cavern)",
"Nightmare Key (Tail Cave)",
"Nightmare Key (Turtle Rock)",
},
"Small Keys": {
"Small Key (Angler's Tunnel)",
"Small Key (Bottle Grotto)",
"Small Key (Catfish's Maw)",
"Small Key (Color Dungeon)",
"Small Key (Eagle's Tower)",
"Small Key (Face Shrine)",
"Small Key (Key Cavern)",
"Small Key (Tail Cave)",
"Small Key (Turtle Rock)",
},
"Compasses": {
"Compass (Angler's Tunnel)",
"Compass (Bottle Grotto)",
"Compass (Catfish's Maw)",
"Compass (Color Dungeon)",
"Compass (Eagle's Tower)",
"Compass (Face Shrine)",
"Compass (Key Cavern)",
"Compass (Tail Cave)",
"Compass (Turtle Rock)",
},
"Maps": {
"Dungeon Map (Angler's Tunnel)",
"Dungeon Map (Bottle Grotto)",
"Dungeon Map (Catfish's Maw)",
"Dungeon Map (Color Dungeon)",
"Dungeon Map (Eagle's Tower)",
"Dungeon Map (Face Shrine)",
"Dungeon Map (Key Cavern)",
"Dungeon Map (Tail Cave)",
"Dungeon Map (Turtle Rock)",
},
"Stone Beaks": {
"Stone Beak (Angler's Tunnel)",
"Stone Beak (Bottle Grotto)",
"Stone Beak (Catfish's Maw)",
"Stone Beak (Color Dungeon)",
"Stone Beak (Eagle's Tower)",
"Stone Beak (Face Shrine)",
"Stone Beak (Key Cavern)",
"Stone Beak (Tail Cave)",
"Stone Beak (Turtle Rock)",
},
"Trading Items": {
"Yoshi Doll",
"Ribbon",
"Dog Food",
"Bananas",
"Stick",
"Honeycomb",
"Pineapple",
"Hibiscus",
"Letter",
"Broom",
"Fishing Hook",
"Necklace",
"Scale",
"Magnifying Glass",
},
"Rupees": {
"20 Rupees",
"50 Rupees",
"100 Rupees",
"200 Rupees",
"500 Rupees",
},
"Upgrades": {
"Max Powder Upgrade",
"Max Bombs Upgrade",
"Max Arrows Upgrade",
},
"Songs": {
"Ballad of the Wind Fish",
"Manbo's Mambo",
"Frog's Song of Soul",
},
"Tunics": {
"Red Tunic",
"Blue Tunic",
},
"Bush Breakers": {
"Progressive Power Bracelet",
"Magic Rod",
"Magic Powder",
"Bomb",
"Progressive Sword",
"Boomerang",
},
"Sword": {
"Progressive Sword",
},
"Shield": {
"Progressive Shield",
},
"Power Bracelet": {
"Progressive Power Bracelet",
},
"Bracelet": {
"Progressive Power Bracelet",
},
}
85 changes: 65 additions & 20 deletions worlds/ladx/LADXR/generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,14 @@
from .. import Options

# Function to generate a final rom, this patches the rom with all required patches
def generateRom(base_rom: bytes, args, data: Dict):
random.seed(data["seed"] + data["player"])
multi_key = binascii.unhexlify(data["multi_key"].encode())
item_list = pickle.loads(binascii.unhexlify(data["item_list"].encode()))
options = data["options"]
def generateRom(base_rom: bytes, args, patch_data: Dict):
random.seed(patch_data["seed"] + patch_data["player"])
multi_key = binascii.unhexlify(patch_data["multi_key"].encode())
item_list = pickle.loads(binascii.unhexlify(patch_data["item_list"].encode()))
options = patch_data["options"]
rom_patches = []
rom = ROMWithTables(base_rom, rom_patches)
rom.player_names = data["other_player_names"]
rom.player_names = patch_data["other_player_names"]
pymods = []
if args.pymod:
for pymod in args.pymod:
Expand Down Expand Up @@ -125,7 +125,7 @@ def generateRom(base_rom: bytes, args, data: Dict):
patches.core.easyColorDungeonAccess(rom)
patches.owl.removeOwlEvents(rom)
patches.enemies.fixArmosKnightAsMiniboss(rom)
patches.bank3e.addBank3E(rom, multi_key, data["player"], data["other_player_names"])
patches.bank3e.addBank3E(rom, multi_key, patch_data["player"], patch_data["other_player_names"])
patches.bank3f.addBank3F(rom)
patches.bank34.addBank34(rom, item_list)
patches.core.removeGhost(rom)
Expand Down Expand Up @@ -241,21 +241,22 @@ def generateRom(base_rom: bytes, args, data: Dict):

patches.core.addBootsControls(rom, options["boots_controls"])

hints.addHints(rom, random, data["hint_texts"])
random.seed(patch_data["seed"] + patch_data["player"])
hints.addHints(rom, random, patch_data["hint_texts"])

if data["world_setup"]["goal"] == "raft":
if patch_data["world_setup"]["goal"] == "raft":
patches.goal.setRaftGoal(rom)
elif data["world_setup"]["goal"] in ("bingo", "bingo-full"):
patches.bingo.setBingoGoal(rom, data["world_setup"]["bingo_goals"], data["world_setup"]["goal"])
elif data["world_setup"]["goal"] == "seashells":
elif patch_data["world_setup"]["goal"] in ("bingo", "bingo-full"):
patches.bingo.setBingoGoal(rom, patch_data["world_setup"]["bingo_goals"], patch_data["world_setup"]["goal"])
elif patch_data["world_setup"]["goal"] == "seashells":
patches.goal.setSeashellGoal(rom, 20)
else:
patches.goal.setRequiredInstrumentCount(rom, data["world_setup"]["goal"])
patches.goal.setRequiredInstrumentCount(rom, patch_data["world_setup"]["goal"])

# Patch the generated logic into the rom
patches.chest.setMultiChest(rom, data["world_setup"]["multichest"])
patches.chest.setMultiChest(rom, patch_data["world_setup"]["multichest"])
#if ladxr_settings["overworld"] not in {"dungeondive", "random"}:
patches.entrances.changeEntrances(rom, data["world_setup"]["entrance_mapping"])
patches.entrances.changeEntrances(rom, patch_data["world_setup"]["entrance_mapping"])
for spot in item_list:
if spot.item and spot.item.startswith("*"):
spot.item = spot.item[1:]
Expand All @@ -266,14 +267,14 @@ def generateRom(base_rom: bytes, args, data: Dict):
# There are only 101 player name slots (99 + "The Server" + "another world"), so don't use more than that
mw = 100
spot.patch(rom, spot.item, multiworld=mw)
#patches.enemies.changeBosses(rom, data["world_setup"]["boss_mapping"])
#patches.enemies.changeMiniBosses(rom, data["world_setup"]["miniboss_mapping"])
#patches.enemies.changeBosses(rom, patch_data["world_setup"]["boss_mapping"])
#patches.enemies.changeMiniBosses(rom, patch_data["world_setup"]["miniboss_mapping"])

if not args.romdebugmode:
patches.core.addFrameCounter(rom, len(item_list))

patches.core.warpHome(rom) # Needs to be done after setting the start location.
patches.titleScreen.setRomInfo(rom, data)
patches.titleScreen.setRomInfo(rom, patch_data)
if options["ap_title_screen"]:
patches.titleScreen.setTitleGraphics(rom)
patches.endscreen.updateEndScreen(rom)
Expand All @@ -282,11 +283,54 @@ def generateRom(base_rom: bytes, args, data: Dict):
patches.enemies.doubleTrouble(rom)

if options["text_shuffle"]:
excluded_ids = [
# Overworld owl statues
0x1B6, 0x1B7, 0x1B8, 0x1B9, 0x1BA, 0x1BB, 0x1BC, 0x1BD, 0x1BE, 0x22D,

# Dungeon owls
0x288, 0x280, # D1
0x28A, 0x289, 0x281, # D2
0x282, 0x28C, 0x28B, # D3
0x283, # D4
0x28D, 0x284, # D5
0x285, 0x28F, 0x28E, # D6
0x291, 0x290, 0x286, # D7
0x293, 0x287, 0x292, # D8
0x263, # D0

# Hint books
0x267, # color dungeon
0x200, 0x201,
0x202, 0x203,
0x204, 0x205,
0x206, 0x207,
0x208, 0x209,
0x20A, 0x20B,
0x20C,
0x20D, 0x20E,
0x217, 0x218, 0x219, 0x21A,

# Goal sign
0x1A3,

# Signpost maze
0x1A9, 0x1AA, 0x1AB, 0x1AC, 0x1AD,

# Prices
0x02C, 0x02D, 0x030, 0x031, 0x032, 0x033, # Shop items
0x03B, # Trendy Game
0x045, # Fisherman
0x018, 0x019, # Crazy Tracy
0x0DC, # Mamu
0x0F0, # Raft ride
]
excluded_texts = [ rom.texts[excluded_id] for excluded_id in excluded_ids]
buckets = defaultdict(list)
# For each ROM bank, shuffle text within the bank
random.seed(patch_data["seed"] + patch_data["player"])
for n, data in enumerate(rom.texts._PointerTable__data):
# Don't muck up which text boxes are questions and which are statements
if type(data) != int and data and data != b'\xFF':
if type(data) != int and data and data != b'\xFF' and data not in excluded_texts:
buckets[(rom.texts._PointerTable__banks[n], data[len(data) - 1] == 0xfe)].append((n, data))
for bucket in buckets.values():
# For each bucket, make a copy and shuffle
Expand Down Expand Up @@ -329,6 +373,7 @@ def generateRom(base_rom: bytes, args, data: Dict):
Options.TrendyGame.option_impossible: (3, 16),
}
def speed():
random.seed(patch_data["seed"] + patch_data["player"])
return random.randint(*speeds[options["trendy_game"]])
rom.banks[0x4][0x76A0-0x4000] = 0xFF - speed()
rom.banks[0x4][0x76A2-0x4000] = speed()
Expand Down Expand Up @@ -433,4 +478,4 @@ def clamp(x, min, max):
for pymod in pymods:
pymod.postPatch(rom)

return rom.export()
return rom.save()
15 changes: 8 additions & 7 deletions worlds/ladx/LADXR/logic/dungeon1.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def __init__(self, options, world_setup, r):
entrance.add(DungeonChest(0x113), DungeonChest(0x115), DungeonChest(0x10E))
Location(dungeon=1).add(DroppedKey(0x116)).connect(entrance, OR(BOMB, r.push_hardhat)) # hardhat beetles (can kill with bomb)
Location(dungeon=1).add(DungeonChest(0x10D)).connect(entrance, OR(r.attack_hookshot_powder, SHIELD)) # moldorm spawn chest
stalfos_keese_room = Location(dungeon=1).add(DungeonChest(0x114)).connect(entrance, r.attack_hookshot) # 2 stalfos 2 keese room
stalfos_keese_room = Location(dungeon=1).add(DungeonChest(0x114)).connect(entrance, AND(OR(r.attack_skeleton, SHIELD),r.attack_hookshot_powder)) # 2 stalfos 2 keese room
Location(dungeon=1).add(DungeonChest(0x10C)).connect(entrance, BOMB) # hidden seashell room
dungeon1_upper_left = Location(dungeon=1).connect(entrance, AND(KEY1, FOUND(KEY1, 3)))
if options.owlstatues == "both" or options.owlstatues == "dungeon":
Expand All @@ -19,21 +19,22 @@ def __init__(self, options, world_setup, r):
dungeon1_right_side = Location(dungeon=1).connect(entrance, AND(KEY1, FOUND(KEY1, 3)))
if options.owlstatues == "both" or options.owlstatues == "dungeon":
Location(dungeon=1).add(OwlStatue(0x10A)).connect(dungeon1_right_side, STONE_BEAK1)
Location(dungeon=1).add(DungeonChest(0x10A)).connect(dungeon1_right_side, OR(r.attack_hookshot, SHIELD)) # three of a kind, shield stops the suit from changing
dungeon1_3_of_a_kind = Location(dungeon=1).add(DungeonChest(0x10A)).connect(dungeon1_right_side, OR(r.attack_hookshot_no_bomb, SHIELD)) # three of a kind, shield stops the suit from changing
dungeon1_miniboss = Location(dungeon=1).connect(dungeon1_right_side, AND(r.miniboss_requirements[world_setup.miniboss_mapping[0]], FEATHER))
dungeon1_boss = Location(dungeon=1).connect(dungeon1_miniboss, NIGHTMARE_KEY1)
Location(dungeon=1).add(HeartContainer(0x106), Instrument(0x102)).connect(dungeon1_boss, r.boss_requirements[world_setup.boss_mapping[0]])
boss = Location(dungeon=1).add(HeartContainer(0x106), Instrument(0x102)).connect(dungeon1_boss, r.boss_requirements[world_setup.boss_mapping[0]])

if options.logic not in ('normal', 'casual'):
if options.logic == 'hard' or options.logic == 'glitched' or options.logic == 'hell':
stalfos_keese_room.connect(entrance, r.attack_hookshot_powder) # stalfos jump away when you press a button.

dungeon1_3_of_a_kind.connect(dungeon1_right_side, BOMB) # use timed bombs to match the 3 of a kinds

if options.logic == 'glitched' or options.logic == 'hell':
boss_key.connect(entrance, FEATHER) # super jump
boss_key.connect(entrance, r.super_jump_feather) # super jump
dungeon1_miniboss.connect(dungeon1_right_side, r.miniboss_requirements[world_setup.miniboss_mapping[0]]) # damage boost or buffer pause over the pit to cross or mushroom

if options.logic == 'hell':
feather_chest.connect(dungeon1_upper_left, SWORD) # keep slashing the spiked beetles until they keep moving 1 pixel close towards you and the pit, to get them to fall
boss_key.connect(entrance, FOUND(KEY1,3)) # damage boost off the hardhat to cross the pit
boss_key.connect(entrance, AND(r.damage_boost, FOUND(KEY1,3))) # damage boost off the hardhat to cross the pit

self.entrance = entrance

Expand Down
14 changes: 7 additions & 7 deletions worlds/ladx/LADXR/logic/dungeon2.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def __init__(self, options, world_setup, r):
Location(dungeon=2).add(DungeonChest(0x137)).connect(dungeon2_r2, AND(KEY2, FOUND(KEY2, 5), OR(r.rear_attack, r.rear_attack_range))) # compass chest
if options.owlstatues == "both" or options.owlstatues == "dungeon":
Location(dungeon=2).add(OwlStatue(0x133)).connect(dungeon2_r2, STONE_BEAK2)
dungeon2_r3 = Location(dungeon=2).add(DungeonChest(0x138)).connect(dungeon2_r2, r.attack_hookshot) # first chest with key, can hookshot the switch in previous room
dungeon2_r3 = Location(dungeon=2).add(DungeonChest(0x138)).connect(dungeon2_r2, r.hit_switch) # first chest with key, can hookshot the switch in previous room
dungeon2_r4 = Location(dungeon=2).add(DungeonChest(0x139)).connect(dungeon2_r3, FEATHER) # button spawn chest
if options.logic == "casual":
shyguy_key_drop = Location(dungeon=2).add(DroppedKey(0x134)).connect(dungeon2_r3, AND(FEATHER, OR(r.rear_attack, r.rear_attack_range))) # shyguy drop key
Expand All @@ -39,16 +39,16 @@ def __init__(self, options, world_setup, r):

if options.logic == 'glitched' or options.logic == 'hell':
dungeon2_ghosts_chest.connect(dungeon2_ghosts_room, SWORD) # use sword to spawn ghosts on other side of the room so they run away (logically irrelevant because of torches at start)
dungeon2_r6.connect(miniboss, FEATHER) # superjump to staircase next to hinox.
dungeon2_r6.connect(miniboss, r.super_jump_feather) # superjump to staircase next to hinox.

if options.logic == 'hell':
dungeon2_map_chest.connect(dungeon2_l2, AND(r.attack_hookshot_powder, PEGASUS_BOOTS)) # use boots to jump over the pits
dungeon2_r4.connect(dungeon2_r3, OR(PEGASUS_BOOTS, HOOKSHOT)) # can use both pegasus boots bonks or hookshot spam to cross the pit room
dungeon2_map_chest.connect(dungeon2_l2, AND(r.attack_hookshot_powder, r.boots_bonk_pit)) # use boots to jump over the pits
dungeon2_r4.connect(dungeon2_r3, OR(r.boots_bonk_pit, r.hookshot_spam_pit)) # can use both pegasus boots bonks or hookshot spam to cross the pit room
dungeon2_r4.connect(shyguy_key_drop, r.rear_attack_range, one_way=True) # adjust for alternate requirements for dungeon2_r4
miniboss.connect(dungeon2_r5, AND(PEGASUS_BOOTS, r.miniboss_requirements[world_setup.miniboss_mapping[1]])) # use boots to dash over the spikes in the 2d section
miniboss.connect(dungeon2_r5, AND(r.boots_dash_2d, r.miniboss_requirements[world_setup.miniboss_mapping[1]])) # use boots to dash over the spikes in the 2d section
dungeon2_pre_stairs_boss.connect(dungeon2_r6, AND(HOOKSHOT, OR(BOW, BOMB, MAGIC_ROD, AND(OCARINA, SONG1)), FOUND(KEY2, 5))) # hookshot clip through the pot using both pol's voice
dungeon2_post_stairs_boss.connect(dungeon2_pre_stairs_boss, OR(BOMB, AND(PEGASUS_BOOTS, FEATHER))) # use a bomb to lower the last platform, or boots + feather to cross over top (only relevant in hell logic)
dungeon2_pre_boss.connect(dungeon2_post_stairs_boss, AND(PEGASUS_BOOTS, HOOKSHOT)) # boots bonk off bottom wall + hookshot spam across the two 1 tile pits vertically
dungeon2_post_stairs_boss.connect(dungeon2_pre_stairs_boss, OR(BOMB, r.boots_jump)) # use a bomb to lower the last platform, or boots + feather to cross over top (only relevant in hell logic)
dungeon2_pre_boss.connect(dungeon2_post_stairs_boss, AND(r.boots_bonk_pit, r.hookshot_spam_pit)) # boots bonk off bottom wall + hookshot spam across the two 1 tile pits vertically

self.entrance = entrance

Expand Down
Loading

0 comments on commit 36508bb

Please sign in to comment.