Archipelago/worlds/ladx/Locations.py

267 lines
8.5 KiB
Python

from BaseClasses import Region, Entrance, Location, CollectionState
import typing
from .LADXR.checkMetadata import checkMetadataTable
from .Common import *
from worlds.generic.Rules import add_item_rule
from .Items import ladxr_item_to_la_item_name
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})"
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()
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)
address = None
if ladxr_item.event is not None:
name = ladxr_item.event
else:
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)
def has_free_weapon(state: CollectionState, player: int) -> bool:
return state.has("Progressive Sword", player) or state.has("Magic Rod", player) or state.has("Boomerang", player) or state.has("Hookshot", player)
# If the player has access to farm enough rupees to afford a game, we assume that they can keep beating the game
def can_farm_rupees(state: CollectionState, player: int) -> bool:
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):
# Don't allow any money usage if you can't get back wasted rupees
if item == "RUPEES":
if can_farm_rupees(self.state, self.player):
return self.state.prog_items[self.player]["RUPEES"]
return 0
elif item.endswith("_USED"):
return 0
else:
item = ladxr_item_to_la_item_name[item]
return self.state.prog_items[self.player].get(item, default)
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)
r.locations += [LinksAwakeningLocation(player, r, i) for i in l.items]
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())