2023-05-28 23:17:30 +00:00
|
|
|
from BaseClasses import Region, Entrance, Location, CollectionState
|
2024-12-05 11:06:52 +00:00
|
|
|
import typing
|
2023-03-20 16:26:03 +00:00
|
|
|
|
|
|
|
from .LADXR.checkMetadata import checkMetadataTable
|
|
|
|
from .Common import *
|
|
|
|
from worlds.generic.Rules import add_item_rule
|
2023-05-28 23:17:30 +00:00
|
|
|
from .Items import ladxr_item_to_la_item_name
|
|
|
|
|
2023-03-20 16:26:03 +00:00
|
|
|
|
|
|
|
prefilled_events = ["ANGLER_KEYHOLE", "RAFT", "MEDICINE2", "CASTLE_BUTTON"]
|
|
|
|
|
|
|
|
links_awakening_dungeon_names = [
|
|
|
|
"Tail Cave",
|
|
|
|
"Bottle Grotto",
|
|
|
|
"Key Cavern",
|
|
|
|
"Angler's Tunnel",
|
|
|
|
"Catfish's Maw",
|
|
|
|
"Face Shrine",
|
|
|
|
"Eagle's Tower",
|
|
|
|
"Turtle Rock",
|
|
|
|
"Color Dungeon"
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def meta_to_name(meta):
|
|
|
|
return f"{meta.name} ({meta.area})"
|
|
|
|
|
2024-12-05 11:06:52 +00:00
|
|
|
def get_location_name_groups() -> typing.Dict[str, typing.Set[str]]:
|
|
|
|
groups = {
|
|
|
|
"Instrument Pedestals": {
|
|
|
|
"Full Moon Cello (Tail Cave)",
|
|
|
|
"Conch Horn (Bottle Grotto)",
|
|
|
|
"Sea Lily's Bell (Key Cavern)",
|
|
|
|
"Surf Harp (Angler's Tunnel)",
|
|
|
|
"Wind Marimba (Catfish's Maw)",
|
|
|
|
"Coral Triangle (Face Shrine)",
|
|
|
|
"Organ of Evening Calm (Eagle's Tower)",
|
|
|
|
"Thunder Drum (Turtle Rock)",
|
|
|
|
},
|
|
|
|
"Boss Rewards": {
|
|
|
|
"Moldorm Heart Container (Tail Cave)",
|
|
|
|
"Genie Heart Container (Bottle Grotto)",
|
|
|
|
"Slime Eye Heart Container (Key Cavern)",
|
|
|
|
"Angler Fish Heart Container (Angler's Tunnel)",
|
|
|
|
"Slime Eel Heart Container (Catfish's Maw)",
|
|
|
|
"Facade Heart Container (Face Shrine)",
|
|
|
|
"Evil Eagle Heart Container (Eagle's Tower)",
|
|
|
|
"Hot Head Heart Container (Turtle Rock)",
|
|
|
|
"Tunic Fairy Item 1 (Color Dungeon)",
|
|
|
|
"Tunic Fairy Item 2 (Color Dungeon)",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
# Add region groups
|
|
|
|
for s, v in checkMetadataTable.items():
|
|
|
|
if s == "None":
|
|
|
|
continue
|
|
|
|
groups.setdefault(v.area, set()).add(meta_to_name(v))
|
|
|
|
return groups
|
|
|
|
|
|
|
|
links_awakening_location_name_groups = get_location_name_groups()
|
2023-03-20 16:26:03 +00:00
|
|
|
|
|
|
|
def get_locations_to_id():
|
|
|
|
ret = {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
# Magic to generate unique ids
|
|
|
|
for s, v in checkMetadataTable.items():
|
|
|
|
if s == "None":
|
|
|
|
continue
|
|
|
|
splits = s.split("-")
|
|
|
|
|
|
|
|
main_id = int(splits[0], 16)
|
|
|
|
sub_id = 0
|
|
|
|
if len(splits) > 1:
|
|
|
|
sub_id = splits[1]
|
|
|
|
if sub_id.isnumeric():
|
|
|
|
sub_id = (int(sub_id) + 1) * 1000
|
|
|
|
else:
|
|
|
|
sub_id = 1000
|
|
|
|
name = f"{v.name} ({v.area})"
|
|
|
|
ret[name] = BASE_ID + main_id + sub_id
|
|
|
|
|
|
|
|
return ret
|
|
|
|
|
|
|
|
|
|
|
|
locations_to_id = get_locations_to_id()
|
|
|
|
|
|
|
|
|
|
|
|
class LinksAwakeningLocation(Location):
|
|
|
|
game = LINKS_AWAKENING
|
|
|
|
dungeon = None
|
|
|
|
|
|
|
|
def __init__(self, player: int, region, ladxr_item):
|
|
|
|
name = meta_to_name(ladxr_item.metadata)
|
2024-04-14 18:37:48 +00:00
|
|
|
address = None
|
2023-03-20 16:26:03 +00:00
|
|
|
|
2024-04-14 18:37:48 +00:00
|
|
|
if ladxr_item.event is not None:
|
2023-03-20 16:26:03 +00:00
|
|
|
name = ladxr_item.event
|
2024-04-14 18:37:48 +00:00
|
|
|
else:
|
2023-03-20 16:26:03 +00:00
|
|
|
address = locations_to_id[name]
|
|
|
|
super().__init__(player, name, address)
|
|
|
|
self.parent_region = region
|
|
|
|
self.ladxr_item = ladxr_item
|
|
|
|
|
|
|
|
def filter_item(item):
|
|
|
|
if not ladxr_item.MULTIWORLD and item.player != player:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
add_item_rule(self, filter_item)
|
|
|
|
|
|
|
|
|
2023-05-28 23:17:30 +00:00
|
|
|
def has_free_weapon(state: CollectionState, player: int) -> bool:
|
2023-03-20 16:26:03 +00:00
|
|
|
return state.has("Progressive Sword", player) or state.has("Magic Rod", player) or state.has("Boomerang", player) or state.has("Hookshot", player)
|
|
|
|
|
2023-05-28 23:17:30 +00:00
|
|
|
|
2023-03-20 16:26:03 +00:00
|
|
|
# If the player has access to farm enough rupees to afford a game, we assume that they can keep beating the game
|
2023-05-28 23:17:30 +00:00
|
|
|
def can_farm_rupees(state: CollectionState, player: int) -> bool:
|
2023-03-20 16:26:03 +00:00
|
|
|
return has_free_weapon(state, player) and (state.has("Can Play Trendy Game", player=player) or state.has("RAFT", player=player))
|
|
|
|
|
|
|
|
|
|
|
|
class LinksAwakeningRegion(Region):
|
|
|
|
dungeon_index = None
|
|
|
|
ladxr_region = None
|
|
|
|
|
|
|
|
def __init__(self, name, ladxr_region, hint, player, world):
|
|
|
|
super().__init__(name, player, world, hint)
|
|
|
|
if ladxr_region:
|
|
|
|
self.ladxr_region = ladxr_region
|
|
|
|
if ladxr_region.dungeon:
|
|
|
|
self.dungeon_index = ladxr_region.dungeon
|
|
|
|
|
|
|
|
|
|
|
|
def translate_item_name(item):
|
|
|
|
if item in ladxr_item_to_la_item_name:
|
|
|
|
return ladxr_item_to_la_item_name[item]
|
|
|
|
|
|
|
|
return item
|
|
|
|
|
|
|
|
|
|
|
|
class GameStateAdapater:
|
|
|
|
def __init__(self, state, player):
|
|
|
|
self.state = state
|
|
|
|
self.player = player
|
|
|
|
|
|
|
|
def __contains__(self, item):
|
|
|
|
if item.endswith("_USED"):
|
|
|
|
return False
|
|
|
|
if item in ladxr_item_to_la_item_name:
|
|
|
|
item = ladxr_item_to_la_item_name[item]
|
|
|
|
|
|
|
|
return self.state.has(item, self.player)
|
|
|
|
|
|
|
|
def get(self, item, default):
|
2023-07-04 17:33:33 +00:00
|
|
|
# Don't allow any money usage if you can't get back wasted rupees
|
2023-03-20 16:26:03 +00:00
|
|
|
if item == "RUPEES":
|
2023-07-04 17:33:33 +00:00
|
|
|
if can_farm_rupees(self.state, self.player):
|
2023-11-02 05:41:20 +00:00
|
|
|
return self.state.prog_items[self.player]["RUPEES"]
|
2023-07-04 17:33:33 +00:00
|
|
|
return 0
|
2023-03-20 16:26:03 +00:00
|
|
|
elif item.endswith("_USED"):
|
|
|
|
return 0
|
|
|
|
else:
|
|
|
|
item = ladxr_item_to_la_item_name[item]
|
2023-11-02 05:41:20 +00:00
|
|
|
return self.state.prog_items[self.player].get(item, default)
|
2023-03-20 16:26:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
class LinksAwakeningEntrance(Entrance):
|
|
|
|
def __init__(self, player: int, name, region, condition):
|
|
|
|
super().__init__(player, name, region)
|
|
|
|
if isinstance(condition, str):
|
|
|
|
if condition in ladxr_item_to_la_item_name:
|
|
|
|
# Test if in inventory
|
|
|
|
self.condition = ladxr_item_to_la_item_name[condition]
|
|
|
|
else:
|
|
|
|
# Event
|
|
|
|
self.condition = condition
|
|
|
|
elif condition:
|
|
|
|
# rewrite condition
|
|
|
|
# .copyWithModifiedItemNames(translate_item_name)
|
|
|
|
self.condition = condition
|
|
|
|
else:
|
|
|
|
self.condition = None
|
|
|
|
|
|
|
|
def access_rule(self, state):
|
|
|
|
if isinstance(self.condition, str):
|
|
|
|
return state.has(self.condition, self.player)
|
|
|
|
if self.condition is None:
|
|
|
|
return True
|
|
|
|
|
|
|
|
return self.condition.test(GameStateAdapater(state, self.player))
|
|
|
|
|
|
|
|
|
|
|
|
# Helper to apply function to every ladxr region
|
|
|
|
def walk_ladxdr(f, n, walked=set()):
|
|
|
|
if n in walked:
|
|
|
|
return
|
|
|
|
f(n)
|
|
|
|
walked.add(n)
|
|
|
|
|
|
|
|
for o, req in n.simple_connections:
|
|
|
|
walk_ladxdr(f, o, walked)
|
|
|
|
for o, req in n.gated_connections:
|
|
|
|
walk_ladxdr(f, o, walked)
|
|
|
|
|
|
|
|
|
|
|
|
def ladxr_region_to_name(n):
|
|
|
|
name = n.name
|
|
|
|
if not name:
|
|
|
|
if len(n.items) == 1:
|
|
|
|
meta = n.items[0].metadata
|
|
|
|
name = f"{meta.name} ({meta.area})"
|
|
|
|
elif n.dungeon:
|
|
|
|
name = f"D{n.dungeon} Room"
|
|
|
|
else:
|
|
|
|
name = "No Name"
|
|
|
|
|
|
|
|
return name
|
|
|
|
|
|
|
|
|
|
|
|
def create_regions_from_ladxr(player, multiworld, logic):
|
|
|
|
tmp = set()
|
|
|
|
|
|
|
|
def print_items(n):
|
|
|
|
print(f"Creating Region {ladxr_region_to_name(n)}")
|
|
|
|
print("Has simple connections:")
|
|
|
|
for region, info in n.simple_connections:
|
|
|
|
print(" " + ladxr_region_to_name(region) + " | " + str(info))
|
|
|
|
print("Has gated connections:")
|
|
|
|
|
|
|
|
for region, info in n.gated_connections:
|
|
|
|
print(" " + ladxr_region_to_name(region) + " | " + str(info))
|
|
|
|
|
|
|
|
print("Has Locations:")
|
|
|
|
for item in n.items:
|
|
|
|
print(" " + str(item.metadata))
|
|
|
|
print()
|
|
|
|
|
|
|
|
used_names = {}
|
|
|
|
|
|
|
|
regions = {}
|
|
|
|
|
|
|
|
# Create regions
|
|
|
|
for l in logic.location_list:
|
|
|
|
# Temporarily uniqueify the name, until all regions are named
|
|
|
|
name = ladxr_region_to_name(l)
|
|
|
|
index = used_names.get(name, 0) + 1
|
|
|
|
used_names[name] = index
|
|
|
|
if index != 1:
|
|
|
|
name += f" {index}"
|
|
|
|
|
|
|
|
r = LinksAwakeningRegion(
|
|
|
|
name=name, ladxr_region=l, hint="", player=player, world=multiworld)
|
2023-10-29 18:47:37 +00:00
|
|
|
r.locations += [LinksAwakeningLocation(player, r, i) for i in l.items]
|
2023-03-20 16:26:03 +00:00
|
|
|
regions[l] = r
|
|
|
|
|
|
|
|
for ladxr_location in logic.location_list:
|
|
|
|
for connection_location, connection_condition in ladxr_location.simple_connections + ladxr_location.gated_connections:
|
|
|
|
region_a = regions[ladxr_location]
|
|
|
|
region_b = regions[connection_location]
|
|
|
|
# TODO: This name ain't gonna work for entrance rando, we need to cross reference with logic.world.overworld_entrance
|
|
|
|
entrance = LinksAwakeningEntrance(
|
|
|
|
player, f"{region_a.name} -> {region_b.name}", region_a, connection_condition)
|
|
|
|
region_a.exits.append(entrance)
|
|
|
|
entrance.connect(region_b)
|
|
|
|
|
|
|
|
return list(regions.values())
|