Merge branch 'main' into breaking_changes
# Conflicts: # BaseClasses.py # Fill.py # MultiClient.py # MultiServer.py # Utils.py # test/dungeons/TestDungeon.py # test/inverted/TestInverted.py # test/inverted_minor_glitches/TestInvertedMinor.py # test/inverted_owg/TestInvertedOWG.py # test/minor_glitches/TestMinor.py # test/owg/TestVanillaOWG.py # test/vanilla/TestVanilla.py # worlds/alttp/ItemPool.py # worlds/alttp/Main.py # worlds/alttp/Rom.py
This commit is contained in:
commit
a646594f08
224
BaseClasses.py
224
BaseClasses.py
|
@ -9,8 +9,7 @@ from typing import *
|
||||||
import secrets
|
import secrets
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from worlds.alttp.EntranceShuffle import door_addresses, indirect_connections
|
from worlds.alttp.EntranceShuffle import indirect_connections
|
||||||
from Utils import int16_as_bytes
|
|
||||||
from worlds.alttp.Items import item_name_groups
|
from worlds.alttp.Items import item_name_groups
|
||||||
from worlds.generic import PlandoItem, PlandoConnection
|
from worlds.generic import PlandoItem, PlandoConnection
|
||||||
|
|
||||||
|
@ -127,6 +126,7 @@ class MultiWorld():
|
||||||
set_player_attr('triforce_pieces_available', 30)
|
set_player_attr('triforce_pieces_available', 30)
|
||||||
set_player_attr('triforce_pieces_required', 20)
|
set_player_attr('triforce_pieces_required', 20)
|
||||||
set_player_attr('shop_shuffle', 'off')
|
set_player_attr('shop_shuffle', 'off')
|
||||||
|
set_player_attr('shop_shuffle_slots', 0)
|
||||||
set_player_attr('shuffle_prizes', "g")
|
set_player_attr('shuffle_prizes', "g")
|
||||||
set_player_attr('sprite_pool', [])
|
set_player_attr('sprite_pool', [])
|
||||||
set_player_attr('dark_room_logic', "lamp")
|
set_player_attr('dark_room_logic', "lamp")
|
||||||
|
@ -367,7 +367,7 @@ class MultiWorld():
|
||||||
else:
|
else:
|
||||||
return all((self.has_beaten_game(state, p) for p in range(1, self.players + 1)))
|
return all((self.has_beaten_game(state, p) for p in range(1, self.players + 1)))
|
||||||
|
|
||||||
def can_beat_game(self, starting_state=None):
|
def can_beat_game(self, starting_state : Optional[CollectionState]=None):
|
||||||
if starting_state:
|
if starting_state:
|
||||||
if self.has_beaten_game(starting_state):
|
if self.has_beaten_game(starting_state):
|
||||||
return True
|
return True
|
||||||
|
@ -399,6 +399,90 @@ class MultiWorld():
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_spheres(self):
|
||||||
|
state = CollectionState(self)
|
||||||
|
|
||||||
|
locations = {location for location in self.get_locations()}
|
||||||
|
|
||||||
|
while locations:
|
||||||
|
sphere = set()
|
||||||
|
|
||||||
|
for location in locations:
|
||||||
|
if location.can_reach(state):
|
||||||
|
sphere.add(location)
|
||||||
|
sphere_list = list(sphere)
|
||||||
|
sphere_list.sort(key=lambda location: location.name)
|
||||||
|
self.random.shuffle(sphere_list)
|
||||||
|
yield sphere_list
|
||||||
|
if not sphere:
|
||||||
|
if locations:
|
||||||
|
yield locations # unreachable locations
|
||||||
|
break
|
||||||
|
|
||||||
|
for location in sphere:
|
||||||
|
state.collect(location.item, True, location)
|
||||||
|
locations -= sphere
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def fulfills_accessibility(self, state: Optional[CollectionState] = None):
|
||||||
|
"""Check if accessibility rules are fulfilled with current or supplied state."""
|
||||||
|
if not state:
|
||||||
|
state = CollectionState(self)
|
||||||
|
players = {"none" : set(),
|
||||||
|
"items": set(),
|
||||||
|
"locations": set()}
|
||||||
|
for player, access in self.accessibility.items():
|
||||||
|
players[access].add(player)
|
||||||
|
|
||||||
|
beatable_fulfilled = False
|
||||||
|
|
||||||
|
def location_conditition(location : Location):
|
||||||
|
"""Determine if this location has to be accessible, location is already filtered by location_relevant"""
|
||||||
|
if location.player in players["none"]:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def location_relevant(location : Location):
|
||||||
|
"""Determine if this location is relevant to sweep."""
|
||||||
|
if location.player in players["locations"] or location.event or \
|
||||||
|
(location.item and location.item.advancement):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def all_done():
|
||||||
|
"""Check if all access rules are fulfilled"""
|
||||||
|
if beatable_fulfilled:
|
||||||
|
if any(location_conditition(location) for location in locations):
|
||||||
|
return False # still locations required to be collected
|
||||||
|
return True
|
||||||
|
|
||||||
|
locations = {location for location in self.get_locations() if location_relevant(location)}
|
||||||
|
|
||||||
|
while locations:
|
||||||
|
sphere = set()
|
||||||
|
for location in locations:
|
||||||
|
if location.can_reach(state):
|
||||||
|
sphere.add(location)
|
||||||
|
|
||||||
|
if not sphere:
|
||||||
|
# ran out of places and did not finish yet, quit
|
||||||
|
logging.debug(f"Could not access required locations.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
for location in sphere:
|
||||||
|
locations.remove(location)
|
||||||
|
state.collect(location.item, True, location)
|
||||||
|
|
||||||
|
if self.has_beaten_game(state):
|
||||||
|
beatable_fulfilled = True
|
||||||
|
|
||||||
|
if all_done():
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class CollectionState(object):
|
class CollectionState(object):
|
||||||
|
|
||||||
def __init__(self, parent: MultiWorld):
|
def __init__(self, parent: MultiWorld):
|
||||||
|
@ -932,7 +1016,7 @@ class Dungeon(object):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
||||||
|
|
||||||
class Boss(object):
|
class Boss():
|
||||||
def __init__(self, name, enemizer_name, defeat_rule, player: int):
|
def __init__(self, name, enemizer_name, defeat_rule, player: int):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.enemizer_name = enemizer_name
|
self.enemizer_name = enemizer_name
|
||||||
|
@ -942,7 +1026,13 @@ class Boss(object):
|
||||||
def can_defeat(self, state) -> bool:
|
def can_defeat(self, state) -> bool:
|
||||||
return self.defeat_rule(state, self.player)
|
return self.defeat_rule(state, self.player)
|
||||||
|
|
||||||
class Location(object):
|
|
||||||
|
class Location():
|
||||||
|
shop_slot: bool = False
|
||||||
|
shop_slot_disabled: bool = False
|
||||||
|
event: bool = False
|
||||||
|
locked: bool = False
|
||||||
|
|
||||||
def __init__(self, player: int, name: str = '', address=None, crystal: bool = False,
|
def __init__(self, player: int, name: str = '', address=None, crystal: bool = False,
|
||||||
hint_text: Optional[str] = None, parent=None,
|
hint_text: Optional[str] = None, parent=None,
|
||||||
player_address=None):
|
player_address=None):
|
||||||
|
@ -955,8 +1045,6 @@ class Location(object):
|
||||||
self.spot_type = 'Location'
|
self.spot_type = 'Location'
|
||||||
self.hint_text: str = hint_text if hint_text else name
|
self.hint_text: str = hint_text if hint_text else name
|
||||||
self.recursion_count = 0
|
self.recursion_count = 0
|
||||||
self.event = False
|
|
||||||
self.locked = False
|
|
||||||
self.always_allow = lambda item, state: False
|
self.always_allow = lambda item, state: False
|
||||||
self.access_rule = lambda state: True
|
self.access_rule = lambda state: True
|
||||||
self.item_rule = lambda item: True
|
self.item_rule = lambda item: True
|
||||||
|
@ -981,13 +1069,17 @@ class Location(object):
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash((self.name, self.player))
|
return hash((self.name, self.player))
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
return (self.player, self.name) < (other.player, other.name)
|
||||||
|
|
||||||
|
|
||||||
class Item(object):
|
class Item(object):
|
||||||
|
location: Optional[Location] = None
|
||||||
|
world: Optional[World] = None
|
||||||
|
|
||||||
def __init__(self, name='', advancement=False, priority=False, type=None, code=None, pedestal_hint=None, pedestal_credit=None, sickkid_credit=None, zora_credit=None, witch_credit=None, fluteboy_credit=None, hint_text=None, player=None):
|
def __init__(self, name='', advancement=False, type=None, code=None, pedestal_hint=None, pedestal_credit=None, sickkid_credit=None, zora_credit=None, witch_credit=None, fluteboy_credit=None, hint_text=None, player=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.advancement = advancement
|
self.advancement = advancement
|
||||||
self.priority = priority
|
|
||||||
self.type = type
|
self.type = type
|
||||||
self.pedestal_hint_text = pedestal_hint
|
self.pedestal_hint_text = pedestal_hint
|
||||||
self.pedestal_credit_text = pedestal_credit
|
self.pedestal_credit_text = pedestal_credit
|
||||||
|
@ -997,8 +1089,6 @@ class Item(object):
|
||||||
self.fluteboy_credit_text = fluteboy_credit
|
self.fluteboy_credit_text = fluteboy_credit
|
||||||
self.hint_text = hint_text
|
self.hint_text = hint_text
|
||||||
self.code = code
|
self.code = code
|
||||||
self.location = None
|
|
||||||
self.world = None
|
|
||||||
self.player = player
|
self.player = player
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
|
@ -1038,105 +1128,6 @@ class Item(object):
|
||||||
class Crystal(Item):
|
class Crystal(Item):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@unique
|
|
||||||
class ShopType(Enum):
|
|
||||||
Shop = 0
|
|
||||||
TakeAny = 1
|
|
||||||
UpgradeShop = 2
|
|
||||||
|
|
||||||
class Shop():
|
|
||||||
slots = 3 # slot count is not dynamic in asm, however inventory can have None as empty slots
|
|
||||||
blacklist = set() # items that don't work, todo: actually check against this
|
|
||||||
type = ShopType.Shop
|
|
||||||
|
|
||||||
def __init__(self, region: Region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool):
|
|
||||||
self.region = region
|
|
||||||
self.room_id = room_id
|
|
||||||
self.inventory: List[Union[None, dict]] = [None] * self.slots
|
|
||||||
self.shopkeeper_config = shopkeeper_config
|
|
||||||
self.custom = custom
|
|
||||||
self.locked = locked
|
|
||||||
|
|
||||||
@property
|
|
||||||
def item_count(self) -> int:
|
|
||||||
for x in range(self.slots - 1, -1, -1): # last x is 0
|
|
||||||
if self.inventory[x]:
|
|
||||||
return x + 1
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_bytes(self) -> List[int]:
|
|
||||||
# [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index]
|
|
||||||
entrances = self.region.entrances
|
|
||||||
config = self.item_count
|
|
||||||
if len(entrances) == 1 and entrances[0].name in door_addresses:
|
|
||||||
door_id = door_addresses[entrances[0].name][0] + 1
|
|
||||||
else:
|
|
||||||
door_id = 0
|
|
||||||
config |= 0x40 # ignore door id
|
|
||||||
if self.type == ShopType.TakeAny:
|
|
||||||
config |= 0x80
|
|
||||||
elif self.type == ShopType.UpgradeShop:
|
|
||||||
config |= 0x10 # Alt. VRAM
|
|
||||||
return [0x00]+int16_as_bytes(self.room_id)+[door_id, 0x00, config, self.shopkeeper_config, 0x00]
|
|
||||||
|
|
||||||
def has_unlimited(self, item: str) -> bool:
|
|
||||||
for inv in self.inventory:
|
|
||||||
if inv is None:
|
|
||||||
continue
|
|
||||||
if inv['item'] == item:
|
|
||||||
return True
|
|
||||||
if inv['max'] != 0 and inv['replacement'] is not None and inv['replacement'] == item:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def has(self, item: str) -> bool:
|
|
||||||
for inv in self.inventory:
|
|
||||||
if inv is None:
|
|
||||||
continue
|
|
||||||
if inv['item'] == item:
|
|
||||||
return True
|
|
||||||
if inv['max'] != 0 and inv['replacement'] == item:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def clear_inventory(self):
|
|
||||||
self.inventory = [None] * self.slots
|
|
||||||
|
|
||||||
def add_inventory(self, slot: int, item: str, price: int, max: int = 0,
|
|
||||||
replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False):
|
|
||||||
self.inventory[slot] = {
|
|
||||||
'item': item,
|
|
||||||
'price': price,
|
|
||||||
'max': max,
|
|
||||||
'replacement': replacement,
|
|
||||||
'replacement_price': replacement_price,
|
|
||||||
'create_location': create_location
|
|
||||||
}
|
|
||||||
|
|
||||||
def push_inventory(self, slot: int, item: str, price: int, max: int = 1):
|
|
||||||
if not self.inventory[slot]:
|
|
||||||
raise ValueError("Inventory can't be pushed back if it doesn't exist")
|
|
||||||
|
|
||||||
self.inventory[slot] = {
|
|
||||||
'item': item,
|
|
||||||
'price': price,
|
|
||||||
'max': max,
|
|
||||||
'replacement': self.inventory[slot]["item"],
|
|
||||||
'replacement_price': self.inventory[slot]["price"],
|
|
||||||
'create_location': self.inventory[slot]["create_location"]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TakeAny(Shop):
|
|
||||||
type = ShopType.TakeAny
|
|
||||||
|
|
||||||
|
|
||||||
class UpgradeShop(Shop):
|
|
||||||
type = ShopType.UpgradeShop
|
|
||||||
# Potions break due to VRAM flags set in UpgradeShop.
|
|
||||||
# Didn't check for more things breaking as not much else can be shuffled here currently
|
|
||||||
blacklist = item_name_groups["Potions"]
|
|
||||||
|
|
||||||
|
|
||||||
class Spoiler(object):
|
class Spoiler(object):
|
||||||
world: MultiWorld
|
world: MultiWorld
|
||||||
|
@ -1199,6 +1190,7 @@ class Spoiler(object):
|
||||||
listed_locations.update(other_locations)
|
listed_locations.update(other_locations)
|
||||||
|
|
||||||
self.shops = []
|
self.shops = []
|
||||||
|
from Shops import ShopType
|
||||||
for shop in self.world.shops:
|
for shop in self.world.shops:
|
||||||
if not shop.custom:
|
if not shop.custom:
|
||||||
continue
|
continue
|
||||||
|
@ -1209,6 +1201,10 @@ class Spoiler(object):
|
||||||
if item is None:
|
if item is None:
|
||||||
continue
|
continue
|
||||||
shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item']
|
shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item']
|
||||||
|
|
||||||
|
if item['player'] > 0:
|
||||||
|
shopdata['item_{}'.format(index)] = shopdata['item_{}'.format(index)].replace('—', '(Player {}) — '.format(item['player']))
|
||||||
|
|
||||||
if item['max'] == 0:
|
if item['max'] == 0:
|
||||||
continue
|
continue
|
||||||
shopdata['item_{}'.format(index)] += " x {}".format(item['max'])
|
shopdata['item_{}'.format(index)] += " x {}".format(item['max'])
|
||||||
|
@ -1282,6 +1278,7 @@ class Spoiler(object):
|
||||||
'triforce_pieces_available': self.world.triforce_pieces_available,
|
'triforce_pieces_available': self.world.triforce_pieces_available,
|
||||||
'triforce_pieces_required': self.world.triforce_pieces_required,
|
'triforce_pieces_required': self.world.triforce_pieces_required,
|
||||||
'shop_shuffle': self.world.shop_shuffle,
|
'shop_shuffle': self.world.shop_shuffle,
|
||||||
|
'shop_shuffle_slots': self.world.shop_shuffle_slots,
|
||||||
'shuffle_prizes': self.world.shuffle_prizes,
|
'shuffle_prizes': self.world.shuffle_prizes,
|
||||||
'sprite_pool': self.world.sprite_pool,
|
'sprite_pool': self.world.sprite_pool,
|
||||||
'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss
|
'restrict_dungeon_item_on_boss': self.world.restrict_dungeon_item_on_boss
|
||||||
|
@ -1367,6 +1364,13 @@ class Spoiler(object):
|
||||||
bool_to_text("p" in self.metadata["shop_shuffle"][player]))
|
bool_to_text("p" in self.metadata["shop_shuffle"][player]))
|
||||||
outfile.write('Shop upgrade shuffle: %s\n' %
|
outfile.write('Shop upgrade shuffle: %s\n' %
|
||||||
bool_to_text("u" in self.metadata["shop_shuffle"][player]))
|
bool_to_text("u" in self.metadata["shop_shuffle"][player]))
|
||||||
|
outfile.write('New Shop inventory: %s\n' %
|
||||||
|
bool_to_text("g" in self.metadata["shop_shuffle"][player] or
|
||||||
|
"f" in self.metadata["shop_shuffle"][player]))
|
||||||
|
outfile.write('Custom Potion Shop: %s\n' %
|
||||||
|
bool_to_text("w" in self.metadata["shop_shuffle"][player]))
|
||||||
|
outfile.write('Shop Slots: %s\n' %
|
||||||
|
self.metadata["shop_shuffle_slots"][player])
|
||||||
outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player])
|
outfile.write('Boss shuffle: %s\n' % self.metadata['boss_shuffle'][player])
|
||||||
outfile.write(
|
outfile.write(
|
||||||
'Enemy shuffle: %s\n' % bool_to_text(self.metadata['enemy_shuffle'][player]))
|
'Enemy shuffle: %s\n' % bool_to_text(self.metadata['enemy_shuffle'][player]))
|
||||||
|
|
56
Fill.py
56
Fill.py
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from BaseClasses import CollectionState, PlandoItem
|
from BaseClasses import CollectionState, PlandoItem, Location
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import key_drop_data
|
from worlds.alttp.Regions import key_drop_data
|
||||||
|
|
||||||
|
@ -10,7 +10,8 @@ class FillError(RuntimeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def fill_restrictive(world, base_state: CollectionState, locations, itempool, single_player_placement=False):
|
def fill_restrictive(world, base_state: CollectionState, locations, itempool, single_player_placement=False,
|
||||||
|
lock=False):
|
||||||
def sweep_from_pool():
|
def sweep_from_pool():
|
||||||
new_state = base_state.copy()
|
new_state = base_state.copy()
|
||||||
for item in itempool:
|
for item in itempool:
|
||||||
|
@ -59,6 +60,8 @@ def fill_restrictive(world, base_state: CollectionState, locations, itempool, si
|
||||||
f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}')
|
f'Already placed {len(placements)}: {", ".join(str(place) for place in placements)}')
|
||||||
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
if lock:
|
||||||
|
spot_to_fill.locked = True
|
||||||
locations.remove(spot_to_fill)
|
locations.remove(spot_to_fill)
|
||||||
placements.append(spot_to_fill)
|
placements.append(spot_to_fill)
|
||||||
spot_to_fill.event = True
|
spot_to_fill.event = True
|
||||||
|
@ -74,21 +77,14 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
||||||
# get items to distribute
|
# get items to distribute
|
||||||
world.random.shuffle(world.itempool)
|
world.random.shuffle(world.itempool)
|
||||||
progitempool = []
|
progitempool = []
|
||||||
localprioitempool = {player: [] for player in range(1, world.players + 1)}
|
|
||||||
localrestitempool = {player: [] for player in range(1, world.players + 1)}
|
localrestitempool = {player: [] for player in range(1, world.players + 1)}
|
||||||
prioitempool = []
|
|
||||||
restitempool = []
|
restitempool = []
|
||||||
|
|
||||||
for item in world.itempool:
|
for item in world.itempool:
|
||||||
if item.advancement:
|
if item.advancement:
|
||||||
progitempool.append(item)
|
progitempool.append(item)
|
||||||
elif item.name in world.local_items[item.player]:
|
elif item.name in world.local_items[item.player]:
|
||||||
if item.priority:
|
localrestitempool[item.player].append(item)
|
||||||
localprioitempool[item.player].append(item)
|
|
||||||
else:
|
|
||||||
localrestitempool[item.player].append(item)
|
|
||||||
elif item.priority:
|
|
||||||
prioitempool.append(item)
|
|
||||||
else:
|
else:
|
||||||
restitempool.append(item)
|
restitempool.append(item)
|
||||||
|
|
||||||
|
@ -138,24 +134,13 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
||||||
|
|
||||||
fill_restrictive(world, world.state, fill_locations, progitempool)
|
fill_restrictive(world, world.state, fill_locations, progitempool)
|
||||||
|
|
||||||
if any(localprioitempool.values() or
|
if any(localrestitempool.values()): # we need to make sure some fills are limited to certain worlds
|
||||||
localrestitempool.values()): # we need to make sure some fills are limited to certain worlds
|
|
||||||
local_locations = {player: [] for player in world.player_ids}
|
local_locations = {player: [] for player in world.player_ids}
|
||||||
for location in fill_locations:
|
for location in fill_locations:
|
||||||
local_locations[location.player].append(location)
|
local_locations[location.player].append(location)
|
||||||
for locations in local_locations.values():
|
for locations in local_locations.values():
|
||||||
world.random.shuffle(locations)
|
world.random.shuffle(locations)
|
||||||
|
|
||||||
for player, items in localprioitempool.items(): # items already shuffled
|
|
||||||
player_local_locations = local_locations[player]
|
|
||||||
for item_to_place in items:
|
|
||||||
if not player_local_locations:
|
|
||||||
logging.warning(f"Ran out of local locations for player {player}, "
|
|
||||||
f"cannot place {item_to_place}.")
|
|
||||||
break
|
|
||||||
spot_to_fill = player_local_locations.pop()
|
|
||||||
world.push_item(spot_to_fill, item_to_place, False)
|
|
||||||
fill_locations.remove(spot_to_fill)
|
|
||||||
for player, items in localrestitempool.items(): # items already shuffled
|
for player, items in localrestitempool.items(): # items already shuffled
|
||||||
player_local_locations = local_locations[player]
|
player_local_locations = local_locations[player]
|
||||||
for item_to_place in items:
|
for item_to_place in items:
|
||||||
|
@ -168,12 +153,14 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
||||||
fill_locations.remove(spot_to_fill)
|
fill_locations.remove(spot_to_fill)
|
||||||
|
|
||||||
world.random.shuffle(fill_locations)
|
world.random.shuffle(fill_locations)
|
||||||
prioitempool, fill_locations = fast_fill(world, prioitempool, fill_locations)
|
|
||||||
|
|
||||||
restitempool, fill_locations = fast_fill(world, restitempool, fill_locations)
|
restitempool, fill_locations = fast_fill(world, restitempool, fill_locations)
|
||||||
unplaced = [item for item in progitempool + prioitempool + restitempool]
|
unplaced = [item for item in progitempool + restitempool]
|
||||||
unfilled = [location.name for location in fill_locations]
|
unfilled = [location.name for location in fill_locations]
|
||||||
|
|
||||||
|
for location in fill_locations:
|
||||||
|
world.push_item(location, ItemFactory('Nothing', location.player), False)
|
||||||
|
|
||||||
if unplaced or unfilled:
|
if unplaced or unfilled:
|
||||||
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
||||||
|
|
||||||
|
@ -235,7 +222,7 @@ def flood_items(world):
|
||||||
location_list = world.get_reachable_locations()
|
location_list = world.get_reachable_locations()
|
||||||
world.random.shuffle(location_list)
|
world.random.shuffle(location_list)
|
||||||
for location in location_list:
|
for location in location_list:
|
||||||
if location.item is not None and not location.item.advancement and not location.item.priority and not location.item.smallkey and not location.item.bigkey:
|
if location.item is not None and not location.item.advancement and not location.item.smallkey and not location.item.bigkey:
|
||||||
# safe to replace
|
# safe to replace
|
||||||
replace_item = location.item
|
replace_item = location.item
|
||||||
replace_item.location = None
|
replace_item.location = None
|
||||||
|
@ -244,6 +231,7 @@ def flood_items(world):
|
||||||
itempool.remove(item_to_place)
|
itempool.remove(item_to_place)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
def balance_multiworld_progression(world):
|
def balance_multiworld_progression(world):
|
||||||
balanceable_players = {player for player in range(1, world.players + 1) if world.progression_balancing[player]}
|
balanceable_players = {player for player in range(1, world.players + 1) if world.progression_balancing[player]}
|
||||||
if not balanceable_players:
|
if not balanceable_players:
|
||||||
|
@ -331,7 +319,8 @@ def balance_multiworld_progression(world):
|
||||||
replacement_locations.insert(0, new_location)
|
replacement_locations.insert(0, new_location)
|
||||||
new_location = replacement_locations.pop()
|
new_location = replacement_locations.pop()
|
||||||
|
|
||||||
new_location.item, old_location.item = old_location.item, new_location.item
|
swap_location_item(old_location, new_location)
|
||||||
|
|
||||||
new_location.event, old_location.event = True, False
|
new_location.event, old_location.event = True, False
|
||||||
logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, "
|
logging.debug(f"Progression balancing moved {new_location.item} to {new_location}, "
|
||||||
f"displacing {old_location.item} in {old_location}")
|
f"displacing {old_location.item} in {old_location}")
|
||||||
|
@ -355,6 +344,18 @@ def balance_multiworld_progression(world):
|
||||||
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
raise RuntimeError('Not all required items reachable. Something went terribly wrong here.')
|
||||||
|
|
||||||
|
|
||||||
|
def swap_location_item(location_1: Location, location_2: Location, check_locked=True):
|
||||||
|
"""Swaps Items of locations. Does NOT swap flags like event, shop_slot or locked"""
|
||||||
|
if check_locked:
|
||||||
|
if location_1.locked:
|
||||||
|
logging.warning(f"Swapping {location_1}, which is marked as locked.")
|
||||||
|
if location_2.locked:
|
||||||
|
logging.warning(f"Swapping {location_2}, which is marked as locked.")
|
||||||
|
location_2.item, location_1.item = location_1.item, location_2.item
|
||||||
|
location_1.item.location = location_1
|
||||||
|
location_2.item.location = location_2
|
||||||
|
|
||||||
|
|
||||||
def distribute_planned(world):
|
def distribute_planned(world):
|
||||||
world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids}
|
world_name_lookup = {world.player_names[player_id][0]: player_id for player_id in world.player_ids}
|
||||||
|
|
||||||
|
@ -362,7 +363,8 @@ def distribute_planned(world):
|
||||||
placement: PlandoItem
|
placement: PlandoItem
|
||||||
for placement in world.plando_items[player]:
|
for placement in world.plando_items[player]:
|
||||||
if placement.location in key_drop_data:
|
if placement.location in key_drop_data:
|
||||||
placement.warn(f"Can't place '{placement.item}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
|
placement.warn(
|
||||||
|
f"Can't place '{placement.item}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
|
||||||
continue
|
continue
|
||||||
item = ItemFactory(placement.item, player)
|
item = ItemFactory(placement.item, player)
|
||||||
target_world: int = placement.world
|
target_world: int = placement.world
|
||||||
|
|
33
Gui.py
33
Gui.py
|
@ -64,8 +64,13 @@ def guiMain(args=None):
|
||||||
createSpoilerCheckbutton = Checkbutton(checkBoxFrame, text="Create Spoiler Log", variable=createSpoilerVar)
|
createSpoilerCheckbutton = Checkbutton(checkBoxFrame, text="Create Spoiler Log", variable=createSpoilerVar)
|
||||||
suppressRomVar = IntVar()
|
suppressRomVar = IntVar()
|
||||||
suppressRomCheckbutton = Checkbutton(checkBoxFrame, text="Do not create patched Rom", variable=suppressRomVar)
|
suppressRomCheckbutton = Checkbutton(checkBoxFrame, text="Do not create patched Rom", variable=suppressRomVar)
|
||||||
openpyramidVar = IntVar()
|
openpyramidFrame = Frame(checkBoxFrame)
|
||||||
openpyramidCheckbutton = Checkbutton(checkBoxFrame, text="Pre-open Pyramid Hole", variable=openpyramidVar)
|
openpyramidVar = StringVar()
|
||||||
|
openpyramidVar.set('auto')
|
||||||
|
openpyramidOptionMenu = OptionMenu(openpyramidFrame, openpyramidVar, 'auto', 'goal', 'yes', 'no')
|
||||||
|
openpyramidLabel = Label(openpyramidFrame, text='Pre-open Pyramid Hole')
|
||||||
|
openpyramidLabel.pack(side=LEFT)
|
||||||
|
openpyramidOptionMenu.pack(side=LEFT)
|
||||||
mcsbshuffleFrame = Frame(checkBoxFrame)
|
mcsbshuffleFrame = Frame(checkBoxFrame)
|
||||||
mcsbLabel = Label(mcsbshuffleFrame, text="Shuffle: ")
|
mcsbLabel = Label(mcsbshuffleFrame, text="Shuffle: ")
|
||||||
|
|
||||||
|
@ -102,7 +107,7 @@ def guiMain(args=None):
|
||||||
|
|
||||||
createSpoilerCheckbutton.pack(expand=True, anchor=W)
|
createSpoilerCheckbutton.pack(expand=True, anchor=W)
|
||||||
suppressRomCheckbutton.pack(expand=True, anchor=W)
|
suppressRomCheckbutton.pack(expand=True, anchor=W)
|
||||||
openpyramidCheckbutton.pack(expand=True, anchor=W)
|
openpyramidFrame.pack(expand=True, anchor=W)
|
||||||
mcsbshuffleFrame.pack(expand=True, anchor=W)
|
mcsbshuffleFrame.pack(expand=True, anchor=W)
|
||||||
mcsbLabel.grid(row=0, column=0)
|
mcsbLabel.grid(row=0, column=0)
|
||||||
mapshuffleCheckbutton.grid(row=0, column=1)
|
mapshuffleCheckbutton.grid(row=0, column=1)
|
||||||
|
@ -488,6 +493,18 @@ def guiMain(args=None):
|
||||||
shopUpgradeShuffleButton = Checkbutton(shopframe, text="Lootable Upgrades", variable=shopUpgradeShuffleVar)
|
shopUpgradeShuffleButton = Checkbutton(shopframe, text="Lootable Upgrades", variable=shopUpgradeShuffleVar)
|
||||||
shopUpgradeShuffleButton.grid(row=0, column=2, sticky=W)
|
shopUpgradeShuffleButton.grid(row=0, column=2, sticky=W)
|
||||||
|
|
||||||
|
shopInventoryShuffleVar = IntVar()
|
||||||
|
shopInventoryShuffleButton = Checkbutton(shopframe, text="New Inventories", variable=shopInventoryShuffleVar)
|
||||||
|
shopInventoryShuffleButton.grid(row=1, column=0, sticky=W)
|
||||||
|
|
||||||
|
shopPoolShuffleVar = IntVar()
|
||||||
|
shopPoolShuffleButton = Checkbutton(shopframe, text="Itempool in Shops", variable=shopPoolShuffleVar)
|
||||||
|
shopPoolShuffleButton.grid(row=1, column=1, sticky=W)
|
||||||
|
|
||||||
|
shopWitchShuffleVar = IntVar()
|
||||||
|
shopWitchShuffleButton = Checkbutton(shopframe, text="Custom Potion Shop", variable=shopWitchShuffleVar)
|
||||||
|
shopWitchShuffleButton.grid(row=1, column=2, sticky=W)
|
||||||
|
|
||||||
multiworldframe = LabelFrame(randomizerWindow, text="Multiworld", padx=5, pady=2)
|
multiworldframe = LabelFrame(randomizerWindow, text="Multiworld", padx=5, pady=2)
|
||||||
|
|
||||||
worldLabel = Label(multiworldframe, text='Players per Team')
|
worldLabel = Label(multiworldframe, text='Players per Team')
|
||||||
|
@ -552,7 +569,7 @@ def guiMain(args=None):
|
||||||
guiargs.create_spoiler = bool(createSpoilerVar.get())
|
guiargs.create_spoiler = bool(createSpoilerVar.get())
|
||||||
guiargs.skip_playthrough = not bool(createSpoilerVar.get())
|
guiargs.skip_playthrough = not bool(createSpoilerVar.get())
|
||||||
guiargs.suppress_rom = bool(suppressRomVar.get())
|
guiargs.suppress_rom = bool(suppressRomVar.get())
|
||||||
guiargs.open_pyramid = bool(openpyramidVar.get())
|
guiargs.open_pyramid = openpyramidVar.get()
|
||||||
guiargs.mapshuffle = bool(mapshuffleVar.get())
|
guiargs.mapshuffle = bool(mapshuffleVar.get())
|
||||||
guiargs.compassshuffle = bool(compassshuffleVar.get())
|
guiargs.compassshuffle = bool(compassshuffleVar.get())
|
||||||
guiargs.keyshuffle = {"on": True, "universal": "universal", "off": False}[keyshuffleVar.get()]
|
guiargs.keyshuffle = {"on": True, "universal": "universal", "off": False}[keyshuffleVar.get()]
|
||||||
|
@ -586,6 +603,12 @@ def guiMain(args=None):
|
||||||
guiargs.shop_shuffle += "p"
|
guiargs.shop_shuffle += "p"
|
||||||
if shopUpgradeShuffleVar.get():
|
if shopUpgradeShuffleVar.get():
|
||||||
guiargs.shop_shuffle += "u"
|
guiargs.shop_shuffle += "u"
|
||||||
|
if shopInventoryShuffleVar.get():
|
||||||
|
guiargs.shop_shuffle += "f"
|
||||||
|
if shopWitchShuffleVar.get():
|
||||||
|
guiargs.shop_shuffle += "w"
|
||||||
|
if shopPoolShuffleVar.get():
|
||||||
|
guiargs.shop_shuffle_slots = 30
|
||||||
guiargs.shuffle_prizes = {"none": "",
|
guiargs.shuffle_prizes = {"none": "",
|
||||||
"bonk": "b",
|
"bonk": "b",
|
||||||
"general": "g",
|
"general": "g",
|
||||||
|
@ -1891,5 +1914,5 @@ if __name__ == '__main__':
|
||||||
top.update()
|
top.update()
|
||||||
print("Done updating sprites")
|
print("Done updating sprites")
|
||||||
else:
|
else:
|
||||||
logging.basicConfig(format='%(message)s', level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
guiMain()
|
guiMain()
|
||||||
|
|
127
MultiClient.py
127
MultiClient.py
|
@ -11,9 +11,12 @@ import socket
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import base64
|
import base64
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
|
||||||
from random import randrange
|
from random import randrange
|
||||||
|
|
||||||
|
import Shops
|
||||||
from Utils import get_item_name_from_id, get_location_name_from_address, ReceivedItem
|
from Utils import get_item_name_from_id, get_location_name_from_address, ReceivedItem
|
||||||
|
|
||||||
exit_func = atexit.register(input, "Press enter to close.")
|
exit_func = atexit.register(input, "Press enter to close.")
|
||||||
|
@ -86,18 +89,18 @@ class Context():
|
||||||
self.team = None
|
self.team = None
|
||||||
self.slot = None
|
self.slot = None
|
||||||
self.player_names: typing.Dict[int: str] = {}
|
self.player_names: typing.Dict[int: str] = {}
|
||||||
|
self.locations_recognized = set()
|
||||||
self.locations_checked = set()
|
self.locations_checked = set()
|
||||||
self.unsafe_locations_checked = set()
|
|
||||||
self.locations_scouted = set()
|
self.locations_scouted = set()
|
||||||
self.items_received = []
|
self.items_received = []
|
||||||
self.items_missing = []
|
self.items_missing = []
|
||||||
|
self.items_checked = None
|
||||||
self.locations_info = {}
|
self.locations_info = {}
|
||||||
self.awaiting_rom = False
|
self.awaiting_rom = False
|
||||||
self.rom = None
|
self.rom = None
|
||||||
self.prev_rom = None
|
self.prev_rom = None
|
||||||
self.auth = None
|
self.auth = None
|
||||||
self.found_items = found_items
|
self.found_items = found_items
|
||||||
self.send_unsafe = False
|
|
||||||
self.finished_game = False
|
self.finished_game = False
|
||||||
self.slow_mode = False
|
self.slow_mode = False
|
||||||
|
|
||||||
|
@ -148,6 +151,11 @@ SCOUT_LOCATION_ADDR = SAVEDATA_START + 0x4D7 # 1 byte
|
||||||
SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte
|
SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte
|
||||||
SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte
|
SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte
|
||||||
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
|
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
|
||||||
|
SHOP_ADDR = SAVEDATA_START + 0x302 # 2 bytes
|
||||||
|
|
||||||
|
|
||||||
|
location_shop_order = [name for name, info in Shops.shop_table.items()] # probably don't leave this here. This relies on python 3.6+ dictionary keys having defined order
|
||||||
|
location_shop_ids = set([info[0] for name, info in Shops.shop_table.items()])
|
||||||
|
|
||||||
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
||||||
"Blind's Hideout - Left": (0x11d, 0x20),
|
"Blind's Hideout - Left": (0x11d, 0x20),
|
||||||
|
@ -783,8 +791,19 @@ async def server_autoreconnect(ctx: Context):
|
||||||
ctx.server_task = asyncio.create_task(server_loop(ctx))
|
ctx.server_task = asyncio.create_task(server_loop(ctx))
|
||||||
|
|
||||||
|
|
||||||
async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]):
|
missing_unknown = re.compile("Unknown Location ID: (?P<ID>\d+)")
|
||||||
|
def convert_unknown_missing(missing_items: list) -> list:
|
||||||
|
missing = []
|
||||||
|
for location in missing_items:
|
||||||
|
match = missing_unknown.match(location)
|
||||||
|
if match:
|
||||||
|
missing.append(Regions.lookup_id_to_name.get(int(match['ID']), location))
|
||||||
|
else:
|
||||||
|
missing.append(location)
|
||||||
|
return missing
|
||||||
|
|
||||||
|
|
||||||
|
async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]):
|
||||||
if cmd == 'RoomInfo':
|
if cmd == 'RoomInfo':
|
||||||
logger.info('--------------------------------')
|
logger.info('--------------------------------')
|
||||||
logger.info('Room Information:')
|
logger.info('Room Information:')
|
||||||
|
@ -843,11 +862,7 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
||||||
raise Exception('Connection refused by the multiworld host, no reason provided')
|
raise Exception('Connection refused by the multiworld host, no reason provided')
|
||||||
|
|
||||||
elif cmd == 'Connected':
|
elif cmd == 'Connected':
|
||||||
if ctx.send_unsafe:
|
Utils.persistent_store("servers", ctx.rom, ctx.server_address)
|
||||||
ctx.send_unsafe = False
|
|
||||||
logger.info(
|
|
||||||
f'Turning off sending of ALL location checks not declared as missing. If you want it on, please use /send_unsafe true')
|
|
||||||
Utils.persistent_store("servers", ctx.rom, ctx.server_address)
|
|
||||||
ctx.team = args["team"]
|
ctx.team = args["team"]
|
||||||
ctx.slot = args["slot"]
|
ctx.slot = args["slot"]
|
||||||
ctx.player_names = {p: n for p, n in args["playernames"]}
|
ctx.player_names = {p: n for p, n in args["playernames"]}
|
||||||
|
@ -867,6 +882,7 @@ async def process_server_cmd(ctx: Context, cmd: str, args: typing.Optional[dict]
|
||||||
# This also serves to allow an easy visual of what locations were already checked previously
|
# This also serves to allow an easy visual of what locations were already checked previously
|
||||||
# when /missing is used for the client side view of what is missing.
|
# when /missing is used for the client side view of what is missing.
|
||||||
ctx.items_missing = args["missing_checks"]
|
ctx.items_missing = args["missing_checks"]
|
||||||
|
ctx.items_checked = args["items_checked"]
|
||||||
|
|
||||||
elif cmd == 'ReceivedItems':
|
elif cmd == 'ReceivedItems':
|
||||||
start_index = args["index"]
|
start_index = args["index"]
|
||||||
|
@ -1052,30 +1068,17 @@ class ClientCommandProcessor(CommandProcessor):
|
||||||
"""List all missing location checks, from your local game state"""
|
"""List all missing location checks, from your local game state"""
|
||||||
count = 0
|
count = 0
|
||||||
checked_count = 0
|
checked_count = 0
|
||||||
for location in [k for k, v in Regions.location_table.items() if type(v[0]) is int]:
|
for location, location_id in Regions.lookup_name_to_id.items():
|
||||||
|
if location_id < 0:
|
||||||
|
continue
|
||||||
if location not in self.ctx.locations_checked:
|
if location not in self.ctx.locations_checked:
|
||||||
if location not in self.ctx.items_missing:
|
if location in self.ctx.items_missing:
|
||||||
self.output('Checked: ' + location)
|
|
||||||
checked_count += 1
|
|
||||||
else:
|
|
||||||
self.output('Missing: ' + location)
|
self.output('Missing: ' + location)
|
||||||
count += 1
|
|
||||||
|
|
||||||
key_drop_count = 0
|
|
||||||
for location in [k for k, v in Regions.key_drop_data.items()]:
|
|
||||||
if location not in self.ctx.items_missing:
|
|
||||||
key_drop_count += 1
|
|
||||||
|
|
||||||
# No point on reporting on missing key drop locations if the server doesn't declare ANY of them missing.
|
|
||||||
if key_drop_count != len(Regions.key_drop_data.items()):
|
|
||||||
for location in [k for k, v in Regions.key_drop_data.items()]:
|
|
||||||
if location not in self.ctx.locations_checked:
|
|
||||||
if location not in self.ctx.items_missing:
|
|
||||||
self.output('Checked: ' + location)
|
|
||||||
key_drop_count += 1
|
|
||||||
else:
|
|
||||||
self.output('Missing: ' + location)
|
|
||||||
count += 1
|
count += 1
|
||||||
|
elif self.ctx.items_checked is None or location in self.ctx.items_checked:
|
||||||
|
self.output('Checked: ' + location)
|
||||||
|
count += 1
|
||||||
|
checked_count += 1
|
||||||
|
|
||||||
if count:
|
if count:
|
||||||
self.output(
|
self.output(
|
||||||
|
@ -1109,16 +1112,6 @@ class ClientCommandProcessor(CommandProcessor):
|
||||||
else:
|
else:
|
||||||
self.output("Web UI was never started.")
|
self.output("Web UI was never started.")
|
||||||
|
|
||||||
def _cmd_send_unsafe(self, toggle: str = ""):
|
|
||||||
"""Force sending of locations the server did not specify was actually missing. WARNING: This may brick online trackers. Turned off on reconnect."""
|
|
||||||
if toggle:
|
|
||||||
self.ctx.send_unsafe = toggle.lower() in {"1", "true", "on"}
|
|
||||||
logger.info(
|
|
||||||
f'Turning {("on" if self.ctx.send_unsafe else "off")} the option to send ALL location checks to the multiserver.')
|
|
||||||
else:
|
|
||||||
logger.info("You must specify /send_unsafe true explicitly.")
|
|
||||||
self.ctx.send_unsafe = False
|
|
||||||
|
|
||||||
def default(self, raw: str):
|
def default(self, raw: str):
|
||||||
asyncio.create_task(self.ctx.send_msgs([['Say', {"text": raw}]]))
|
asyncio.create_task(self.ctx.send_msgs([['Say', {"text": raw}]]))
|
||||||
|
|
||||||
|
@ -1148,13 +1141,36 @@ async def track_locations(ctx: Context, roomid, roomdata):
|
||||||
new_locations = []
|
new_locations = []
|
||||||
|
|
||||||
def new_check(location):
|
def new_check(location):
|
||||||
ctx.unsafe_locations_checked.add(location)
|
ctx.locations_checked.add(location)
|
||||||
logging.info("New check: %s (%d/216)" % (location, len(ctx.unsafe_locations_checked)))
|
|
||||||
|
check = None
|
||||||
|
if ctx.items_checked is None:
|
||||||
|
check = f'New Check: {location} ({len(ctx.locations_checked)}/{len(Regions.lookup_name_to_id)})'
|
||||||
|
else:
|
||||||
|
items_total = len(ctx.items_missing) + len(ctx.items_checked)
|
||||||
|
if location in ctx.items_missing or location in ctx.items_checked:
|
||||||
|
ctx.locations_recognized.add(location)
|
||||||
|
check = f'New Check: {location} ({len(ctx.locations_recognized)}/{items_total})'
|
||||||
|
|
||||||
|
if check:
|
||||||
|
logger.info(check)
|
||||||
ctx.ui_node.send_location_check(ctx, location)
|
ctx.ui_node.send_location_check(ctx, location)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if roomid in location_shop_ids:
|
||||||
|
misc_data = await snes_read(ctx, SHOP_ADDR, (len(location_shop_order)*3)+5)
|
||||||
|
for cnt, b in enumerate(misc_data):
|
||||||
|
my_check = Shops.shop_table_by_location_id[Shops.SHOP_ID_START + cnt]
|
||||||
|
if int(b) > 0 and my_check not in ctx.locations_checked:
|
||||||
|
new_check(my_check)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
logger.info(f"Exception: {e}")
|
||||||
|
|
||||||
|
|
||||||
for location, (loc_roomid, loc_mask) in location_table_uw.items():
|
for location, (loc_roomid, loc_mask) in location_table_uw.items():
|
||||||
try:
|
try:
|
||||||
if location not in ctx.unsafe_locations_checked and loc_roomid == roomid and (
|
if location not in ctx.locations_checked and loc_roomid == roomid and (
|
||||||
roomdata << 4) & loc_mask != 0:
|
roomdata << 4) & loc_mask != 0:
|
||||||
new_check(location)
|
new_check(location)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
@ -1164,7 +1180,7 @@ async def track_locations(ctx: Context, roomid, roomdata):
|
||||||
ow_end = uw_end = 0
|
ow_end = uw_end = 0
|
||||||
uw_unchecked = {}
|
uw_unchecked = {}
|
||||||
for location, (roomid, mask) in location_table_uw.items():
|
for location, (roomid, mask) in location_table_uw.items():
|
||||||
if location not in ctx.unsafe_locations_checked:
|
if location not in ctx.locations_checked:
|
||||||
uw_unchecked[location] = (roomid, mask)
|
uw_unchecked[location] = (roomid, mask)
|
||||||
uw_begin = min(uw_begin, roomid)
|
uw_begin = min(uw_begin, roomid)
|
||||||
uw_end = max(uw_end, roomid + 1)
|
uw_end = max(uw_end, roomid + 1)
|
||||||
|
@ -1180,7 +1196,7 @@ async def track_locations(ctx: Context, roomid, roomdata):
|
||||||
ow_begin = 0x82
|
ow_begin = 0x82
|
||||||
ow_unchecked = {}
|
ow_unchecked = {}
|
||||||
for location, screenid in location_table_ow.items():
|
for location, screenid in location_table_ow.items():
|
||||||
if location not in ctx.unsafe_locations_checked:
|
if location not in ctx.locations_checked:
|
||||||
ow_unchecked[location] = screenid
|
ow_unchecked[location] = screenid
|
||||||
ow_begin = min(ow_begin, screenid)
|
ow_begin = min(ow_begin, screenid)
|
||||||
ow_end = max(ow_end, screenid + 1)
|
ow_end = max(ow_end, screenid + 1)
|
||||||
|
@ -1191,26 +1207,30 @@ async def track_locations(ctx: Context, roomid, roomdata):
|
||||||
if ow_data[screenid - ow_begin] & 0x40 != 0:
|
if ow_data[screenid - ow_begin] & 0x40 != 0:
|
||||||
new_check(location)
|
new_check(location)
|
||||||
|
|
||||||
if not all([location in ctx.unsafe_locations_checked for location in location_table_npc.keys()]):
|
if not all([location in ctx.locations_checked for location in location_table_npc.keys()]):
|
||||||
npc_data = await snes_read(ctx, SAVEDATA_START + 0x410, 2)
|
npc_data = await snes_read(ctx, SAVEDATA_START + 0x410, 2)
|
||||||
if npc_data is not None:
|
if npc_data is not None:
|
||||||
npc_value = npc_data[0] | (npc_data[1] << 8)
|
npc_value = npc_data[0] | (npc_data[1] << 8)
|
||||||
for location, mask in location_table_npc.items():
|
for location, mask in location_table_npc.items():
|
||||||
if npc_value & mask != 0 and location not in ctx.unsafe_locations_checked:
|
if npc_value & mask != 0 and location not in ctx.locations_checked:
|
||||||
new_check(location)
|
new_check(location)
|
||||||
|
|
||||||
if not all([location in ctx.unsafe_locations_checked for location in location_table_misc.keys()]):
|
if not all([location in ctx.locations_checked for location in location_table_misc.keys()]):
|
||||||
misc_data = await snes_read(ctx, SAVEDATA_START + 0x3c6, 4)
|
misc_data = await snes_read(ctx, SAVEDATA_START + 0x3c6, 4)
|
||||||
if misc_data is not None:
|
if misc_data is not None:
|
||||||
for location, (offset, mask) in location_table_misc.items():
|
for location, (offset, mask) in location_table_misc.items():
|
||||||
assert (0x3c6 <= offset <= 0x3c9)
|
assert (0x3c6 <= offset <= 0x3c9)
|
||||||
if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.unsafe_locations_checked:
|
if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.locations_checked:
|
||||||
new_check(location)
|
new_check(location)
|
||||||
|
|
||||||
for location in ctx.unsafe_locations_checked:
|
for location in ctx.locations_checked:
|
||||||
if (location in ctx.items_missing and location not in ctx.locations_checked) or ctx.send_unsafe:
|
try:
|
||||||
ctx.locations_checked.add(location)
|
my_id = Regions.lookup_name_to_id.get(location, Shops.shop_table_by_location.get(location, -1))
|
||||||
new_locations.append(Regions.lookup_name_to_id[location])
|
new_locations.append(my_id)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
logger.info(f"Exception: {e}")
|
||||||
|
|
||||||
|
|
||||||
if new_locations:
|
if new_locations:
|
||||||
await ctx.send_msgs([['LocationChecks', {"locations": new_locations}]])
|
await ctx.send_msgs([['LocationChecks', {"locations": new_locations}]])
|
||||||
|
@ -1243,7 +1263,6 @@ async def game_watcher(ctx: Context):
|
||||||
ctx.rom = rom
|
ctx.rom = rom
|
||||||
if not ctx.prev_rom or ctx.prev_rom != ctx.rom:
|
if not ctx.prev_rom or ctx.prev_rom != ctx.rom:
|
||||||
ctx.locations_checked = set()
|
ctx.locations_checked = set()
|
||||||
ctx.unsafe_locations_checked = set()
|
|
||||||
ctx.locations_scouted = set()
|
ctx.locations_scouted = set()
|
||||||
ctx.prev_rom = ctx.rom
|
ctx.prev_rom = ctx.rom
|
||||||
|
|
||||||
|
@ -1405,7 +1424,7 @@ async def main():
|
||||||
adjustedromfile, adjusted = Utils.get_adjuster_settings(romfile)
|
adjustedromfile, adjusted = Utils.get_adjuster_settings(romfile)
|
||||||
if adjusted:
|
if adjusted:
|
||||||
try:
|
try:
|
||||||
os.replace(adjustedromfile, romfile)
|
shutil.move(adjustedromfile, romfile)
|
||||||
adjustedromfile = romfile
|
adjustedromfile = romfile
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(e)
|
logging.exception(e)
|
||||||
|
|
|
@ -32,7 +32,7 @@ from Utils import get_item_name_from_id, get_location_name_from_address, \
|
||||||
from NetUtils import Node, Endpoint, CLIENT_GOAL
|
from NetUtils import Node, Endpoint, CLIENT_GOAL
|
||||||
|
|
||||||
colorama.init()
|
colorama.init()
|
||||||
console_names = frozenset(set(Items.item_table) | set(Regions.location_table) | set(Items.item_name_groups) | set(Regions.key_drop_data))
|
console_names = frozenset(set(Items.item_table) | set(Items.item_name_groups) | set(Regions.lookup_name_to_id))
|
||||||
|
|
||||||
|
|
||||||
class Client(Endpoint):
|
class Client(Endpoint):
|
||||||
|
@ -416,9 +416,10 @@ async def countdown(ctx: Context, timer):
|
||||||
ctx.notify_all(f'[Server]: GO')
|
ctx.notify_all(f'[Server]: GO')
|
||||||
ctx.countdown_timer = 0
|
ctx.countdown_timer = 0
|
||||||
|
|
||||||
async def missing(ctx: Context, client: Client, locations: list):
|
async def missing(ctx: Context, client: Client, locations: list, checked_locations: list):
|
||||||
await ctx.send_msgs(client, [['Missing', {
|
await ctx.send_msgs(client, [['Missing', {
|
||||||
'locations': dumps(locations)
|
'locations': dumps(locations),
|
||||||
|
'checked_locations': json.dumps(checked_locations)
|
||||||
}]])
|
}]])
|
||||||
|
|
||||||
|
|
||||||
|
@ -827,6 +828,7 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||||
"""List all missing location checks from the server's perspective"""
|
"""List all missing location checks from the server's perspective"""
|
||||||
|
|
||||||
locations = get_missing_checks(self.ctx, self.client)
|
locations = get_missing_checks(self.ctx, self.client)
|
||||||
|
checked_locations = get_checked_checks(self.ctx, self.client)
|
||||||
|
|
||||||
if len(locations) > 0:
|
if len(locations) > 0:
|
||||||
texts = [f'Missing: {location}\n' for location in locations]
|
texts = [f'Missing: {location}\n' for location in locations]
|
||||||
|
@ -952,6 +954,13 @@ class ClientMessageProcessor(CommonCommandProcessor):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_checked_checks(ctx: Context, client: Client) -> list:
|
||||||
|
return [Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for
|
||||||
|
location_id, slot in ctx.locations if
|
||||||
|
slot == client.slot and
|
||||||
|
location_id in ctx.location_checks[client.team, client.slot]]
|
||||||
|
|
||||||
|
|
||||||
def get_missing_checks(ctx: Context, client: Client) -> list:
|
def get_missing_checks(ctx: Context, client: Client) -> list:
|
||||||
return [Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for
|
return [Regions.lookup_id_to_name.get(location_id, f'Unknown Location ID: {location_id}') for
|
||||||
location_id, slot in ctx.locations if
|
location_id, slot in ctx.locations if
|
||||||
|
@ -1024,7 +1033,8 @@ async def process_client_cmd(ctx: Context, client: Client, cmd: str, args: typin
|
||||||
reply = [['Connected', {"team": client.team, "slot": client.slot,
|
reply = [['Connected', {"team": client.team, "slot": client.slot,
|
||||||
"playernames": [(p, ctx.get_aliased_name(t, p)) for (t, p), n in
|
"playernames": [(p, ctx.get_aliased_name(t, p)) for (t, p), n in
|
||||||
ctx.player_names.items() if t == client.team],
|
ctx.player_names.items() if t == client.team],
|
||||||
"missing_checks": get_missing_checks(ctx, client)}]]
|
"missing_checks": get_missing_checks(ctx, client),
|
||||||
|
"items_checked": get_checked_checks(ctx, client)}]]
|
||||||
items = get_received_items(ctx, client.team, client.slot)
|
items = get_received_items(ctx, client.team, client.slot)
|
||||||
if items:
|
if items:
|
||||||
reply.append(['ReceivedItems', {"index": 0, "items": tuplize_received_items(items)}])
|
reply.append(['ReceivedItems', {"index": 0, "items": tuplize_received_items(items)}])
|
||||||
|
|
16
Mystery.py
16
Mystery.py
|
@ -379,7 +379,7 @@ def roll_settings(weights, plando_options: typing.Set[str] = frozenset(("bosses"
|
||||||
|
|
||||||
# TODO consider moving open_pyramid to an automatic variable in the core roller, set to True when
|
# TODO consider moving open_pyramid to an automatic variable in the core roller, set to True when
|
||||||
# fast ganon + ganon at hole
|
# fast ganon + ganon at hole
|
||||||
ret.open_pyramid = ret.goal in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'}
|
ret.open_pyramid = get_choice('open_pyramid', weights, 'goal')
|
||||||
|
|
||||||
ret.crystals_gt = prefer_int(get_choice('tower_open', weights))
|
ret.crystals_gt = prefer_int(get_choice('tower_open', weights))
|
||||||
|
|
||||||
|
@ -405,6 +405,8 @@ def roll_settings(weights, plando_options: typing.Set[str] = frozenset(("bosses"
|
||||||
# change minimum to required pieces to avoid problems
|
# change minimum to required pieces to avoid problems
|
||||||
ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90)
|
ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90)
|
||||||
|
|
||||||
|
ret.shop_shuffle_slots = int(get_choice('shop_shuffle_slots', weights, '0'))
|
||||||
|
|
||||||
ret.shop_shuffle = get_choice('shop_shuffle', weights, '')
|
ret.shop_shuffle = get_choice('shop_shuffle', weights, '')
|
||||||
if not ret.shop_shuffle:
|
if not ret.shop_shuffle:
|
||||||
ret.shop_shuffle = ''
|
ret.shop_shuffle = ''
|
||||||
|
@ -510,11 +512,15 @@ def roll_settings(weights, plando_options: typing.Set[str] = frozenset(("bosses"
|
||||||
|
|
||||||
ret.shuffle_prizes = get_choice('shuffle_prizes', weights, "g")
|
ret.shuffle_prizes = get_choice('shuffle_prizes', weights, "g")
|
||||||
|
|
||||||
ret.required_medallions = (get_choice("misery_mire_medallion", weights, "random"),
|
ret.required_medallions = [get_choice("misery_mire_medallion", weights, "random"),
|
||||||
get_choice("turtle_rock_medallion", weights, "random"))
|
get_choice("turtle_rock_medallion", weights, "random")]
|
||||||
for medallion in ret.required_medallions:
|
|
||||||
if medallion not in {"random", "Ether", "Bombos", "Quake"}:
|
for index, medallion in enumerate(ret.required_medallions):
|
||||||
|
ret.required_medallions[index] = {"ether": "Ether", "quake": "Quake", "bombos": "Bombos", "random": "random"}\
|
||||||
|
.get(medallion.lower(), None)
|
||||||
|
if not ret.required_medallions[index]:
|
||||||
raise Exception(f"unknown Medallion {medallion}")
|
raise Exception(f"unknown Medallion {medallion}")
|
||||||
|
|
||||||
inventoryweights = weights.get('startinventory', {})
|
inventoryweights = weights.get('startinventory', {})
|
||||||
startitems = []
|
startitems = []
|
||||||
for item in inventoryweights.keys():
|
for item in inventoryweights.keys():
|
||||||
|
|
|
@ -0,0 +1,440 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from enum import unique, Enum
|
||||||
|
from typing import List, Union, Optional, Set, NamedTuple, Dict
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from BaseClasses import Location
|
||||||
|
from EntranceShuffle import door_addresses
|
||||||
|
from Items import item_name_groups, item_table, ItemFactory, trap_replaceable
|
||||||
|
from Utils import int16_as_bytes
|
||||||
|
|
||||||
|
logger = logging.getLogger("Shops")
|
||||||
|
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class ShopType(Enum):
|
||||||
|
Shop = 0
|
||||||
|
TakeAny = 1
|
||||||
|
UpgradeShop = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Shop():
|
||||||
|
slots: int = 3 # slot count is not dynamic in asm, however inventory can have None as empty slots
|
||||||
|
blacklist: Set[str] = set() # items that don't work, todo: actually check against this
|
||||||
|
type = ShopType.Shop
|
||||||
|
|
||||||
|
def __init__(self, region, room_id: int, shopkeeper_config: int, custom: bool, locked: bool, sram_offset: int):
|
||||||
|
self.region = region
|
||||||
|
self.room_id = room_id
|
||||||
|
self.inventory: List[Optional[dict]] = [None] * self.slots
|
||||||
|
self.shopkeeper_config = shopkeeper_config
|
||||||
|
self.custom = custom
|
||||||
|
self.locked = locked
|
||||||
|
self.sram_offset = sram_offset
|
||||||
|
|
||||||
|
@property
|
||||||
|
def item_count(self) -> int:
|
||||||
|
for x in range(self.slots - 1, -1, -1): # last x is 0
|
||||||
|
if self.inventory[x]:
|
||||||
|
return x + 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_bytes(self) -> List[int]:
|
||||||
|
# [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index]
|
||||||
|
entrances = self.region.entrances
|
||||||
|
config = self.item_count
|
||||||
|
if len(entrances) == 1 and entrances[0].name in door_addresses:
|
||||||
|
door_id = door_addresses[entrances[0].name][0] + 1
|
||||||
|
else:
|
||||||
|
door_id = 0
|
||||||
|
config |= 0x40 # ignore door id
|
||||||
|
if self.type == ShopType.TakeAny:
|
||||||
|
config |= 0x80
|
||||||
|
elif self.type == ShopType.UpgradeShop:
|
||||||
|
config |= 0x10 # Alt. VRAM
|
||||||
|
return [0x00] + int16_as_bytes(self.room_id) + [door_id, 0x00, config, self.shopkeeper_config, 0x00]
|
||||||
|
|
||||||
|
def has_unlimited(self, item: str) -> bool:
|
||||||
|
for inv in self.inventory:
|
||||||
|
if inv is None:
|
||||||
|
continue
|
||||||
|
if inv['max']:
|
||||||
|
if inv['replacement'] == item:
|
||||||
|
return True
|
||||||
|
elif inv['item'] == item:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def has(self, item: str) -> bool:
|
||||||
|
for inv in self.inventory:
|
||||||
|
if inv is None:
|
||||||
|
continue
|
||||||
|
if inv['item'] == item:
|
||||||
|
return True
|
||||||
|
if inv['replacement'] == item:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clear_inventory(self):
|
||||||
|
self.inventory = [None] * self.slots
|
||||||
|
|
||||||
|
def add_inventory(self, slot: int, item: str, price: int, max: int = 0,
|
||||||
|
replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False,
|
||||||
|
player: int = 0):
|
||||||
|
self.inventory[slot] = {
|
||||||
|
'item': item,
|
||||||
|
'price': price,
|
||||||
|
'max': max,
|
||||||
|
'replacement': replacement,
|
||||||
|
'replacement_price': replacement_price,
|
||||||
|
'create_location': create_location,
|
||||||
|
'player': player
|
||||||
|
}
|
||||||
|
|
||||||
|
def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0):
|
||||||
|
if not self.inventory[slot]:
|
||||||
|
raise ValueError("Inventory can't be pushed back if it doesn't exist")
|
||||||
|
|
||||||
|
if not self.can_push_inventory(slot):
|
||||||
|
logging.warning(f'Warning, there is already an item pushed into this slot.')
|
||||||
|
|
||||||
|
self.inventory[slot] = {
|
||||||
|
'item': item,
|
||||||
|
'price': price,
|
||||||
|
'max': max,
|
||||||
|
'replacement': self.inventory[slot]["item"],
|
||||||
|
'replacement_price': self.inventory[slot]["price"],
|
||||||
|
'create_location': self.inventory[slot]["create_location"],
|
||||||
|
'player': player
|
||||||
|
}
|
||||||
|
|
||||||
|
def can_push_inventory(self, slot: int):
|
||||||
|
return self.inventory[slot] and not self.inventory[slot]["replacement"]
|
||||||
|
|
||||||
|
|
||||||
|
class TakeAny(Shop):
|
||||||
|
type = ShopType.TakeAny
|
||||||
|
|
||||||
|
|
||||||
|
class UpgradeShop(Shop):
|
||||||
|
type = ShopType.UpgradeShop
|
||||||
|
# Potions break due to VRAM flags set in UpgradeShop.
|
||||||
|
# Didn't check for more things breaking as not much else can be shuffled here currently
|
||||||
|
blacklist = item_name_groups["Potions"]
|
||||||
|
|
||||||
|
|
||||||
|
shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
||||||
|
ShopType.Shop: Shop,
|
||||||
|
ShopType.TakeAny: TakeAny}
|
||||||
|
|
||||||
|
|
||||||
|
def FillDisabledShopSlots(world):
|
||||||
|
shop_slots: Set[Location] = {location for shop_locations in (shop.region.locations for shop in world.shops)
|
||||||
|
for location in shop_locations if location.shop_slot and location.shop_slot_disabled}
|
||||||
|
for location in shop_slots:
|
||||||
|
location.shop_slot_disabled = True
|
||||||
|
slot_num = int(location.name[-1]) - 1
|
||||||
|
shop: Shop = location.parent_region.shop
|
||||||
|
location.item = ItemFactory(shop.inventory[slot_num]['item'], location.player)
|
||||||
|
location.item_rule = lambda item: item.name == location.item.name and item.player == location.player
|
||||||
|
|
||||||
|
|
||||||
|
def ShopSlotFill(world):
|
||||||
|
shop_slots: Set[Location] = {location for shop_locations in (shop.region.locations for shop in world.shops)
|
||||||
|
for location in shop_locations if location.shop_slot}
|
||||||
|
removed = set()
|
||||||
|
for location in shop_slots:
|
||||||
|
slot_num = int(location.name[-1]) - 1
|
||||||
|
shop: Shop = location.parent_region.shop
|
||||||
|
if not shop.can_push_inventory(slot_num) or location.shop_slot_disabled:
|
||||||
|
location.shop_slot_disabled = True
|
||||||
|
removed.add(location)
|
||||||
|
|
||||||
|
if removed:
|
||||||
|
shop_slots -= removed
|
||||||
|
|
||||||
|
if shop_slots:
|
||||||
|
from Fill import swap_location_item
|
||||||
|
# TODO: allow each game to register a blacklist to be used here?
|
||||||
|
blacklist_words = {"Rupee"}
|
||||||
|
blacklist_words = {item_name for item_name in item_table if any(
|
||||||
|
blacklist_word in item_name for blacklist_word in blacklist_words)}
|
||||||
|
blacklist_words.add("Bee")
|
||||||
|
candidates_per_sphere = list(list(sphere) for sphere in world.get_spheres())
|
||||||
|
|
||||||
|
candidate_condition = lambda location: not location.locked and \
|
||||||
|
not location.shop_slot and \
|
||||||
|
not location.item.name in blacklist_words
|
||||||
|
|
||||||
|
# currently special care needs to be taken so that Shop.region.locations.item is identical to Shop.inventory
|
||||||
|
# Potentially create Locations as needed and make inventory the only source, to prevent divergence
|
||||||
|
cumu_weights = []
|
||||||
|
|
||||||
|
for sphere in candidates_per_sphere:
|
||||||
|
if cumu_weights:
|
||||||
|
x = cumu_weights[-1]
|
||||||
|
else:
|
||||||
|
x = 0
|
||||||
|
cumu_weights.append(len(sphere) + x)
|
||||||
|
world.random.shuffle(sphere)
|
||||||
|
|
||||||
|
for i, sphere in enumerate(candidates_per_sphere):
|
||||||
|
current_shop_slots = [location for location in sphere if location.shop_slot and not location.shop_slot_disabled]
|
||||||
|
if current_shop_slots:
|
||||||
|
|
||||||
|
for location in current_shop_slots:
|
||||||
|
shop: Shop = location.parent_region.shop
|
||||||
|
# TODO: might need to implement trying randomly across spheres until canditates are exhausted.
|
||||||
|
# As spheres may be as small as one item.
|
||||||
|
swapping_sphere = world.random.choices(candidates_per_sphere[i:], cum_weights=cumu_weights[i:])[0]
|
||||||
|
for c in swapping_sphere: # chosen item locations
|
||||||
|
if candidate_condition(c) and c.item_rule(location.item) and location.item_rule(c.item):
|
||||||
|
swap_location_item(c, location, check_locked=False)
|
||||||
|
logger.debug(f'Swapping {c} into {location}:: {location.item}')
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
# This *should* never happen. But let's fail safely just in case.
|
||||||
|
logger.warning("Ran out of ShopShuffle Item candidate locations.")
|
||||||
|
location.shop_slot_disabled = True
|
||||||
|
continue
|
||||||
|
item_name = location.item.name
|
||||||
|
if any(x in item_name for x in ['Single Bomb', 'Single Arrow', 'Piece of Heart']):
|
||||||
|
price = world.random.randrange(1, 7)
|
||||||
|
elif any(x in item_name for x in ['Arrows', 'Bombs', 'Clock', 'Heart']):
|
||||||
|
price = world.random.randrange(4, 24)
|
||||||
|
elif any(x in item_name for x in ['Compass', 'Map', 'Small Key']):
|
||||||
|
price = world.random.randrange(10, 30)
|
||||||
|
else:
|
||||||
|
price = world.random.randrange(10, 60)
|
||||||
|
|
||||||
|
price *= 5
|
||||||
|
shop.push_inventory(int(location.name[-1]) - 1, item_name, price, 1,
|
||||||
|
location.item.player if location.item.player != location.player else 0)
|
||||||
|
|
||||||
|
|
||||||
|
def create_shops(world, player: int):
|
||||||
|
option = world.shop_shuffle[player]
|
||||||
|
|
||||||
|
player_shop_table = shop_table.copy()
|
||||||
|
if "w" in option:
|
||||||
|
player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False)
|
||||||
|
dynamic_shop_slots = total_dynamic_shop_slots + 3
|
||||||
|
else:
|
||||||
|
dynamic_shop_slots = total_dynamic_shop_slots
|
||||||
|
|
||||||
|
num_slots = min(dynamic_shop_slots, max(0, int(world.shop_shuffle_slots[player]))) # 0 to 30
|
||||||
|
single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots)
|
||||||
|
world.random.shuffle(single_purchase_slots)
|
||||||
|
|
||||||
|
if 'g' in option or 'f' in option:
|
||||||
|
default_shop_table = [i for l in [shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if not world.retro[player] or x != 'arrows'] for i in l]
|
||||||
|
new_basic_shop = world.random.sample(default_shop_table, k=3)
|
||||||
|
new_dark_shop = world.random.sample(default_shop_table, k=3)
|
||||||
|
for name, shop in player_shop_table.items():
|
||||||
|
typ, shop_id, keeper, custom, locked, items, sram_offset = shop
|
||||||
|
if not locked:
|
||||||
|
new_items = world.random.sample(default_shop_table, k=3)
|
||||||
|
if 'f' not in option:
|
||||||
|
if items == _basic_shop_defaults:
|
||||||
|
new_items = new_basic_shop
|
||||||
|
elif items == _dark_world_shop_defaults:
|
||||||
|
new_items = new_dark_shop
|
||||||
|
keeper = world.random.choice([0xA0, 0xC1, 0xFF])
|
||||||
|
player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset)
|
||||||
|
if world.mode[player] == "inverted":
|
||||||
|
player_shop_table["Dark Lake Hylia Shop"] = \
|
||||||
|
player_shop_table["Dark Lake Hylia Shop"]._replace(locked=True, items=_inverted_hylia_shop_defaults)
|
||||||
|
for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram_offset) in player_shop_table.items():
|
||||||
|
region = world.get_region(region_name, player)
|
||||||
|
shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked, sram_offset)
|
||||||
|
region.shop = shop
|
||||||
|
world.shops.append(shop)
|
||||||
|
for index, item in enumerate(inventory):
|
||||||
|
shop.add_inventory(index, *item)
|
||||||
|
if not locked and num_slots:
|
||||||
|
slot_name = "{} Slot {}".format(region.name, index + 1)
|
||||||
|
loc = Location(player, slot_name, address=shop_table_by_location[slot_name],
|
||||||
|
parent=region, hint_text="for sale")
|
||||||
|
loc.shop_slot = True
|
||||||
|
loc.locked = True
|
||||||
|
if single_purchase_slots.pop():
|
||||||
|
if world.goal[player] != 'icerodhunt':
|
||||||
|
additional_item = 'Rupees (50)' # world.random.choice(['Rupees (50)', 'Rupees (100)', 'Rupees (300)'])
|
||||||
|
else:
|
||||||
|
additional_item = 'Nothing'
|
||||||
|
loc.item = ItemFactory(additional_item, player)
|
||||||
|
else:
|
||||||
|
loc.item = ItemFactory('Nothing', player)
|
||||||
|
loc.shop_slot_disabled = True
|
||||||
|
shop.region.locations.append(loc)
|
||||||
|
world.dynamic_locations.append(loc)
|
||||||
|
world.clear_location_cache()
|
||||||
|
|
||||||
|
|
||||||
|
class ShopData(NamedTuple):
|
||||||
|
room: int
|
||||||
|
type: ShopType
|
||||||
|
shopkeeper: int
|
||||||
|
custom: bool
|
||||||
|
locked: bool
|
||||||
|
items: List
|
||||||
|
sram_offset: int
|
||||||
|
|
||||||
|
|
||||||
|
# (type, room_id, shopkeeper, custom, locked, [items], sram_offset)
|
||||||
|
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
||||||
|
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
||||||
|
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||||
|
_inverted_hylia_shop_defaults = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||||
|
shop_table: Dict[str, ShopData] = {
|
||||||
|
'Cave Shop (Dark Death Mountain)': ShopData(0x0112, ShopType.Shop, 0xC1, True, False, _basic_shop_defaults, 0),
|
||||||
|
'Red Shield Shop': ShopData(0x0110, ShopType.Shop, 0xC1, True, False,
|
||||||
|
[('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)], 3),
|
||||||
|
'Dark Lake Hylia Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 6),
|
||||||
|
'Dark World Lumberjack Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 9),
|
||||||
|
'Village of Outcasts Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 12),
|
||||||
|
'Dark World Potion Shop': ShopData(0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults, 15),
|
||||||
|
'Light World Death Mountain Shop': ShopData(0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults, 18),
|
||||||
|
'Kakariko Shop': ShopData(0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults, 21),
|
||||||
|
'Cave Shop (Lake Hylia)': ShopData(0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults, 24),
|
||||||
|
'Potion Shop': ShopData(0x0109, ShopType.Shop, 0xA0, True, True,
|
||||||
|
[('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)], 27),
|
||||||
|
'Capacity Upgrade': ShopData(0x0115, ShopType.UpgradeShop, 0x04, True, True,
|
||||||
|
[('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)], 30)
|
||||||
|
}
|
||||||
|
|
||||||
|
total_shop_slots = len(shop_table) * 3
|
||||||
|
total_dynamic_shop_slots = sum(3 for shopname, data in shop_table.items() if not data[4]) # data[4] -> locked
|
||||||
|
|
||||||
|
SHOP_ID_START = 0x400000
|
||||||
|
shop_table_by_location_id = {cnt: s for cnt, s in enumerate(
|
||||||
|
(f"{name} Slot {num}" for name in [key for key, value in sorted(shop_table.items(), key=lambda item: item[1].sram_offset)]
|
||||||
|
for num in range(1, 4)), start=SHOP_ID_START)}
|
||||||
|
shop_table_by_location_id[(SHOP_ID_START + total_shop_slots)] = "Old Man Sword Cave"
|
||||||
|
shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 1)] = "Take-Any #1"
|
||||||
|
shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 2)] = "Take-Any #2"
|
||||||
|
shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 3)] = "Take-Any #3"
|
||||||
|
shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 4)] = "Take-Any #4"
|
||||||
|
shop_table_by_location = {y: x for x, y in shop_table_by_location_id.items()}
|
||||||
|
|
||||||
|
shop_generation_types = {
|
||||||
|
'arrows': [('Single Arrow', 5), ('Arrows (10)', 50)],
|
||||||
|
'bombs': [('Single Bomb', 10), ('Bombs (3)', 30), ('Bombs (10)', 50)],
|
||||||
|
'shields': [('Red Shield', 500), ('Blue Shield', 50)],
|
||||||
|
'potions': [('Red Potion', 150), ('Green Potion', 90), ('Blue Potion', 190)],
|
||||||
|
'discount_potions': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
|
||||||
|
'bottle': [('Small Heart', 10), ('Apple', 50), ('Bee', 10), ('Good Bee', 100), ('Faerie', 100), ('Magic Jar', 100)],
|
||||||
|
'time': [('Red Clock', 100), ('Blue Clock', 200), ('Green Clock', 300)],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def set_up_shops(world, player: int):
|
||||||
|
# TODO: move hard+ mode changes for shields here, utilizing the new shops
|
||||||
|
|
||||||
|
if world.retro[player]:
|
||||||
|
rss = world.get_region('Red Shield Shop', player).shop
|
||||||
|
replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50],
|
||||||
|
['Blue Shield', 50], ['Small Heart', 10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them.
|
||||||
|
if world.keyshuffle[player] == "universal":
|
||||||
|
replacement_items.append(['Small Key (Universal)', 100])
|
||||||
|
replacement_item = world.random.choice(replacement_items)
|
||||||
|
rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1])
|
||||||
|
rss.locked = True
|
||||||
|
|
||||||
|
if world.keyshuffle[player] == "universal" or world.retro[player]:
|
||||||
|
for shop in world.random.sample([s for s in world.shops if
|
||||||
|
s.custom and not s.locked and s.type == ShopType.Shop and s.region.player == player],
|
||||||
|
5):
|
||||||
|
shop.locked = True
|
||||||
|
slots = [0, 1, 2]
|
||||||
|
world.random.shuffle(slots)
|
||||||
|
slots = iter(slots)
|
||||||
|
if world.keyshuffle[player] == "universal":
|
||||||
|
shop.add_inventory(next(slots), 'Small Key (Universal)', 100)
|
||||||
|
if world.retro[player]:
|
||||||
|
shop.push_inventory(next(slots), 'Single Arrow', 80)
|
||||||
|
|
||||||
|
|
||||||
|
def shuffle_shops(world, items, player: int):
|
||||||
|
option = world.shop_shuffle[player]
|
||||||
|
if 'u' in option:
|
||||||
|
progressive = world.progressive[player]
|
||||||
|
progressive = world.random.choice([True, False]) if progressive == 'random' else progressive == 'on'
|
||||||
|
progressive &= world.goal == 'icerodhunt'
|
||||||
|
new_items = ["Bomb Upgrade (+5)"] * 6
|
||||||
|
new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
|
||||||
|
|
||||||
|
if not world.retro[player]:
|
||||||
|
new_items += ["Arrow Upgrade (+5)"] * 6
|
||||||
|
new_items.append("Arrow Upgrade (+5)" if progressive else "Arrow Upgrade (+10)")
|
||||||
|
|
||||||
|
world.random.shuffle(new_items) # Decide what gets tossed randomly if it can't insert everything.
|
||||||
|
|
||||||
|
capacityshop: Optional[Shop] = None
|
||||||
|
for shop in world.shops:
|
||||||
|
if shop.type == ShopType.UpgradeShop and shop.region.player == player and \
|
||||||
|
shop.region.name == "Capacity Upgrade":
|
||||||
|
shop.clear_inventory()
|
||||||
|
capacityshop = shop
|
||||||
|
|
||||||
|
if world.goal[player] != 'icerodhunt':
|
||||||
|
for i, item in enumerate(items):
|
||||||
|
if item.name in trap_replaceable:
|
||||||
|
items[i] = ItemFactory(new_items.pop(), player)
|
||||||
|
if not new_items:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
logging.warning(f"Not all upgrades put into Player{player}' item pool. Putting remaining items in Capacity Upgrade shop instead.")
|
||||||
|
bombupgrades = sum(1 for item in new_items if 'Bomb Upgrade' in item)
|
||||||
|
arrowupgrades = sum(1 for item in new_items if 'Arrow Upgrade' in item)
|
||||||
|
if bombupgrades:
|
||||||
|
capacityshop.add_inventory(1, 'Bomb Upgrade (+5)', 100, bombupgrades)
|
||||||
|
if arrowupgrades:
|
||||||
|
capacityshop.add_inventory(1, 'Arrow Upgrade (+5)', 100, arrowupgrades)
|
||||||
|
else:
|
||||||
|
for item in new_items:
|
||||||
|
world.push_precollected(ItemFactory(item, player))
|
||||||
|
|
||||||
|
if 'p' in option or 'i' in option:
|
||||||
|
shops = []
|
||||||
|
upgrade_shops = []
|
||||||
|
total_inventory = []
|
||||||
|
for shop in world.shops:
|
||||||
|
if shop.region.player == player:
|
||||||
|
if shop.type == ShopType.UpgradeShop:
|
||||||
|
upgrade_shops.append(shop)
|
||||||
|
elif shop.type == ShopType.Shop:
|
||||||
|
if shop.region.name == 'Potion Shop' and not 'w' in option:
|
||||||
|
# don't modify potion shop
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
shops.append(shop)
|
||||||
|
total_inventory.extend(shop.inventory)
|
||||||
|
|
||||||
|
if 'p' in option:
|
||||||
|
def price_adjust(price: int) -> int:
|
||||||
|
# it is important that a base price of 0 always returns 0 as new price!
|
||||||
|
adjust = 2 if price < 100 else 5
|
||||||
|
return int((price / adjust) * (0.5 + world.random.random() * 1.5)) * adjust
|
||||||
|
|
||||||
|
def adjust_item(item):
|
||||||
|
if item:
|
||||||
|
item["price"] = price_adjust(item["price"])
|
||||||
|
item['replacement_price'] = price_adjust(item["price"])
|
||||||
|
|
||||||
|
for item in total_inventory:
|
||||||
|
adjust_item(item)
|
||||||
|
for shop in upgrade_shops:
|
||||||
|
for item in shop.inventory:
|
||||||
|
adjust_item(item)
|
||||||
|
|
||||||
|
if 'i' in option:
|
||||||
|
world.random.shuffle(total_inventory)
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
for shop in shops:
|
||||||
|
slots = shop.slots
|
||||||
|
shop.inventory = total_inventory[i:i + slots]
|
||||||
|
i += slots
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
1. Plando features have to be enabled first, before they can be used (opt-in).
|
1. Plando features have to be enabled first, before they can be used (opt-in).
|
||||||
2. To do so, go to your installation directory (Windows default: C:\ProgramData\BerserkerMultiWorld),
|
2. To do so, go to your installation directory (Windows default: `C:\ProgramData\BerserkerMultiWorld`),
|
||||||
then open the host.yaml file therein with a text editor.
|
then open the host.yaml file with a text editor.
|
||||||
3. In it, you're looking for the option key "plando_options",
|
3. In it, you're looking for the option key `plando_options`. To enable all plando modules you can set the
|
||||||
to enable all plando modules you can set the value to "bosses, items, texts, connections"
|
value to
|
||||||
|
`bosses, items, texts, connections`
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
|
@ -19,20 +20,21 @@
|
||||||
- Instructions are separated by a semicolon
|
- Instructions are separated by a semicolon
|
||||||
- Available Instructions:
|
- Available Instructions:
|
||||||
- Direct Placement:
|
- Direct Placement:
|
||||||
- Example: "Eastern Palace-Trinexx"
|
- Example: `Eastern Palace-Trinexx`
|
||||||
- Takes a particular Arena and particular boss, then places that boss into that arena
|
- Takes a particular Arena and particular boss, then places that boss into that arena
|
||||||
- Ganons Tower has 3 placements, "Ganons Tower Top", "Ganons Tower Middle" and "Ganons Tower Bottom"
|
- Ganons Tower has 3 placements, `Ganons Tower Top`, `Ganons Tower Middle` and `Ganons Tower Bottom`
|
||||||
- Boss Placement:
|
- Boss Placement:
|
||||||
- Example: "Trinexx"
|
- Example: `Trinexx`
|
||||||
- Takes a particular boss and places that boss in any remaining slots in which this boss can function.
|
- Takes a particular boss and places that boss in any remaining slots in which this boss can function.
|
||||||
- In this example, it would fill Desert Palace, but not Tower of Hera.
|
- In this example, it would fill Desert Palace, but not Tower of Hera.
|
||||||
- Boss Shuffle:
|
- Boss Shuffle:
|
||||||
- Example: "simple"
|
- Example: `simple`
|
||||||
- Runs a particular boss shuffle mode to finish construction instead of vanilla placement, typically used as a last instruction.
|
- Runs a particular boss shuffle mode to finish construction instead of vanilla placement, typically used as
|
||||||
|
a last instruction.
|
||||||
- [Available Bosses](https://github.com/Berserker66/MultiWorld-Utilities/blob/65fa39df95c90c9b66141aee8b16b7e560d00819/Bosses.py#L135)
|
- [Available Bosses](https://github.com/Berserker66/MultiWorld-Utilities/blob/65fa39df95c90c9b66141aee8b16b7e560d00819/Bosses.py#L135)
|
||||||
- [Available Arenas](https://github.com/Berserker66/MultiWorld-Utilities/blob/65fa39df95c90c9b66141aee8b16b7e560d00819/Bosses.py#L186)
|
- [Available Arenas](https://github.com/Berserker66/MultiWorld-Utilities/blob/65fa39df95c90c9b66141aee8b16b7e560d00819/Bosses.py#L186)
|
||||||
|
|
||||||
#### Examples:
|
#### Examples
|
||||||
```yaml
|
```yaml
|
||||||
boss_shuffle:
|
boss_shuffle:
|
||||||
Turtle Rock-Trinexx;basic: 1
|
Turtle Rock-Trinexx;basic: 1
|
||||||
|
@ -62,10 +64,10 @@ boss_shuffle:
|
||||||
- can be true, to target any other player's world
|
- can be true, to target any other player's world
|
||||||
- can be false, to target own world and is the default
|
- can be false, to target own world and is the default
|
||||||
- can be null, to target a random world
|
- can be null, to target a random world
|
||||||
- force is either "silent", "true" or "false".
|
- force is either `silent`, `true` or `false`.
|
||||||
- "true" means the item has to be placed, or the generator aborts with an exception.
|
- `true` means the item has to be placed, or the generator aborts with an exception.
|
||||||
- "false" means the generator logs a warning if the placement can't be done.
|
- `false` means the generator logs a warning if the placement can't be done.
|
||||||
- "silent" means that this entry is entirely ignored if the placement fails and is the default.
|
- `silent` means that this entry is entirely ignored if the placement fails and is the default.
|
||||||
- Single Placement
|
- Single Placement
|
||||||
- place a single item at a single location
|
- place a single item at a single location
|
||||||
- item denotes the Item to place
|
- item denotes the Item to place
|
||||||
|
@ -75,20 +77,22 @@ boss_shuffle:
|
||||||
- items denotes the items to use, can be given a number to have multiple of that item
|
- items denotes the items to use, can be given a number to have multiple of that item
|
||||||
- locations lists the possible locations those items can be placed in
|
- locations lists the possible locations those items can be placed in
|
||||||
- placements are picked randomly, not sorted in any way
|
- placements are picked randomly, not sorted in any way
|
||||||
|
- Warning: Placing non-Dungeon Prizes on Prize locations and
|
||||||
|
Prizes on non-Prize locations will break the game in various ways.
|
||||||
- [Available Items](https://github.com/Berserker66/MultiWorld-Utilities/blob/3b5ba161dea223b96e9b1fc890e03469d9c6eb59/Items.py#L26)
|
- [Available Items](https://github.com/Berserker66/MultiWorld-Utilities/blob/3b5ba161dea223b96e9b1fc890e03469d9c6eb59/Items.py#L26)
|
||||||
- [Available Locations](https://github.com/Berserker66/MultiWorld-Utilities/blob/3b5ba161dea223b96e9b1fc890e03469d9c6eb59/Regions.py#L418)
|
- [Available Locations](https://github.com/Berserker66/MultiWorld-Utilities/blob/3b5ba161dea223b96e9b1fc890e03469d9c6eb59/Regions.py#L418)
|
||||||
|
|
||||||
#### Examples
|
#### Examples
|
||||||
```yaml
|
```yaml
|
||||||
plando_items:
|
plando_items:
|
||||||
- item:
|
- item: # 1
|
||||||
Lamp: 1
|
Lamp: 1
|
||||||
Fire Rod: 1
|
Fire Rod: 1
|
||||||
location: Link's House
|
location: Link's House
|
||||||
from_pool: true
|
from_pool: true
|
||||||
world: true
|
world: true
|
||||||
percentage: 50
|
percentage: 50
|
||||||
- items:
|
- items: # 2
|
||||||
Progressive Sword: 4
|
Progressive Sword: 4
|
||||||
Progressive Bow: 1
|
Progressive Bow: 1
|
||||||
Progressive Bow (Alt): 1
|
Progressive Bow (Alt): 1
|
||||||
|
@ -104,21 +108,30 @@ plando_items:
|
||||||
- Turtle Rock - Big Chest
|
- Turtle Rock - Big Chest
|
||||||
- Palace of Darkness - Big Chest
|
- Palace of Darkness - Big Chest
|
||||||
world: false
|
world: false
|
||||||
|
- items: # 3
|
||||||
|
Red Pendant: 1
|
||||||
|
Green Pendant: 1
|
||||||
|
Blue Pendant: 1
|
||||||
|
locations:
|
||||||
|
- Desert Palace - Prize
|
||||||
|
- Eastern Palace - Prize
|
||||||
|
- Tower of Hera - Prize
|
||||||
|
from_pool: true
|
||||||
```
|
```
|
||||||
|
|
||||||
The first example has a 50% chance to occur, which if it does places either the Lamp or Fire Rod in one's own
|
1. has a 50% chance to occur, which if it does places either the Lamp or Fire Rod in one's own
|
||||||
Link's House and removes the picked item from the item pool.
|
Link's House and removes the picked item from the item pool.
|
||||||
|
2. Always triggers and places the Swords and Bows into one's own Big Chests
|
||||||
The second example always triggers and places the Swords and Bows into one's own Big Chests
|
3. Locks Pendants to The Light World and therefore Crystals to dark world
|
||||||
|
|
||||||
### Texts
|
### Texts
|
||||||
- This module is disabled by default.
|
- This module is disabled by default.
|
||||||
- Has the options "text", "at" and "percentage"
|
- Has the options `text`, `at`, and `percentage`
|
||||||
- percentage is the percentage chance for this text to be placed, can be omitted entirely for 100%
|
- percentage is the percentage chance for this text to be placed, can be omitted entirely for 100%
|
||||||
- text is the text to be placed.
|
- text is the text to be placed.
|
||||||
- can be weighted.
|
- can be weighted.
|
||||||
- \n is a newline.
|
- `\n` is a newline.
|
||||||
- @ is the entered player's name.
|
- `@` is the entered player's name.
|
||||||
- Warning: Text Mapper does not support full unicode.
|
- Warning: Text Mapper does not support full unicode.
|
||||||
- [Alphabet](https://github.com/Berserker66/MultiWorld-Utilities/blob/65fa39df95c90c9b66141aee8b16b7e560d00819/Text.py#L756)
|
- [Alphabet](https://github.com/Berserker66/MultiWorld-Utilities/blob/65fa39df95c90c9b66141aee8b16b7e560d00819/Text.py#L756)
|
||||||
- at is the location within the game to attach the text to.
|
- at is the location within the game to attach the text to.
|
||||||
|
@ -135,19 +148,18 @@ plando_texts:
|
||||||
percentage: 50
|
percentage: 50
|
||||||
```
|
```
|
||||||

|

|
||||||
This has a 50% chance to trigger at all, if it does,
|
This has a 50% chance to trigger at all. If it does, it throws a coin between `uncle_leaving_text` and
|
||||||
it throws a coin between "uncle_leaving_text" and "uncle_dying_sewer", then places the text
|
`uncle_dying_sewer`, then places the text "This is a plando. You've been warned." at that location.
|
||||||
"This is a plando.\nYou've been warned." at that location.
|
|
||||||
|
|
||||||
### Connections
|
### Connections
|
||||||
- This module is disabled by default.
|
- This module is disabled by default.
|
||||||
- Has the options "percentage", "entrance", "exit" and "direction".
|
- Has the options `percentage`, `entrance`, `exit` and `direction`.
|
||||||
- All options support subweights
|
- All options support subweights
|
||||||
- percentage is the percentage chance for this to be connected, can be omitted entirely for 100%
|
- percentage is the percentage chance for this to be connected, can be omitted entirely for 100%
|
||||||
- Any Door has 4 total directions, as a door can be unlinked like in insanity ER
|
- Any Door has 4 total directions, as a door can be unlinked like in insanity ER
|
||||||
- entrance is the overworld door
|
- entrance is the overworld door
|
||||||
- exit is the underworld exit
|
- exit is the underworld exit
|
||||||
- direction can be "both", "entrance" or "exit"
|
- direction can be `both`, `entrance` or `exit`
|
||||||
- doors can be found in [this file](https://github.com/Berserker66/MultiWorld-Utilities/blob/main/EntranceShuffle.py)
|
- doors can be found in [this file](https://github.com/Berserker66/MultiWorld-Utilities/blob/main/EntranceShuffle.py)
|
||||||
|
|
||||||
|
|
||||||
|
@ -166,4 +178,4 @@ The first block connects the overworld entrance that normally leads to Link's Ho
|
||||||
to put you into the HC West Wing instead, exiting from within there will put you at the Overworld exiting Link's House.
|
to put you into the HC West Wing instead, exiting from within there will put you at the Overworld exiting Link's House.
|
||||||
|
|
||||||
Without the second block, you'd still exit from within Link's House to outside Link's House and the left side
|
Without the second block, you'd still exit from within Link's House to outside Link's House and the left side
|
||||||
Balcony Entrance would still lead into HC West Wing
|
Balcony Entrance would still lead into HC West Wing
|
||||||
|
|
|
@ -215,7 +215,9 @@ const buildUI = (settings, spriteData) => {
|
||||||
|
|
||||||
const spriteOptionsDescription = document.createElement('span');
|
const spriteOptionsDescription = document.createElement('span');
|
||||||
spriteOptionsDescription.className = 'description-span';
|
spriteOptionsDescription.className = 'description-span';
|
||||||
spriteOptionsDescription.innerText = "Choose an alternate sprite to play the game with.";
|
spriteOptionsDescription.innerHTML = 'Choose an alternate sprite to play the game with. Additional randomization ' +
|
||||||
|
'options are documented in the ' +
|
||||||
|
'<a href="https://github.com/Berserker66/MultiWorld-Utilities/blob/main/playerSettings.yaml#L374">settings file</a>.';
|
||||||
spriteOptionsWrapper.appendChild(spriteOptionsDescription);
|
spriteOptionsWrapper.appendChild(spriteOptionsDescription);
|
||||||
|
|
||||||
const spriteOptionsTable = document.createElement('table');
|
const spriteOptionsTable = document.createElement('table');
|
||||||
|
|
|
@ -48,6 +48,10 @@
|
||||||
{
|
{
|
||||||
"name": "Triforce Hunt + Ganon",
|
"name": "Triforce Hunt + Ganon",
|
||||||
"value": "ganon_triforce_hunt"
|
"value": "ganon_triforce_hunt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Ice Rod Hunt",
|
||||||
|
"value": "ice_rod_hunt"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -479,7 +483,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Inventory",
|
"name": "Inventory",
|
||||||
"value": "i"
|
"value": "f"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Prices",
|
"name": "Prices",
|
||||||
|
@ -491,11 +495,11 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Inventory and Prices",
|
"name": "Inventory and Prices",
|
||||||
"value": "ip"
|
"value": "fp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Inventory, Prices, and Upgrades",
|
"name": "Inventory, Prices, and Upgrades",
|
||||||
"value": "ipu"
|
"value": "fpu"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"restrict_dungeon_item_on_boss": {
|
||||||
|
"keyString": "restrict_dungeon_item_on_boss",
|
||||||
|
"friendlyName": "Dungeon Item on Boss",
|
||||||
|
"description": "Defeating a dungeon bosses always awards a dungeon item.",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"on": {
|
||||||
|
"keyString": "restrict_dungeon_item_on_boss.on",
|
||||||
|
"friendlyName": "On",
|
||||||
|
"description": "Dungeon bosses will always drop a dungeon item.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"off": {
|
||||||
|
"keyString": "restrict_dungeon_item_on_boss.off",
|
||||||
|
"friendlyName": "Off",
|
||||||
|
"description": "Dungeon bosses may not drop a dungeon item.",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"map_shuffle": {
|
"map_shuffle": {
|
||||||
"keyString": "map_shuffle",
|
"keyString": "map_shuffle",
|
||||||
"friendlyName": "Map Shuffle",
|
"friendlyName": "Map Shuffle",
|
||||||
|
@ -377,6 +397,12 @@
|
||||||
"friendlyName": "Local Triforce hunt /w Ganon",
|
"friendlyName": "Local Triforce hunt /w Ganon",
|
||||||
"description": "Same as Local Triforce Hunt, but you need to defeat Ganon in his lair instead of talking with Murahadala.",
|
"description": "Same as Local Triforce Hunt, but you need to defeat Ganon in his lair instead of talking with Murahadala.",
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"ice_rod_hunt": {
|
||||||
|
"keyString": "goals.ice_rod_hunt",
|
||||||
|
"friendlyName": "Ice Rod Hunt",
|
||||||
|
"description": "Look for the Ice Rod within your 215 available checks, then go kill Trinexx at Turtle rock.",
|
||||||
|
"defaultValue": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -944,6 +970,70 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"misery_mire_medallion": {
|
||||||
|
"keyString": "misery_mire_medallion",
|
||||||
|
"friendlyName": "Misery Mire Medallion",
|
||||||
|
"description": "Determines the medallion required to access Misery Mire",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"random": {
|
||||||
|
"keyString": "misery_mire_medallion.random",
|
||||||
|
"friendlyName": "Random",
|
||||||
|
"description": "Choose the medallion randomly.",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"quake": {
|
||||||
|
"keyString": "misery_mire_medallion.quake",
|
||||||
|
"friendlyName": "Quake",
|
||||||
|
"description": "Quake will be required ot enter Misery Mire.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"bombos": {
|
||||||
|
"keyString": "misery_mire_medallion.bombos",
|
||||||
|
"friendlyName": "Bombos",
|
||||||
|
"description": "Bombos will be required ot enter Misery Mire.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"ether": {
|
||||||
|
"keyString": "misery_mire_medallion.ether",
|
||||||
|
"friendlyName": "Ether",
|
||||||
|
"description": "Ether will be required ot enter Misery Mire.",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"turtle_rock_medallion": {
|
||||||
|
"keyString": "turtle_rock_medallion",
|
||||||
|
"friendlyName": "Turtle Rock Medallion",
|
||||||
|
"description": "Determines the medallion required to access Turtle Rock",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"random": {
|
||||||
|
"keyString": "turtle_rock_medallion.random",
|
||||||
|
"friendlyName": "Random",
|
||||||
|
"description": "Choose the medallion randomly.",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"quake": {
|
||||||
|
"keyString": "turtle_rock_medallion.quake",
|
||||||
|
"friendlyName": "Quake",
|
||||||
|
"description": "Quake will be required ot enter Turtle Rock.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"bombos": {
|
||||||
|
"keyString": "turtle_rock_medallion.bombos",
|
||||||
|
"friendlyName": "Bombos",
|
||||||
|
"description": "Bombos will be required ot enter Turtle Rock.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"ether": {
|
||||||
|
"keyString": "turtle_rock_medallion.ether",
|
||||||
|
"friendlyName": "Ether",
|
||||||
|
"description": "Ether will be required ot enter Turtle Rock.",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"bush_shuffle": {
|
"bush_shuffle": {
|
||||||
"keyString": "bush_shuffle",
|
"keyString": "bush_shuffle",
|
||||||
"friendlyName": "Bush Shuffle",
|
"friendlyName": "Bush Shuffle",
|
||||||
|
@ -1092,10 +1182,16 @@
|
||||||
"description": "Shop contents are left unchanged.",
|
"description": "Shop contents are left unchanged.",
|
||||||
"defaultValue": 50
|
"defaultValue": 50
|
||||||
},
|
},
|
||||||
"i": {
|
"g": {
|
||||||
"keyString": "shop_shuffle.i",
|
"keyString": "shop_shuffle.g",
|
||||||
"friendlyName": "Inventory Shuffle",
|
"friendlyName": "Pool Shuffle",
|
||||||
"description": "Randomizes the inventories of shops.",
|
"description": "Shuffles the inventory of shops.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"f": {
|
||||||
|
"keyString": "shop_shuffle.f",
|
||||||
|
"friendlyName": "Random Shuffle",
|
||||||
|
"description": "Randomly generate an inventory for each shop from a pool of non-progression items.",
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
"p": {
|
"p": {
|
||||||
|
@ -1110,16 +1206,66 @@
|
||||||
"description": "Shuffles capacity upgrades throughout the game world.",
|
"description": "Shuffles capacity upgrades throughout the game world.",
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
"ip": {
|
"gp": {
|
||||||
"keyString": "shop_shuffle.ip",
|
"keyString": "shop_shuffle.gp",
|
||||||
"friendlyName": "Inventory & Prices",
|
"friendlyName": "Pool & Prices",
|
||||||
"description": "Shuffles the inventory and randomizes the prices of items in shops.",
|
"description": "Shuffles the inventory and randomizes the prices of items in shops.",
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
},
|
},
|
||||||
"uip": {
|
"fp": {
|
||||||
"keyString": "shop_shuffle.uip",
|
"keyString": "shop_shuffle.fp",
|
||||||
"friendlyName": "Full Shuffle",
|
"friendlyName": "Full Shuffle",
|
||||||
"description": "Shuffles the inventory and randomizes the prices of items in shops. Also distributes capacity upgrades throughout the world.",
|
"description": "Randomizes the inventory and prices of shops.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"ufp": {
|
||||||
|
"keyString": "shop_shuffle.ufp",
|
||||||
|
"friendlyName": "Full Shuffle & Capacity",
|
||||||
|
"description": "Randomizes the inventory and prices in shops, and distributes capacity upgrades throughout the world.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"wfp": {
|
||||||
|
"keyString": "shop_shuffle.wfp",
|
||||||
|
"friendlyName": "Full Shuffle & Potion Shop",
|
||||||
|
"description": "Randomizes the inventory prices of shops, and shuffles items in the potion shop.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"ufpw": {
|
||||||
|
"keyString": "shop_shuffle.ufpw",
|
||||||
|
"friendlyName": "Randomize Everything",
|
||||||
|
"description": "Randomizes the inventory and prices in shops, distributes capacity upgrades throughout the world, and shuffles items in the potion shop.",
|
||||||
|
"defaultValue": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shop_shuffle_slots": {
|
||||||
|
"keyString": "shop_shuffle_slots",
|
||||||
|
"friendlyName": "Pay (Rupees) to Win",
|
||||||
|
"description": "Move items from the general item pool into shops for purchase.",
|
||||||
|
"inputType": "range",
|
||||||
|
"subOptions": {
|
||||||
|
"0": {
|
||||||
|
"keyString": "shop_shuffle_slots.0",
|
||||||
|
"friendlyName": "Off",
|
||||||
|
"description": "No items are moved",
|
||||||
|
"defaultValue": 50
|
||||||
|
},
|
||||||
|
"1": {
|
||||||
|
"keyString": "shop_shuffle_slots.10",
|
||||||
|
"friendlyName": "Level 1",
|
||||||
|
"description": "10 Items are moved into shops.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"2": {
|
||||||
|
"keyString": "shop_shuffle_slots.20",
|
||||||
|
"friendlyName": "Level 2",
|
||||||
|
"description": "20 Items are moved into shops.",
|
||||||
|
"defaultValue": 0
|
||||||
|
},
|
||||||
|
"3": {
|
||||||
|
"keyString": "shop_shuffle_slots.30",
|
||||||
|
"friendlyName": "Level 3",
|
||||||
|
"description": "30 Items are moved into shops.",
|
||||||
"defaultValue": 0
|
"defaultValue": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,6 +96,7 @@ goals:
|
||||||
local_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle
|
local_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle
|
||||||
ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon
|
ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon
|
||||||
local_ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon
|
local_ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon
|
||||||
|
ice_rod_hunt: 0 # You start with everything needed to 216 the seed. Find the Ice rod, then kill Trinexx at Turtle rock.
|
||||||
triforce_pieces_mode: #Determine how to calculate the extra available triforce pieces.
|
triforce_pieces_mode: #Determine how to calculate the extra available triforce pieces.
|
||||||
extra: 0 # available = triforce_pieces_extra + triforce_pieces_required
|
extra: 0 # available = triforce_pieces_extra + triforce_pieces_required
|
||||||
percentage: 0 # available = (triforce_pieces_percentage /100) * triforce_pieces_required
|
percentage: 0 # available = (triforce_pieces_percentage /100) * triforce_pieces_required
|
||||||
|
@ -107,7 +108,7 @@ triforce_pieces_extra: # Set to how many extra triforces pieces are available to
|
||||||
10: 50
|
10: 50
|
||||||
15: 0
|
15: 0
|
||||||
20: 0
|
20: 0
|
||||||
triforce_pieces_percentage: # Set to how many extra triforces pieces according to a percentage of the required ones, are available to collect in the world.
|
triforce_pieces_percentage: # Set to how many triforce pieces according to a percentage of the required ones, are available to collect in the world.
|
||||||
# Format "pieces: chance"
|
# Format "pieces: chance"
|
||||||
100: 0 #No extra
|
100: 0 #No extra
|
||||||
150: 50 #Half the required will be added as extra
|
150: 50 #Half the required will be added as extra
|
||||||
|
@ -173,6 +174,19 @@ item_functionality:
|
||||||
progression_balancing:
|
progression_balancing:
|
||||||
on: 50 # A system to reduce BK, as in times during which you can't do anything by moving your items into an earlier access sphere to make it likely you have stuff to do
|
on: 50 # A system to reduce BK, as in times during which you can't do anything by moving your items into an earlier access sphere to make it likely you have stuff to do
|
||||||
off: 0 # Turn this off if you don't mind a longer multiworld, or can glitch around missing items.
|
off: 0 # Turn this off if you don't mind a longer multiworld, or can glitch around missing items.
|
||||||
|
tile_shuffle: # Randomize the tile layouts in flying tile rooms
|
||||||
|
on: 0
|
||||||
|
off: 50
|
||||||
|
misery_mire_medallion: # required medallion to open Misery Mire front entrance
|
||||||
|
random: 50
|
||||||
|
ether: 0
|
||||||
|
bombos: 0
|
||||||
|
quake: 0
|
||||||
|
turtle_rock_medallion: # required medallion to open Turtle Rock front entrance
|
||||||
|
random: 50
|
||||||
|
ether: 0
|
||||||
|
bombos: 0
|
||||||
|
quake: 0
|
||||||
### Enemizer Section ###
|
### Enemizer Section ###
|
||||||
boss_shuffle:
|
boss_shuffle:
|
||||||
none: 50 # Vanilla bosses
|
none: 50 # Vanilla bosses
|
||||||
|
@ -186,9 +200,6 @@ enemy_shuffle: # Randomize enemy placement
|
||||||
killable_thieves: # Make thieves killable
|
killable_thieves: # Make thieves killable
|
||||||
on: 0 # Usually turned on together with enemy_shuffle to make annoying thief placement more manageable
|
on: 0 # Usually turned on together with enemy_shuffle to make annoying thief placement more manageable
|
||||||
off: 50
|
off: 50
|
||||||
tile_shuffle: # Randomize the tile layouts in flying tile rooms
|
|
||||||
on: 0
|
|
||||||
off: 50
|
|
||||||
bush_shuffle: # Randomize the chance that bushes have enemies and the enemies under said bush
|
bush_shuffle: # Randomize the chance that bushes have enemies and the enemies under said bush
|
||||||
on: 0
|
on: 0
|
||||||
off: 50
|
off: 50
|
||||||
|
@ -211,14 +222,26 @@ beemizer: # Remove items from the global item pool and replace them with single
|
||||||
2: 0 # 60% of the non-essential item pool is replaced with bee traps, of which 20% could be single bees
|
2: 0 # 60% of the non-essential item pool is replaced with bee traps, of which 20% could be single bees
|
||||||
3: 0 # 100% of the non-essential item pool is replaced with bee traps, of which 50% could be single bees
|
3: 0 # 100% of the non-essential item pool is replaced with bee traps, of which 50% could be single bees
|
||||||
4: 0 # 100% of the non-essential item pool is replaced with bee traps
|
4: 0 # 100% of the non-essential item pool is replaced with bee traps
|
||||||
|
### Shop Settings ###
|
||||||
|
shop_shuffle_slots: # Maximum amount of shop slots to be filled with regular item pool items (such as Moon Pearl)
|
||||||
|
0: 50
|
||||||
|
10: 0
|
||||||
|
20: 0
|
||||||
|
30: 0
|
||||||
shop_shuffle:
|
shop_shuffle:
|
||||||
none: 50
|
none: 50
|
||||||
i: 0 # Shuffle the inventories of the shops around
|
g: 0 # Generate new default inventories for overworld/underworld shops, and unique shops
|
||||||
|
f: 0 # Generate new default inventories for every shop independently
|
||||||
p: 0 # Randomize the prices of the items in shop inventories
|
p: 0 # Randomize the prices of the items in shop inventories
|
||||||
u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld)
|
u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld)
|
||||||
ip: 0 # Shuffle inventories and randomize prices
|
w: 0 # Consider witch's hut like any other shop and shuffle/randomize it too
|
||||||
uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool
|
gp: 0 # Shuffle inventories and randomize prices
|
||||||
|
fp: 0 # Randomize items in every shop and their prices
|
||||||
|
ufp: 0 # Randomize items and prices in every shop, and include capacity upgrades in item pool
|
||||||
|
wfp: 0 # Randomize items and prices in every shop, and include potion shop inventory in shuffle
|
||||||
|
ufpw: 0 # Randomize items and prices in every shop, shuffle potion shop inventory, and include capacity upgrades
|
||||||
# You can add more combos
|
# You can add more combos
|
||||||
|
### End of Shop Section ###
|
||||||
shuffle_prizes: # aka drops
|
shuffle_prizes: # aka drops
|
||||||
none: 0 # do not shuffle prize packs
|
none: 0 # do not shuffle prize packs
|
||||||
g: 50 # shuffle "general" price packs, as in enemy, tree pull, dig etc.
|
g: 50 # shuffle "general" price packs, as in enemy, tree pull, dig etc.
|
||||||
|
@ -295,6 +318,7 @@ linked_options:
|
||||||
hard: 1
|
hard: 1
|
||||||
expert: 1
|
expert: 1
|
||||||
percentage: 0 # Set this to the percentage chance you want enemizer
|
percentage: 0 # Set this to the percentage chance you want enemizer
|
||||||
|
### door rando only options ###
|
||||||
door_shuffle: # Only available if the host uses the doors branch, it is ignored otherwise
|
door_shuffle: # Only available if the host uses the doors branch, it is ignored otherwise
|
||||||
vanilla: 50 # Everything should be like in vanilla
|
vanilla: 50 # Everything should be like in vanilla
|
||||||
basic: 0 # Dungeons are shuffled within themselves
|
basic: 0 # Dungeons are shuffled within themselves
|
||||||
|
@ -308,41 +332,20 @@ key_drop_shuffle: # Only available if the host uses the doors branch, it is igno
|
||||||
on: 0 # Enables the small keys dropped by enemies or under pots, and the big key dropped by the Ball & Chain guard to be shuffled into the pool. This extends the number of checks to 249.
|
on: 0 # Enables the small keys dropped by enemies or under pots, and the big key dropped by the Ball & Chain guard to be shuffled into the pool. This extends the number of checks to 249.
|
||||||
off: 50
|
off: 50
|
||||||
experimental: # Only available if the host uses the doors branch, it is ignored otherwise
|
experimental: # Only available if the host uses the doors branch, it is ignored otherwise
|
||||||
on: 0 # Enables experimental features. Currently, this is just the dungeon keys in chest counter.
|
on: 0 # Enables experimental features.
|
||||||
off: 50
|
off: 50
|
||||||
debug: # Only available if the host uses the doors branch, it is ignored otherwise
|
debug: # Only available if the host uses the doors branch, it is ignored otherwise
|
||||||
on: 0 # Enables debugging features. Currently, these are the Item collection counter. (overwrites total triforce pieces) and Castle Gate closed indicator.
|
on: 0 # Enables debugging features. Currently, these are the Item collection counter. (overwrites total triforce pieces) and Castle Gate closed indicator.
|
||||||
off: 50
|
off: 50
|
||||||
|
### end of door rando only options ###
|
||||||
rom:
|
rom:
|
||||||
random_sprite_on_event: # An alternative to specifying randomonhit / randomonexit / etc... in sprite down below.
|
|
||||||
enabled: # If enabled, sprite down below is ignored completely, (although it may become the sprite pool)
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
on_hit: # Random sprite on hit. Being hit by things that cause 0 damage still counts.
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
on_enter: # Random sprite on underworld entry. Note that entering hobo counts.
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
on_exit: # Random sprite on underworld exit. Exiting hobo does not count.
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
on_slash: # Random sprite on sword slash. Note, it still counts if you attempt to slash while swordless.
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
on_item: # Random sprite on getting an item. Anything that causes you to hold an item above your head counts.
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
on_bonk: # Random sprite on bonk.
|
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
#sprite_pool: # When specified, limits the pool of sprites used for randomon-event to the specified pool. Uncomment to use this.
|
#sprite_pool: # When specified, limits the pool of sprites used for randomon-event to the specified pool. Uncomment to use this.
|
||||||
# - link
|
# - link
|
||||||
# - pride link
|
# - pride link
|
||||||
# - penguin link
|
# - penguin link
|
||||||
# - random # You can specify random multiple times for however many potentially unique random sprites you want in your pool.
|
# - random # You can specify random multiple times for however many potentially unique random sprites you want in your pool.
|
||||||
sprite: # Enter the name of your preferred sprite and weight it appropriately
|
sprite: # Enter the name of your preferred sprite and weight it appropriately
|
||||||
random: 0 # Choose a sprite at random
|
random: 0
|
||||||
Link: 50 # To add other sprites: open the gui/Creator, go to adjust, select a sprite and write down the name the gui calls it
|
Link: 50 # To add other sprites: open the gui/Creator, go to adjust, select a sprite and write down the name the gui calls it
|
||||||
disablemusic: # If "on", all in-game music will be disabled
|
disablemusic: # If "on", all in-game music will be disabled
|
||||||
on: 0
|
on: 0
|
||||||
|
@ -371,8 +374,8 @@ rom:
|
||||||
off: 0
|
off: 0
|
||||||
ow_palettes: # Change the colors of the overworld
|
ow_palettes: # Change the colors of the overworld
|
||||||
default: 50 # No changes
|
default: 50 # No changes
|
||||||
random: 0 # Shuffle the colors
|
random: 0 # Shuffle the colors, with harmony in mind
|
||||||
blackout: 0 # Never use this
|
blackout: 0 # everything black / blind mode
|
||||||
grayscale: 0
|
grayscale: 0
|
||||||
negative: 0
|
negative: 0
|
||||||
classic: 0
|
classic: 0
|
||||||
|
@ -381,8 +384,8 @@ rom:
|
||||||
puke: 0
|
puke: 0
|
||||||
uw_palettes: # Change the colors of caves and dungeons
|
uw_palettes: # Change the colors of caves and dungeons
|
||||||
default: 50 # No changes
|
default: 50 # No changes
|
||||||
random: 0 # Shuffle the colors
|
random: 0 # Shuffle the colors, with harmony in mind
|
||||||
blackout: 0 # Never use this
|
blackout: 0 # everything black / blind mode
|
||||||
grayscale: 0
|
grayscale: 0
|
||||||
negative: 0
|
negative: 0
|
||||||
classic: 0
|
classic: 0
|
||||||
|
@ -391,8 +394,8 @@ rom:
|
||||||
puke: 0
|
puke: 0
|
||||||
hud_palettes: # Change the colors of the hud
|
hud_palettes: # Change the colors of the hud
|
||||||
default: 50 # No changes
|
default: 50 # No changes
|
||||||
random: 0 # Shuffle the colors
|
random: 0 # Shuffle the colors, with harmony in mind
|
||||||
blackout: 0 # Never use this
|
blackout: 0 # everything black / blind mode
|
||||||
grayscale: 0
|
grayscale: 0
|
||||||
negative: 0
|
negative: 0
|
||||||
classic: 0
|
classic: 0
|
||||||
|
@ -401,8 +404,18 @@ rom:
|
||||||
puke: 0
|
puke: 0
|
||||||
sword_palettes: # Change the colors of swords
|
sword_palettes: # Change the colors of swords
|
||||||
default: 50 # No changes
|
default: 50 # No changes
|
||||||
random: 0 # Shuffle the colors
|
random: 0 # Shuffle the colors, with harmony in mind
|
||||||
blackout: 0 # Never use this
|
blackout: 0 # everything black / blind mode
|
||||||
|
grayscale: 0
|
||||||
|
negative: 0
|
||||||
|
classic: 0
|
||||||
|
dizzy: 0
|
||||||
|
sick: 0
|
||||||
|
puke: 0
|
||||||
|
shield_palettes: # Change the colors of shields
|
||||||
|
default: 50 # No changes
|
||||||
|
random: 0 # Shuffle the colors, with harmony in mind
|
||||||
|
blackout: 0 # everything black / blind mode
|
||||||
grayscale: 0
|
grayscale: 0
|
||||||
negative: 0
|
negative: 0
|
||||||
classic: 0
|
classic: 0
|
||||||
|
|
|
@ -16,6 +16,11 @@ html{
|
||||||
color: #eeffeb;
|
color: #eeffeb;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#tutorial-wrapper img{
|
||||||
|
max-width: 100%;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
#tutorial-wrapper p{
|
#tutorial-wrapper p{
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -88,7 +88,7 @@ entrance_shuffle: # Documentation: https://alttpr.com/en/options#entrance_shuffl
|
||||||
insanity: 0 # Very few grouping rules. Good luck
|
insanity: 0 # Very few grouping rules. Good luck
|
||||||
goals:
|
goals:
|
||||||
ganon: 50 # Climb GT, defeat Agahnim 2, and then kill Ganon
|
ganon: 50 # Climb GT, defeat Agahnim 2, and then kill Ganon
|
||||||
fast_ganon: 0 # Only killing Ganon is required. The hole is always open. However, items may still be placed in GT
|
fast_ganon: 0 # Only killing Ganon is required. However, items may still be placed in GT
|
||||||
dungeons: 0 # Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2)
|
dungeons: 0 # Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2)
|
||||||
pedestal: 0 # Pull the Triforce from the Master Sword pedestal
|
pedestal: 0 # Pull the Triforce from the Master Sword pedestal
|
||||||
ganon_pedestal: 0 # Pull the Master Sword pedestal, then kill Ganon
|
ganon_pedestal: 0 # Pull the Master Sword pedestal, then kill Ganon
|
||||||
|
@ -96,6 +96,12 @@ goals:
|
||||||
local_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle
|
local_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle
|
||||||
ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon
|
ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout the worlds, then kill Ganon
|
||||||
local_ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon
|
local_ganon_triforce_hunt: 0 # Collect 20 of 30 Triforce pieces spread throughout your world, then kill Ganon
|
||||||
|
ice_rod_hunt: 0 # You start with everything needed to 216 the seed. Find the Ice rod, then kill Trinexx at Turtle rock.
|
||||||
|
pyramid_open:
|
||||||
|
goal: 50 # Opens pyrymid if goal is fast_ganon, ganon_pedestal, ganon_triforce_hunt, or local_ganon_triforce_hunt
|
||||||
|
auto: 0 # Opens pyramid same as goal, except when an entrance shuffle other than vanilla, dungeonssimple or dungeonsfull is in effect.
|
||||||
|
yes: 0 # pyramid is opened unconditionally. You still have to beat agahnim 2 for ganon and dungeons.
|
||||||
|
no: 0 # access to pyramid requires beating agahnim 2.
|
||||||
triforce_pieces_mode: #Determine how to calculate the extra available triforce pieces.
|
triforce_pieces_mode: #Determine how to calculate the extra available triforce pieces.
|
||||||
extra: 0 # available = triforce_pieces_extra + triforce_pieces_required
|
extra: 0 # available = triforce_pieces_extra + triforce_pieces_required
|
||||||
percentage: 0 # available = (triforce_pieces_percentage /100) * triforce_pieces_required
|
percentage: 0 # available = (triforce_pieces_percentage /100) * triforce_pieces_required
|
||||||
|
@ -176,6 +182,16 @@ progression_balancing:
|
||||||
tile_shuffle: # Randomize the tile layouts in flying tile rooms
|
tile_shuffle: # Randomize the tile layouts in flying tile rooms
|
||||||
on: 0
|
on: 0
|
||||||
off: 50
|
off: 50
|
||||||
|
misery_mire_medallion: # required medallion to open Misery Mire front entrance
|
||||||
|
random: 50
|
||||||
|
Ether: 0
|
||||||
|
Bombos: 0
|
||||||
|
Quake: 0
|
||||||
|
turtle_rock_medallion: # required medallion to open Turtle Rock front entrance
|
||||||
|
random: 50
|
||||||
|
Ether: 0
|
||||||
|
Bombos: 0
|
||||||
|
Quake: 0
|
||||||
### Enemizer Section ###
|
### Enemizer Section ###
|
||||||
boss_shuffle:
|
boss_shuffle:
|
||||||
none: 50 # Vanilla bosses
|
none: 50 # Vanilla bosses
|
||||||
|
@ -205,20 +221,31 @@ pot_shuffle:
|
||||||
'on': 0 # Keys, items, and buttons hidden under pots in dungeons are shuffled with other pots in their supertile
|
'on': 0 # Keys, items, and buttons hidden under pots in dungeons are shuffled with other pots in their supertile
|
||||||
'off': 50 # Default pot item locations
|
'off': 50 # Default pot item locations
|
||||||
### End of Enemizer Section ###
|
### End of Enemizer Section ###
|
||||||
beemizer: # Remove items from the global item pool and replace them with single bees and bee traps
|
beemizer: # Remove items from the global item pool and replace them with single bees (fill bottles) and bee traps
|
||||||
0: 50 # No bee traps are placed
|
0: 50 # No bee traps are placed
|
||||||
1: 0 # 25% of the non-essential item pool is replaced with bee traps
|
1: 0 # 25% of rupees, bombs and arrows are replaced with bees, of which 60% are traps and 40% single bees
|
||||||
2: 0 # 60% of the non-essential item pool is replaced with bee traps, of which 20% could be single bees
|
2: 0 # 50% of rupees, bombs and arrows are replaced with bees, of which 70% are traps and 30% single bees
|
||||||
3: 0 # 100% of the non-essential item pool is replaced with bee traps, of which 50% could be single bees
|
3: 0 # 75% of rupees, bombs and arrows are replaced with bees, of which 80% are traps and 20% single bees
|
||||||
4: 0 # 100% of the non-essential item pool is replaced with bee traps
|
4: 0 # 100% of rupees, bombs and arrows are replaced with bees, of which 90% are traps and 10% single bees
|
||||||
|
### Shop Settings ###
|
||||||
|
shop_shuffle_slots: # Maximum amount of shop slots to be filled with regular item pool items (such as Moon Pearl)
|
||||||
|
0: 50
|
||||||
|
5: 0
|
||||||
|
15: 0
|
||||||
|
30: 0
|
||||||
shop_shuffle:
|
shop_shuffle:
|
||||||
none: 50
|
none: 50
|
||||||
i: 0 # Shuffle the inventories of the shops around
|
g: 0 # Generate new default inventories for overworld/underworld shops, and unique shops
|
||||||
|
f: 0 # Generate new default inventories for every shop independently
|
||||||
|
i: 0 # Shuffle default inventories of the shops around
|
||||||
p: 0 # Randomize the prices of the items in shop inventories
|
p: 0 # Randomize the prices of the items in shop inventories
|
||||||
u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld)
|
u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld)
|
||||||
|
w: 0 # Consider witch's hut like any other shop and shuffle/randomize it too
|
||||||
ip: 0 # Shuffle inventories and randomize prices
|
ip: 0 # Shuffle inventories and randomize prices
|
||||||
|
fpu: 0 # Generate new inventories, randomize prices and shuffle capacity upgrades into item pool
|
||||||
uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool
|
uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool
|
||||||
# You can add more combos
|
# You can add more combos
|
||||||
|
### End of Shop Section ###
|
||||||
shuffle_prizes: # aka drops
|
shuffle_prizes: # aka drops
|
||||||
none: 0 # do not shuffle prize packs
|
none: 0 # do not shuffle prize packs
|
||||||
g: 50 # shuffle "general" price packs, as in enemy, tree pull, dig etc.
|
g: 50 # shuffle "general" price packs, as in enemy, tree pull, dig etc.
|
||||||
|
@ -253,11 +280,6 @@ green_clock_time: # For all timer modes, the amount of time in minutes to gain o
|
||||||
# - "Small Keys"
|
# - "Small Keys"
|
||||||
# - "Big Keys"
|
# - "Big Keys"
|
||||||
# Can be uncommented to use it
|
# Can be uncommented to use it
|
||||||
# non_local_items: # Force certain items to appear outside your world only, always across the multiworld. Recognizes some group names, like "Swords"
|
|
||||||
# - "Moon Pearl"
|
|
||||||
# - "Small Keys"
|
|
||||||
# - "Big Keys"
|
|
||||||
# Can be uncommented to use it
|
|
||||||
# startinventory: # Begin the file with the listed items/upgrades
|
# startinventory: # Begin the file with the listed items/upgrades
|
||||||
# Pegasus Boots: on
|
# Pegasus Boots: on
|
||||||
# Bomb Upgrade (+10): 4
|
# Bomb Upgrade (+10): 4
|
||||||
|
|
|
@ -5,7 +5,8 @@ from worlds.alttp.Dungeons import create_dungeons, get_dungeon_item_pool
|
||||||
from worlds.alttp.EntranceShuffle import mandatory_connections, connect_simple
|
from worlds.alttp.EntranceShuffle import mandatory_connections, connect_simple
|
||||||
from worlds.alttp.ItemPool import difficulties, generate_itempool
|
from worlds.alttp.ItemPool import difficulties, generate_itempool
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import create_regions, create_shops
|
from worlds.alttp.Regions import create_regions
|
||||||
|
from Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from worlds.alttp.EntranceShuffle import link_inverted_entrances
|
||||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||||
from worlds.alttp.ItemPool import generate_itempool, difficulties
|
from worlds.alttp.ItemPool import generate_itempool, difficulties
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import mark_light_world_regions, create_shops
|
from worlds.alttp.Regions import mark_light_world_regions
|
||||||
|
from Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from worlds.alttp.EntranceShuffle import link_inverted_entrances
|
||||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||||
from worlds.alttp.ItemPool import generate_itempool, difficulties
|
from worlds.alttp.ItemPool import generate_itempool, difficulties
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import mark_light_world_regions, create_shops
|
from worlds.alttp.Regions import mark_light_world_regions
|
||||||
|
from Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from worlds.alttp.EntranceShuffle import link_inverted_entrances
|
||||||
from worlds.alttp.InvertedRegions import create_inverted_regions
|
from worlds.alttp.InvertedRegions import create_inverted_regions
|
||||||
from worlds.alttp.ItemPool import generate_itempool, difficulties
|
from worlds.alttp.ItemPool import generate_itempool, difficulties
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import mark_light_world_regions, create_shops
|
from worlds.alttp.Regions import mark_light_world_regions
|
||||||
|
from Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from worlds.alttp.EntranceShuffle import link_entrances
|
||||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||||
from worlds.alttp.ItemPool import difficulties, generate_itempool
|
from worlds.alttp.ItemPool import difficulties, generate_itempool
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import create_regions, create_shops
|
from worlds.alttp.Regions import create_regions
|
||||||
|
from Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ from worlds.alttp.EntranceShuffle import link_entrances
|
||||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||||
from worlds.alttp.ItemPool import difficulties, generate_itempool
|
from worlds.alttp.ItemPool import difficulties, generate_itempool
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import create_regions, create_shops
|
from worlds.alttp.Regions import create_regions
|
||||||
|
from Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
from Shops import shop_table
|
||||||
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
||||||
|
class TestSram(TestBase):
|
||||||
|
def testUniqueOffset(self):
|
||||||
|
sram_ids = set()
|
||||||
|
for shop_name, shopdata in shop_table.items():
|
||||||
|
for x in range(3):
|
||||||
|
new = shopdata.sram_offset + x
|
||||||
|
with self.subTest(shop_name, slot=x + 1, offset=new):
|
||||||
|
self.assertNotIn(new, sram_ids)
|
||||||
|
sram_ids.add(new)
|
|
@ -4,7 +4,8 @@ from worlds.alttp.EntranceShuffle import link_entrances
|
||||||
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
from worlds.alttp.InvertedRegions import mark_dark_world_regions
|
||||||
from worlds.alttp.ItemPool import difficulties, generate_itempool
|
from worlds.alttp.ItemPool import difficulties, generate_itempool
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory
|
||||||
from worlds.alttp.Regions import create_regions, create_shops
|
from worlds.alttp.Regions import create_regions
|
||||||
|
from Shops import create_shops
|
||||||
from worlds.alttp.Rules import set_rules
|
from worlds.alttp.Rules import set_rules
|
||||||
from test.TestBase import TestBase
|
from test.TestBase import TestBase
|
||||||
|
|
||||||
|
|
|
@ -132,8 +132,6 @@ def fill_dungeons_restrictive(world):
|
||||||
if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
|
if (item.smallkey and world.keyshuffle[item.player]) or (item.bigkey and world.bigkeyshuffle[item.player]):
|
||||||
all_state_base.collect(item, True)
|
all_state_base.collect(item, True)
|
||||||
item.advancement = True
|
item.advancement = True
|
||||||
elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
|
|
||||||
item.priority = True
|
|
||||||
|
|
||||||
dungeon_items = [item for item in get_dungeon_item_pool(world) if (((item.smallkey and not world.keyshuffle[item.player])
|
dungeon_items = [item for item in get_dungeon_item_pool(world) if (((item.smallkey and not world.keyshuffle[item.player])
|
||||||
or (item.bigkey and not world.bigkeyshuffle[item.player])
|
or (item.bigkey and not world.bigkeyshuffle[item.player])
|
||||||
|
@ -144,7 +142,7 @@ def fill_dungeons_restrictive(world):
|
||||||
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
# sort in the order Big Key, Small Key, Other before placing dungeon items
|
||||||
sort_order = {"BigKey": 3, "SmallKey": 2}
|
sort_order = {"BigKey": 3, "SmallKey": 2}
|
||||||
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
dungeon_items.sort(key=lambda item: sort_order.get(item.type, 1))
|
||||||
fill_restrictive(world, all_state_base, locations, dungeon_items, True)
|
fill_restrictive(world, all_state_base, locations, dungeon_items, True, True)
|
||||||
|
|
||||||
|
|
||||||
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
||||||
|
|
|
@ -222,9 +222,16 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
Random: Picks a random value between 0 and 7 (inclusive).
|
Random: Picks a random value between 0 and 7 (inclusive).
|
||||||
0-7: Number of crystals needed
|
0-7: Number of crystals needed
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--open_pyramid', default=defval(False), help='''\
|
parser.add_argument('--open_pyramid', default=defval('auto'), help='''\
|
||||||
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it
|
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it.
|
||||||
''', action='store_true')
|
Depending on goal, you might still need to beat Agahnim 2 in order to beat ganon.
|
||||||
|
fast ganon goals are crystals, ganontriforcehunt, localganontriforcehunt, pedestalganon
|
||||||
|
auto - Only opens pyramid hole if the goal specifies a fast ganon, and entrance shuffle
|
||||||
|
is vanilla, dungeonssimple or dungeonsfull.
|
||||||
|
goal - Opens pyramid hole if the goal specifies a fast ganon.
|
||||||
|
yes - Always opens the pyramid hole.
|
||||||
|
no - Never opens the pyramid hole.
|
||||||
|
''', choices=['auto', 'goal', 'yes', 'no'])
|
||||||
parser.add_argument('--rom', default=defval('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'),
|
parser.add_argument('--rom', default=defval('Zelda no Densetsu - Kamigami no Triforce (Japan).sfc'),
|
||||||
help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
help='Path to an ALttP JAP(1.0) rom to use as a base.')
|
||||||
parser.add_argument('--loglevel', default=defval('info'), const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
parser.add_argument('--loglevel', default=defval('info'), const='info', nargs='?', choices=['error', 'info', 'warning', 'debug'], help='Select level of logging for output.')
|
||||||
|
@ -326,9 +333,17 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
parser.add_argument('--beemizer', default=defval(0), type=lambda value: min(max(int(value), 0), 4))
|
parser.add_argument('--beemizer', default=defval(0), type=lambda value: min(max(int(value), 0), 4))
|
||||||
parser.add_argument('--shop_shuffle', default='', help='''\
|
parser.add_argument('--shop_shuffle', default='', help='''\
|
||||||
combine letters for options:
|
combine letters for options:
|
||||||
i: shuffle the inventories of the shops around
|
g: generate default inventories for light and dark world shops, and unique shops
|
||||||
|
f: generate default inventories for each shop individually
|
||||||
|
i: shuffle the default inventories of the shops around
|
||||||
p: randomize the prices of the items in shop inventories
|
p: randomize the prices of the items in shop inventories
|
||||||
u: shuffle capacity upgrades into the item pool
|
u: shuffle capacity upgrades into the item pool
|
||||||
|
w: consider witch's hut like any other shop and shuffle/randomize it too
|
||||||
|
''')
|
||||||
|
parser.add_argument('--shop_shuffle_slots', default=defval(0),
|
||||||
|
type=lambda value: min(max(int(value), 1), 96),
|
||||||
|
help='''
|
||||||
|
Maximum amount of shop slots able to be filled by items from the item pool.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb'])
|
parser.add_argument('--shuffle_prizes', default=defval('g'), choices=['', 'g', 'b', 'gb'])
|
||||||
parser.add_argument('--sprite_pool', help='''\
|
parser.add_argument('--sprite_pool', help='''\
|
||||||
|
@ -390,7 +405,8 @@ def parse_arguments(argv, no_defaults=False):
|
||||||
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'enemy_shuffle', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
||||||
'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
|
'heartbeep', "skip_progression_balancing", "triforce_pieces_available",
|
||||||
"triforce_pieces_required", "shop_shuffle", "required_medallions",
|
"triforce_pieces_required", "shop_shuffle", "shop_shuffle_slots",
|
||||||
|
"required_medallions",
|
||||||
"plando_items", "plando_texts", "plando_connections",
|
"plando_items", "plando_texts", "plando_connections",
|
||||||
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
'remote_items', 'progressive', 'dungeon_counters', 'glitch_boots', 'killable_thieves',
|
||||||
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
|
'tile_shuffle', 'bush_shuffle', 'shuffle_prizes', 'sprite_pool', 'dark_room_logic',
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from BaseClasses import Region, RegionType, ShopType, Location, TakeAny
|
from BaseClasses import Region, RegionType, Location
|
||||||
|
from Shops import TakeAny, total_shop_slots, set_up_shops, shuffle_shops
|
||||||
from worlds.alttp.Bosses import place_bosses
|
from worlds.alttp.Bosses import place_bosses
|
||||||
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||||
from worlds.alttp.EntranceShuffle import connect_entrance
|
from worlds.alttp.EntranceShuffle import connect_entrance
|
||||||
from Fill import FillError, fill_restrictive
|
from Fill import FillError, fill_restrictive
|
||||||
from worlds.alttp.Items import ItemFactory
|
from worlds.alttp.Items import ItemFactory, trap_replaceable
|
||||||
from worlds.alttp.Rules import forbid_items_for_player
|
from worlds.alttp.Rules import forbid_items_for_player
|
||||||
|
|
||||||
# This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
# This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
||||||
|
@ -389,27 +390,22 @@ def generate_itempool(world, player: int):
|
||||||
for i in range(4):
|
for i in range(4):
|
||||||
next(adv_heart_pieces).advancement = True
|
next(adv_heart_pieces).advancement = True
|
||||||
|
|
||||||
beeweights = {0: {None: 100},
|
|
||||||
1: {None: 75, 'trap': 25},
|
|
||||||
2: {None: 40, 'trap': 40, 'bee': 20},
|
|
||||||
3: {'trap': 50, 'bee': 50},
|
|
||||||
4: {'trap': 100}}
|
|
||||||
|
|
||||||
def beemizer(item):
|
|
||||||
if world.beemizer[item.player] and not item.advancement and not item.priority and not item.type:
|
|
||||||
choice = world.random.choices(list(beeweights[world.beemizer[item.player]].keys()),
|
|
||||||
weights=list(beeweights[world.beemizer[item.player]].values()))[0]
|
|
||||||
return item if not choice else ItemFactory("Bee Trap", player) if choice == 'trap' else ItemFactory("Bee",
|
|
||||||
player)
|
|
||||||
return item
|
|
||||||
|
|
||||||
progressionitems = []
|
progressionitems = []
|
||||||
nonprogressionitems = []
|
nonprogressionitems = []
|
||||||
for item in items:
|
for item in items:
|
||||||
if item.advancement or item.priority or item.type:
|
if item.advancement or item.type:
|
||||||
progressionitems.append(item)
|
progressionitems.append(item)
|
||||||
|
elif world.beemizer[player] and item.name in trap_replaceable:
|
||||||
|
if world.random.random() < world.beemizer[item.player] * 0.25:
|
||||||
|
if world.random.random() < (0.5 + world.beemizer[item.player] * 0.1):
|
||||||
|
nonprogressionitems.append(ItemFactory("Bee Trap", player))
|
||||||
|
else:
|
||||||
|
nonprogressionitems.append(ItemFactory("Bee", player))
|
||||||
|
else:
|
||||||
|
nonprogressionitems.append(item)
|
||||||
else:
|
else:
|
||||||
nonprogressionitems.append(beemizer(item))
|
nonprogressionitems.append(item)
|
||||||
world.random.shuffle(nonprogressionitems)
|
world.random.shuffle(nonprogressionitems)
|
||||||
|
|
||||||
if additional_triforce_pieces:
|
if additional_triforce_pieces:
|
||||||
|
@ -445,75 +441,6 @@ def generate_itempool(world, player: int):
|
||||||
set_up_take_anys(world, player) # depends on world.itempool to be set
|
set_up_take_anys(world, player) # depends on world.itempool to be set
|
||||||
|
|
||||||
|
|
||||||
def shuffle_shops(world, items, player: int):
|
|
||||||
option = world.shop_shuffle[player]
|
|
||||||
if 'u' in option:
|
|
||||||
progressive = world.progressive[player]
|
|
||||||
progressive = world.random.choice([True, False]) if progressive == 'random' else progressive == 'on'
|
|
||||||
progressive &= world.goal == 'icerodhunt'
|
|
||||||
new_items = ["Bomb Upgrade (+5)"] * 6
|
|
||||||
new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
|
|
||||||
|
|
||||||
if not world.retro[player]:
|
|
||||||
new_items += ["Arrow Upgrade (+5)"] * 6
|
|
||||||
new_items.append("Arrow Upgrade (+5)" if progressive else "Arrow Upgrade (+10)")
|
|
||||||
|
|
||||||
world.random.shuffle(new_items) # Decide what gets tossed randomly if it can't insert everything.
|
|
||||||
|
|
||||||
for shop in world.shops:
|
|
||||||
if shop.type == ShopType.UpgradeShop and shop.region.player == player and \
|
|
||||||
shop.region.name == "Capacity Upgrade":
|
|
||||||
shop.clear_inventory()
|
|
||||||
|
|
||||||
if world.goal[player] != 'icerodhunt':
|
|
||||||
for i, item in enumerate(items):
|
|
||||||
if "Heart" not in item.name:
|
|
||||||
items[i] = ItemFactory(new_items.pop(), player)
|
|
||||||
if not new_items:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logging.warning(f"Not all upgrades put into Player{player}' item pool. Still missing: {new_items}")
|
|
||||||
else:
|
|
||||||
for item in new_items:
|
|
||||||
world.push_precollected(ItemFactory(item, player))
|
|
||||||
|
|
||||||
if 'p' in option or 'i' in option:
|
|
||||||
shops = []
|
|
||||||
upgrade_shops = []
|
|
||||||
total_inventory = []
|
|
||||||
for shop in world.shops:
|
|
||||||
if shop.region.player == player:
|
|
||||||
if shop.type == ShopType.UpgradeShop:
|
|
||||||
upgrade_shops.append(shop)
|
|
||||||
elif shop.type == ShopType.Shop and shop.region.name != 'Potion Shop':
|
|
||||||
shops.append(shop)
|
|
||||||
total_inventory.extend(shop.inventory)
|
|
||||||
|
|
||||||
if 'p' in option:
|
|
||||||
def price_adjust(price: int) -> int:
|
|
||||||
# it is important that a base price of 0 always returns 0 as new price!
|
|
||||||
return int(price * (0.5 + world.random.random() * 1.5))
|
|
||||||
|
|
||||||
def adjust_item(item):
|
|
||||||
if item:
|
|
||||||
item["price"] = price_adjust(item["price"])
|
|
||||||
item['replacement_price'] = price_adjust(item["price"])
|
|
||||||
|
|
||||||
for item in total_inventory:
|
|
||||||
adjust_item(item)
|
|
||||||
for shop in upgrade_shops:
|
|
||||||
for item in shop.inventory:
|
|
||||||
adjust_item(item)
|
|
||||||
|
|
||||||
if 'i' in option:
|
|
||||||
world.random.shuffle(total_inventory)
|
|
||||||
i = 0
|
|
||||||
for shop in shops:
|
|
||||||
slots = shop.slots
|
|
||||||
shop.inventory = total_inventory[i:i + slots]
|
|
||||||
i += slots
|
|
||||||
|
|
||||||
|
|
||||||
take_any_locations = {
|
take_any_locations = {
|
||||||
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
|
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
|
||||||
'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', 'Lumberjack House', 'Bonk Fairy (Light)',
|
'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', 'Lumberjack House', 'Bonk Fairy (Light)',
|
||||||
|
@ -548,7 +475,7 @@ def set_up_take_anys(world, player):
|
||||||
entrance = world.get_region(reg, player).entrances[0]
|
entrance = world.get_region(reg, player).entrances[0]
|
||||||
connect_entrance(world, entrance.name, old_man_take_any.name, player)
|
connect_entrance(world, entrance.name, old_man_take_any.name, player)
|
||||||
entrance.target = 0x58
|
entrance.target = 0x58
|
||||||
old_man_take_any.shop = TakeAny(old_man_take_any, 0x0112, 0xE2, True, True)
|
old_man_take_any.shop = TakeAny(old_man_take_any, 0x0112, 0xE2, True, True, total_shop_slots)
|
||||||
world.shops.append(old_man_take_any.shop)
|
world.shops.append(old_man_take_any.shop)
|
||||||
|
|
||||||
swords = [item for item in world.itempool if item.type == 'Sword' and item.player == player]
|
swords = [item for item in world.itempool if item.type == 'Sword' and item.player == player]
|
||||||
|
@ -570,7 +497,7 @@ def set_up_take_anys(world, player):
|
||||||
entrance = world.get_region(reg, player).entrances[0]
|
entrance = world.get_region(reg, player).entrances[0]
|
||||||
connect_entrance(world, entrance.name, take_any.name, player)
|
connect_entrance(world, entrance.name, take_any.name, player)
|
||||||
entrance.target = target
|
entrance.target = target
|
||||||
take_any.shop = TakeAny(take_any, room_id, 0xE3, True, True)
|
take_any.shop = TakeAny(take_any, room_id, 0xE3, True, True, total_shop_slots + num + 1)
|
||||||
world.shops.append(take_any.shop)
|
world.shops.append(take_any.shop)
|
||||||
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
|
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
|
||||||
take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)
|
take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)
|
||||||
|
@ -584,13 +511,14 @@ def create_dynamic_shop_locations(world, player):
|
||||||
if item is None:
|
if item is None:
|
||||||
continue
|
continue
|
||||||
if item['create_location']:
|
if item['create_location']:
|
||||||
loc = Location(player, "{} Item {}".format(shop.region.name, i+1), parent=shop.region)
|
loc = Location(player, "{} Slot {}".format(shop.region.name, i + 1), parent=shop.region)
|
||||||
shop.region.locations.append(loc)
|
shop.region.locations.append(loc)
|
||||||
world.dynamic_locations.append(loc)
|
world.dynamic_locations.append(loc)
|
||||||
|
|
||||||
world.clear_location_cache()
|
world.clear_location_cache()
|
||||||
|
|
||||||
world.push_item(loc, ItemFactory(item['item'], player), False)
|
world.push_item(loc, ItemFactory(item['item'], player), False)
|
||||||
|
loc.shop_slot = True
|
||||||
loc.event = True
|
loc.event = True
|
||||||
loc.locked = True
|
loc.locked = True
|
||||||
|
|
||||||
|
@ -602,16 +530,16 @@ def fill_prizes(world, attempts=15):
|
||||||
crystal_locations = [world.get_location('Turtle Rock - Prize', player), world.get_location('Eastern Palace - Prize', player), world.get_location('Desert Palace - Prize', player), world.get_location('Tower of Hera - Prize', player), world.get_location('Palace of Darkness - Prize', player),
|
crystal_locations = [world.get_location('Turtle Rock - Prize', player), world.get_location('Eastern Palace - Prize', player), world.get_location('Desert Palace - Prize', player), world.get_location('Tower of Hera - Prize', player), world.get_location('Palace of Darkness - Prize', player),
|
||||||
world.get_location('Thieves\' Town - Prize', player), world.get_location('Skull Woods - Prize', player), world.get_location('Swamp Palace - Prize', player), world.get_location('Ice Palace - Prize', player),
|
world.get_location('Thieves\' Town - Prize', player), world.get_location('Skull Woods - Prize', player), world.get_location('Swamp Palace - Prize', player), world.get_location('Ice Palace - Prize', player),
|
||||||
world.get_location('Misery Mire - Prize', player)]
|
world.get_location('Misery Mire - Prize', player)]
|
||||||
placed_prizes = [loc.item.name for loc in crystal_locations if loc.item is not None]
|
placed_prizes = {loc.item.name for loc in crystal_locations if loc.item}
|
||||||
unplaced_prizes = [crystal for crystal in crystals if crystal.name not in placed_prizes]
|
unplaced_prizes = [crystal for crystal in crystals if crystal.name not in placed_prizes]
|
||||||
empty_crystal_locations = [loc for loc in crystal_locations if loc.item is None]
|
empty_crystal_locations = [loc for loc in crystal_locations if not loc.item]
|
||||||
for attempt in range(attempts):
|
for attempt in range(attempts):
|
||||||
try:
|
try:
|
||||||
prizepool = list(unplaced_prizes)
|
prizepool = list(unplaced_prizes)
|
||||||
prize_locs = list(empty_crystal_locations)
|
prize_locs = list(empty_crystal_locations)
|
||||||
world.random.shuffle(prizepool)
|
world.random.shuffle(prizepool)
|
||||||
world.random.shuffle(prize_locs)
|
world.random.shuffle(prize_locs)
|
||||||
fill_restrictive(world, all_state, prize_locs, prizepool, True)
|
fill_restrictive(world, all_state, prize_locs, prizepool, True, lock=True)
|
||||||
except FillError as e:
|
except FillError as e:
|
||||||
logging.getLogger('').exception("Failed to place dungeon prizes (%s). Will retry %s more times", e,
|
logging.getLogger('').exception("Failed to place dungeon prizes (%s). Will retry %s more times", e,
|
||||||
attempts - attempt)
|
attempts - attempt)
|
||||||
|
@ -623,32 +551,6 @@ def fill_prizes(world, attempts=15):
|
||||||
raise FillError('Unable to place dungeon prizes')
|
raise FillError('Unable to place dungeon prizes')
|
||||||
|
|
||||||
|
|
||||||
def set_up_shops(world, player: int):
|
|
||||||
# TODO: move hard+ mode changes for shields here, utilizing the new shops
|
|
||||||
|
|
||||||
if world.retro[player]:
|
|
||||||
rss = world.get_region('Red Shield Shop', player).shop
|
|
||||||
replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50],
|
|
||||||
['Blue Shield', 50], ['Small Heart', 10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them.
|
|
||||||
if world.keyshuffle[player] == "universal":
|
|
||||||
replacement_items.append(['Small Key (Universal)', 100])
|
|
||||||
replacement_item = world.random.choice(replacement_items)
|
|
||||||
rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1])
|
|
||||||
rss.locked = True
|
|
||||||
|
|
||||||
if world.keyshuffle[player] == "universal" or world.retro[player]:
|
|
||||||
for shop in world.random.sample([s for s in world.shops if
|
|
||||||
s.custom and not s.locked and s.type == ShopType.Shop and s.region.player == player],
|
|
||||||
5):
|
|
||||||
shop.locked = True
|
|
||||||
slots = [0, 0, 1, 1, 2, 2]
|
|
||||||
world.random.shuffle(slots)
|
|
||||||
slots = iter(slots)
|
|
||||||
if world.keyshuffle[player] == "universal":
|
|
||||||
shop.add_inventory(next(slots), 'Small Key (Universal)', 100)
|
|
||||||
if world.retro[player]:
|
|
||||||
shop.push_inventory(next(slots), 'Single Arrow', 80)
|
|
||||||
|
|
||||||
def get_pool_core(world, player: int):
|
def get_pool_core(world, player: int):
|
||||||
progressive = world.progressive[player]
|
progressive = world.progressive[player]
|
||||||
shuffle = world.shuffle[player]
|
shuffle = world.shuffle[player]
|
||||||
|
|
|
@ -11,175 +11,179 @@ def ItemFactory(items, player):
|
||||||
singleton = True
|
singleton = True
|
||||||
for item in items:
|
for item in items:
|
||||||
if item in item_table:
|
if item in item_table:
|
||||||
advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit, hint_text = item_table[item]
|
ret.append(Item(item, *item_table[item], player))
|
||||||
ret.append(Item(item, advancement, priority, type, code, pedestal_hint, pedestal_credit, sickkid_credit, zora_credit, witch_credit, fluteboy_credit, hint_text, player))
|
|
||||||
else:
|
else:
|
||||||
logging.getLogger('').warning('Unknown Item: %s', item)
|
raise Exception(f"Unknown item {item}")
|
||||||
return None
|
|
||||||
|
|
||||||
if singleton:
|
if singleton:
|
||||||
return ret[0]
|
return ret[0]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
# Format: Name: (Advancement, Priority, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text)
|
# Format: Name: (Advancement, Type, ItemCode, Pedestal Hint Text, Pedestal Credit Text, Sick Kid Credit Text, Zora Credit Text, Witch Credit Text, Flute Boy Credit Text, Hint Text)
|
||||||
item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'),
|
item_table = {'Bow': (True, None, 0x0B, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'the Bow'),
|
||||||
'Progressive Bow': (True, False, None, 0x64, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
'Progressive Bow': (True, None, 0x64, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
||||||
'Progressive Bow (Alt)': (True, False, None, 0x65, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
'Progressive Bow (Alt)': (True, None, 0x65, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
||||||
'Silver Arrows': (True, False, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane','ganon-killing kid', 'ganon doom for sale', 'fungus for pork','archer boy shines again', 'the Silver Arrows'),
|
'Silver Arrows': (True, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane','ganon-killing kid', 'ganon doom for sale', 'fungus for pork','archer boy shines again', 'the Silver Arrows'),
|
||||||
'Silver Bow': (True, False, None, 0x3B, 'Buy 1 Silver\nget Archery\nfor free.', 'the baconmaker', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the Silver Bow'),
|
'Silver Bow': (True, None, 0x3B, 'Buy 1 Silver\nget Archery\nfor free.', 'the baconmaker', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the Silver Bow'),
|
||||||
'Book of Mudora': (True, False, None, 0x1D, 'Hylian\nfor\nDingusses.', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'),
|
'Book of Mudora': (True, None, 0x1D, 'Hylian\nfor\nDingusses.', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'),
|
||||||
'Hammer': (True, False, None, 0x09, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the Hammer'),
|
'Hammer': (True, None, 0x09, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the Hammer'),
|
||||||
'Hookshot': (True, False, None, 0x0A, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'),
|
'Hookshot': (True, None, 0x0A, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'),
|
||||||
'Magic Mirror': (True, False, None, 0x1A, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'),
|
'Magic Mirror': (True, None, 0x1A, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'),
|
||||||
'Flute': (True, False, None, 0x14, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'flute boy plays again', 'the Flute'),
|
'Flute': (True, None, 0x14, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'flute boy plays again', 'the Flute'),
|
||||||
'Pegasus Boots': (True, False, None, 0x4B, 'Gotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'the Boots'),
|
'Pegasus Boots': (True, None, 0x4B, 'Gotta go fast!', 'and the sprint shoes', 'the running-man kid', 'sprint shoe for sale', 'shrooms for speed', 'gotta-go-fast boy runs again', 'the Boots'),
|
||||||
'Power Glove': (True, False, None, 0x1B, 'Now you can\nlift weak\nstuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'the Glove'),
|
'Power Glove': (True, None, 0x1B, 'Now you can\nlift weak\nstuff!', 'and the grey mittens', 'body-building kid', 'lift glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'the Glove'),
|
||||||
'Cape': (True, False, None, 0x19, 'Wear this to\nbecome\ninvisible!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'dapper boy hides again', 'the Cape'),
|
'Cape': (True, None, 0x19, 'Wear this to\nbecome\ninvisible!', 'the camouflage cape', 'red riding-hood kid', 'red hood for sale', 'hood from a hood', 'dapper boy hides again', 'the Cape'),
|
||||||
'Mushroom': (True, False, None, 0x29, 'I\'m a fun guy!\n\nI\'m a funghi!', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'the Mushroom'),
|
'Mushroom': (True, None, 0x29, 'I\'m a fun guy!\n\nI\'m a funghi!', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'the Mushroom'),
|
||||||
'Shovel': (True, False, None, 0x13, 'Can\n You\n Dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'the Shovel'),
|
'Shovel': (True, None, 0x13, 'Can\n You\n Dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'the Shovel'),
|
||||||
'Lamp': (True, False, None, 0x12, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the Lamp'),
|
'Lamp': (True, None, 0x12, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the Lamp'),
|
||||||
'Magic Powder': (True, False, None, 0x0D, 'you can turn\nanti-faeries\ninto faeries', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the Powder'),
|
'Magic Powder': (True, None, 0x0D, 'you can turn\nanti-faeries\ninto faeries', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the Powder'),
|
||||||
'Moon Pearl': (True, False, None, 0x1F, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'),
|
'Moon Pearl': (True, None, 0x1F, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'),
|
||||||
'Cane of Somaria': (True, False, None, 0x15, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'),
|
'Cane of Somaria': (True, None, 0x15, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'),
|
||||||
'Fire Rod': (True, False, None, 0x07, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'),
|
'Fire Rod': (True, None, 0x07, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'),
|
||||||
'Flippers': (True, False, None, 0x1E, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the Flippers'),
|
'Flippers': (True, None, 0x1E, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the Flippers'),
|
||||||
'Ice Rod': (True, False, None, 0x08, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'),
|
'Ice Rod': (True, None, 0x08, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'),
|
||||||
'Titans Mitts': (True, False, None, 0x1C, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'),
|
'Titans Mitts': (True, None, 0x1C, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'),
|
||||||
'Bombos': (True, False, None, 0x0F, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'),
|
'Bombos': (True, None, 0x0F, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'),
|
||||||
'Ether': (True, False, None, 0x10, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'),
|
'Ether': (True, None, 0x10, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'),
|
||||||
'Quake': (True, False, None, 0x11, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'),
|
'Quake': (True, None, 0x11, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'),
|
||||||
'Bottle': (True, False, None, 0x16, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a bottle'),
|
'Bottle': (True, None, 0x16, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a bottle'),
|
||||||
'Bottle (Red Potion)': (True, False, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a bottle'),
|
'Bottle (Red Potion)': (True, None, 0x2B, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a bottle'),
|
||||||
'Bottle (Green Potion)': (True, False, None, 0x2C, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a bottle'),
|
'Bottle (Green Potion)': (True, None, 0x2C, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a bottle'),
|
||||||
'Bottle (Blue Potion)': (True, False, None, 0x2D, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a bottle'),
|
'Bottle (Blue Potion)': (True, None, 0x2D, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a bottle'),
|
||||||
'Bottle (Fairy)': (True, False, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid','hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a bottle'),
|
'Bottle (Fairy)': (True, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid','hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a bottle'),
|
||||||
'Bottle (Bee)': (True, False, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bottle'),
|
'Bottle (Bee)': (True, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bottle'),
|
||||||
'Bottle (Good Bee)': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a bottle'),
|
'Bottle (Good Bee)': (True, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a bottle'),
|
||||||
'Master Sword': (True, False, 'Sword', 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
|
'Master Sword': (True, 'Sword', 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
|
||||||
'Tempered Sword': (True, False, 'Sword', 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'),
|
'Tempered Sword': (True, 'Sword', 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'),
|
||||||
'Fighter Sword': (True, False, 'Sword', 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Small Sword'),
|
'Fighter Sword': (True, 'Sword', 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Small Sword'),
|
||||||
'Golden Sword': (True, False, 'Sword', 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'),
|
'Golden Sword': (True, 'Sword', 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'),
|
||||||
'Progressive Sword': (True, False, 'Sword', 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
|
'Progressive Sword': (True, 'Sword', 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
|
||||||
'Progressive Glove': (True, False, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
|
'Progressive Glove': (True, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
|
||||||
'Green Pendant': (True, False, 'Crystal', (0x04, 0x38, 0x62, 0x00, 0x69, 0x01), None, None, None, None, None, None, None),
|
'Green Pendant': (True, 'Crystal', (0x04, 0x38, 0x62, 0x00, 0x69, 0x01), None, None, None, None, None, None, None),
|
||||||
'Blue Pendant': (True, False, 'Crystal', (0x02, 0x34, 0x60, 0x00, 0x69, 0x02), None, None, None, None, None, None, None),
|
'Blue Pendant': (True, 'Crystal', (0x02, 0x34, 0x60, 0x00, 0x69, 0x02), None, None, None, None, None, None, None),
|
||||||
'Red Pendant': (True, False, 'Crystal', (0x01, 0x32, 0x60, 0x00, 0x69, 0x03), None, None, None, None, None, None, None),
|
'Red Pendant': (True, 'Crystal', (0x01, 0x32, 0x60, 0x00, 0x69, 0x03), None, None, None, None, None, None, None),
|
||||||
'Triforce': (True, False, None, 0x6A, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'),
|
'Triforce': (True, None, 0x6A, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'),
|
||||||
'Power Star': (True, False, None, 0x6B, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'),
|
'Power Star': (True, None, 0x6B, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'),
|
||||||
'Triforce Piece': (True, False, None, 0x6C, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'),
|
'Triforce Piece': (True, None, 0x6C, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'),
|
||||||
'Crystal 1': (True, False, 'Crystal', (0x02, 0x34, 0x64, 0x40, 0x7F, 0x06), None, None, None, None, None, None, None),
|
'Crystal 1': (True, 'Crystal', (0x02, 0x34, 0x64, 0x40, 0x7F, 0x06), None, None, None, None, None, None, None),
|
||||||
'Crystal 2': (True, False, 'Crystal', (0x10, 0x34, 0x64, 0x40, 0x79, 0x06), None, None, None, None, None, None, None),
|
'Crystal 2': (True, 'Crystal', (0x10, 0x34, 0x64, 0x40, 0x79, 0x06), None, None, None, None, None, None, None),
|
||||||
'Crystal 3': (True, False, 'Crystal', (0x40, 0x34, 0x64, 0x40, 0x6C, 0x06), None, None, None, None, None, None, None),
|
'Crystal 3': (True, 'Crystal', (0x40, 0x34, 0x64, 0x40, 0x6C, 0x06), None, None, None, None, None, None, None),
|
||||||
'Crystal 4': (True, False, 'Crystal', (0x20, 0x34, 0x64, 0x40, 0x6D, 0x06), None, None, None, None, None, None, None),
|
'Crystal 4': (True, 'Crystal', (0x20, 0x34, 0x64, 0x40, 0x6D, 0x06), None, None, None, None, None, None, None),
|
||||||
'Crystal 5': (True, False, 'Crystal', (0x04, 0x32, 0x64, 0x40, 0x6E, 0x06), None, None, None, None, None, None, None),
|
'Crystal 5': (True, 'Crystal', (0x04, 0x32, 0x64, 0x40, 0x6E, 0x06), None, None, None, None, None, None, None),
|
||||||
'Crystal 6': (True, False, 'Crystal', (0x01, 0x32, 0x64, 0x40, 0x6F, 0x06), None, None, None, None, None, None, None),
|
'Crystal 6': (True, 'Crystal', (0x01, 0x32, 0x64, 0x40, 0x6F, 0x06), None, None, None, None, None, None, None),
|
||||||
'Crystal 7': (True, False, 'Crystal', (0x08, 0x34, 0x64, 0x40, 0x7C, 0x06), None, None, None, None, None, None, None),
|
'Crystal 7': (True, 'Crystal', (0x08, 0x34, 0x64, 0x40, 0x7C, 0x06), None, None, None, None, None, None, None),
|
||||||
'Single Arrow': (False, False, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
|
'Single Arrow': (False, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
|
||||||
'Arrows (10)': (False, False, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack','stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again','ten arrows'),
|
'Arrows (10)': (False, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack','stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again','ten arrows'),
|
||||||
'Arrow Upgrade (+10)': (False, False, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
'Arrow Upgrade (+10)': (False, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
||||||
'Arrow Upgrade (+5)': (False, False, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
'Arrow Upgrade (+5)': (False, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
||||||
'Single Bomb': (False, False, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
|
'Single Bomb': (False, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
|
||||||
'Bombs (3)': (False, False, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
|
'Bombs (3)': (False, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
|
||||||
'Bombs (10)': (False, False, None, 0x31, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),
|
'Bombs (10)': (False, None, 0x31, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),
|
||||||
'Bomb Upgrade (+10)': (False, False, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
|
'Bomb Upgrade (+10)': (False, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
|
||||||
'Bomb Upgrade (+5)': (False, False, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
|
'Bomb Upgrade (+5)': (False, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
|
||||||
'Blue Mail': (False, True, None, 0x22, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the Blue Mail'),
|
'Blue Mail': (False, None, 0x22, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the Blue Mail'),
|
||||||
'Red Mail': (False, True, None, 0x23, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the Red Mail'),
|
'Red Mail': (False, None, 0x23, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the Red Mail'),
|
||||||
'Progressive Mail': (False, True, None, 0x60, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'),
|
'Progressive Mail': (False, None, 0x60, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'),
|
||||||
'Blue Boomerang': (True, False, None, 0x0C, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the Blue Boomerang'),
|
'Blue Boomerang': (True, None, 0x0C, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the Blue Boomerang'),
|
||||||
'Red Boomerang': (True, False, None, 0x2A, 'No matter what\nyou do, red\nreturns to you', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again', 'the Red Boomerang'),
|
'Red Boomerang': (True, None, 0x2A, 'No matter what\nyou do, red\nreturns to you', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again', 'the Red Boomerang'),
|
||||||
'Blue Shield': (False, True, None, 0x04, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'the Blue Shield'),
|
'Blue Shield': (False, None, 0x04, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'the Blue Shield'),
|
||||||
'Red Shield': (False, True, None, 0x05, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'the Red Shield'),
|
'Red Shield': (False, None, 0x05, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'the Red Shield'),
|
||||||
'Mirror Shield': (True, False, None, 0x06, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'the Mirror Shield'),
|
'Mirror Shield': (True, None, 0x06, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'the Mirror Shield'),
|
||||||
'Progressive Shield': (True, False, None, 0x5F, 'have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'),
|
'Progressive Shield': (True, None, 0x5F, 'have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'),
|
||||||
'Bug Catching Net': (True, False, None, 0x21, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again', 'the Bug Net'),
|
'Bug Catching Net': (True, None, 0x21, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again', 'the Bug Net'),
|
||||||
'Cane of Byrna': (True, False, None, 0x18, 'Use this to\nbecome\ninvincible!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'the Blue Cane'),
|
'Cane of Byrna': (True, None, 0x18, 'Use this to\nbecome\ninvincible!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'the Blue Cane'),
|
||||||
'Boss Heart Container': (False, False, None, 0x3E, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
|
'Boss Heart Container': (False, None, 0x3E, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
|
||||||
'Sanctuary Heart Container': (False, False, None, 0x3F, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
|
'Sanctuary Heart Container': (False, None, 0x3F, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
|
||||||
'Piece of Heart': (False, False, None, 0x17, 'Just a little\npiece of love!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart piece'),
|
'Piece of Heart': (False, None, 0x17, 'Just a little\npiece of love!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart piece'),
|
||||||
'Rupee (1)': (False, False, None, 0x34, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a green rupee'),
|
'Rupee (1)': (False, None, 0x34, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a green rupee'),
|
||||||
'Rupees (5)': (False, False, None, 0x35, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a blue rupee'),
|
'Rupees (5)': (False, None, 0x35, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a blue rupee'),
|
||||||
'Rupees (20)': (False, False, None, 0x36, 'Just couch\ncash. Move\nright along.', 'and the couch cash', 'the piggy-bank kid', 'life lesson for sale', 'the witch buying drugs', 'destitute boy has lunch again', 'a red rupee'),
|
'Rupees (20)': (False, None, 0x36, 'Just couch\ncash. Move\nright along.', 'and the couch cash', 'the piggy-bank kid', 'life lesson for sale', 'the witch buying drugs', 'destitute boy has lunch again', 'a red rupee'),
|
||||||
'Rupees (50)': (False, False, None, 0x41, 'A rupee pile!\nOkay?', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again', 'fifty rupees'),
|
'Rupees (50)': (False, None, 0x41, 'A rupee pile!\nOkay?', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again', 'fifty rupees'),
|
||||||
'Rupees (100)': (False, False, None, 0x40, 'A rupee stash!\nHell yeah!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again', 'one hundred rupees'),
|
'Rupees (100)': (False, None, 0x40, 'A rupee stash!\nHell yeah!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again', 'one hundred rupees'),
|
||||||
'Rupees (300)': (False, False, None, 0x46, 'A rupee hoard!\nHell yeah!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again', 'three hundred rupees'),
|
'Rupees (300)': (False, None, 0x46, 'A rupee hoard!\nHell yeah!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again', 'three hundred rupees'),
|
||||||
'Rupoor': (False, False, None, 0x59, 'a debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'),
|
'Rupoor': (False, None, 0x59, 'a debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'),
|
||||||
'Red Clock': (False, True, None, 0x5B, 'a waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'),
|
'Red Clock': (False, None, 0x5B, 'a waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'),
|
||||||
'Blue Clock': (False, True, None, 0x5C, 'a bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'),
|
'Blue Clock': (False, None, 0x5C, 'a bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'),
|
||||||
'Green Clock': (False, True, None, 0x5D, 'a lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'),
|
'Green Clock': (False, None, 0x5D, 'a lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'),
|
||||||
'Single RNG': (False, True, None, 0x62, 'something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'a new mystery'),
|
'Single RNG': (False, None, 0x62, 'something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'a new mystery'),
|
||||||
'Multi RNG': (False, True, None, 0x63, 'something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'),
|
'Multi RNG': (False, None, 0x63, 'something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'),
|
||||||
'Magic Upgrade (1/2)': (True, False, None, 0x4E, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
|
'Magic Upgrade (1/2)': (True, None, 0x4E, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
|
||||||
'Magic Upgrade (1/4)': (True, False, None, 0x4F, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
|
'Magic Upgrade (1/4)': (True, None, 0x4F, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
|
||||||
'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Eastern Palace'),
|
'Small Key (Eastern Palace)': (False, 'SmallKey', 0xA2, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Eastern Palace'),
|
||||||
'Big Key (Eastern Palace)': (False, False, 'BigKey', 0x9D, 'A big key to Armos Knights', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Eastern Palace'),
|
'Big Key (Eastern Palace)': (False, 'BigKey', 0x9D, 'A big key to Armos Knights', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Eastern Palace'),
|
||||||
'Compass (Eastern Palace)': (False, True, 'Compass', 0x8D, 'Now you can find the Armos Knights!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Eastern Palace'),
|
'Compass (Eastern Palace)': (False, 'Compass', 0x8D, 'Now you can find the Armos Knights!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Eastern Palace'),
|
||||||
'Map (Eastern Palace)': (False, True, 'Map', 0x7D, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Eastern Palace'),
|
'Map (Eastern Palace)': (False, 'Map', 0x7D, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Eastern Palace'),
|
||||||
'Small Key (Desert Palace)': (False, False, 'SmallKey', 0xA3, 'A small key to the desert', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Desert Palace'),
|
'Small Key (Desert Palace)': (False, 'SmallKey', 0xA3, 'A small key to the desert', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Desert Palace'),
|
||||||
'Big Key (Desert Palace)': (False, False, 'BigKey', 0x9C, 'A big key to the desert', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Desert Palace'),
|
'Big Key (Desert Palace)': (False, 'BigKey', 0x9C, 'A big key to the desert', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Desert Palace'),
|
||||||
'Compass (Desert Palace)': (False, True, 'Compass', 0x8C, 'Now you can find Lanmolas!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Desert Palace'),
|
'Compass (Desert Palace)': (False, 'Compass', 0x8C, 'Now you can find Lanmolas!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Desert Palace'),
|
||||||
'Map (Desert Palace)': (False, True, 'Map', 0x7C, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Desert Palace'),
|
'Map (Desert Palace)': (False, 'Map', 0x7C, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Desert Palace'),
|
||||||
'Small Key (Tower of Hera)': (False, False, 'SmallKey', 0xAA, 'A small key to Hera', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Tower of Hera'),
|
'Small Key (Tower of Hera)': (False, 'SmallKey', 0xAA, 'A small key to Hera', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Tower of Hera'),
|
||||||
'Big Key (Tower of Hera)': (False, False, 'BigKey', 0x95, 'A big key to Hera', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Tower of Hera'),
|
'Big Key (Tower of Hera)': (False, 'BigKey', 0x95, 'A big key to Hera', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Tower of Hera'),
|
||||||
'Compass (Tower of Hera)': (False, True, 'Compass', 0x85, 'Now you can find Moldorm!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Tower of Hera'),
|
'Compass (Tower of Hera)': (False, 'Compass', 0x85, 'Now you can find Moldorm!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Tower of Hera'),
|
||||||
'Map (Tower of Hera)': (False, True, 'Map', 0x75, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Tower of Hera'),
|
'Map (Tower of Hera)': (False, 'Map', 0x75, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Tower of Hera'),
|
||||||
'Small Key (Hyrule Castle)': (False, False, 'SmallKey', 0xA0, 'A small key to the castle', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Hyrule Castle'),
|
'Small Key (Hyrule Castle)': (False, 'SmallKey', 0xA0, 'A small key to the castle', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Hyrule Castle'),
|
||||||
'Big Key (Hyrule Castle)': (False, False, 'BigKey', 0x9F, 'A big key to the castle', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Hyrule Castle'),
|
'Big Key (Hyrule Castle)': (False, 'BigKey', 0x9F, 'A big key to the castle', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Hyrule Castle'),
|
||||||
'Compass (Hyrule Castle)': (False, True, 'Compass', 0x8F, 'Now you can find no boss!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Hyrule Castle'),
|
'Compass (Hyrule Castle)': (False, 'Compass', 0x8F, 'Now you can find no boss!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Hyrule Castle'),
|
||||||
'Map (Hyrule Castle)': (False, True, 'Map', 0x7F, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Hyrule Castle'),
|
'Map (Hyrule Castle)': (False, 'Map', 0x7F, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Hyrule Castle'),
|
||||||
'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Castle Tower'),
|
'Small Key (Agahnims Tower)': (False, 'SmallKey', 0xA4, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Castle Tower'),
|
||||||
# doors-specific items, baserom will not be able to understand these
|
# doors-specific items, baserom will not be able to understand these
|
||||||
'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 'A big key to Agahnim', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'),
|
'Big Key (Agahnims Tower)': (False, 'BigKey', 0x9B, 'A big key to Agahnim', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'),
|
||||||
'Compass (Agahnims Tower)': (False, True, 'Compass', 0x8B, 'Now you can find Aga1!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Castle Tower'),
|
'Compass (Agahnims Tower)': (False, 'Compass', 0x8B, 'Now you can find Aga1!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Castle Tower'),
|
||||||
'Map (Agahnims Tower)': (False, True, 'Map', 0x7B, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Castle Tower'),
|
'Map (Agahnims Tower)': (False, 'Map', 0x7B, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Castle Tower'),
|
||||||
# end of doors-specific items
|
# end of doors-specific items
|
||||||
'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'),
|
'Small Key (Palace of Darkness)': (False, 'SmallKey', 0xA6, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'),
|
||||||
'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'),
|
'Big Key (Palace of Darkness)': (False, 'BigKey', 0x99, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'),
|
||||||
'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'),
|
'Compass (Palace of Darkness)': (False, 'Compass', 0x89, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'),
|
||||||
'Map (Palace of Darkness)': (False, True, 'Map', 0x79, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Palace of Darkness'),
|
'Map (Palace of Darkness)': (False, 'Map', 0x79, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Palace of Darkness'),
|
||||||
'Small Key (Thieves Town)': (False, False, 'SmallKey', 0xAB, 'A small key to thievery', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Thieves\' Town'),
|
'Small Key (Thieves Town)': (False, 'SmallKey', 0xAB, 'A small key to thievery', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Thieves\' Town'),
|
||||||
'Big Key (Thieves Town)': (False, False, 'BigKey', 0x94, 'A big key to thievery', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Thieves\' Town'),
|
'Big Key (Thieves Town)': (False, 'BigKey', 0x94, 'A big key to thievery', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Thieves\' Town'),
|
||||||
'Compass (Thieves Town)': (False, True, 'Compass', 0x84, 'Now you can find Blind!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Thieves\' Town'),
|
'Compass (Thieves Town)': (False, 'Compass', 0x84, 'Now you can find Blind!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Thieves\' Town'),
|
||||||
'Map (Thieves Town)': (False, True, 'Map', 0x74, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Thieves\' Town'),
|
'Map (Thieves Town)': (False, 'Map', 0x74, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Thieves\' Town'),
|
||||||
'Small Key (Skull Woods)': (False, False, 'SmallKey', 0xA8, 'A small key to the woods', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Skull Woods'),
|
'Small Key (Skull Woods)': (False, 'SmallKey', 0xA8, 'A small key to the woods', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Skull Woods'),
|
||||||
'Big Key (Skull Woods)': (False, False, 'BigKey', 0x97, 'A big key to the woods', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Skull Woods'),
|
'Big Key (Skull Woods)': (False, 'BigKey', 0x97, 'A big key to the woods', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Skull Woods'),
|
||||||
'Compass (Skull Woods)': (False, True, 'Compass', 0x87, 'Now you can find Mothula!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Skull Woods'),
|
'Compass (Skull Woods)': (False, 'Compass', 0x87, 'Now you can find Mothula!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Skull Woods'),
|
||||||
'Map (Skull Woods)': (False, True, 'Map', 0x77, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Skull Woods'),
|
'Map (Skull Woods)': (False, 'Map', 0x77, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Skull Woods'),
|
||||||
'Small Key (Swamp Palace)': (False, False, 'SmallKey', 0xA5, 'A small key to the swamp', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Swamp Palace'),
|
'Small Key (Swamp Palace)': (False, 'SmallKey', 0xA5, 'A small key to the swamp', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Swamp Palace'),
|
||||||
'Big Key (Swamp Palace)': (False, False, 'BigKey', 0x9A, 'A big key to the swamp', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Swamp Palace'),
|
'Big Key (Swamp Palace)': (False, 'BigKey', 0x9A, 'A big key to the swamp', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Swamp Palace'),
|
||||||
'Compass (Swamp Palace)': (False, True, 'Compass', 0x8A, 'Now you can find Arrghus!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Swamp Palace'),
|
'Compass (Swamp Palace)': (False, 'Compass', 0x8A, 'Now you can find Arrghus!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Swamp Palace'),
|
||||||
'Map (Swamp Palace)': (False, True, 'Map', 0x7A, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Swamp Palace'),
|
'Map (Swamp Palace)': (False, 'Map', 0x7A, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Swamp Palace'),
|
||||||
'Small Key (Ice Palace)': (False, False, 'SmallKey', 0xA9, 'A small key to the iceberg', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ice Palace'),
|
'Small Key (Ice Palace)': (False, 'SmallKey', 0xA9, 'A small key to the iceberg', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ice Palace'),
|
||||||
'Big Key (Ice Palace)': (False, False, 'BigKey', 0x96, 'A big key to the iceberg', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ice Palace'),
|
'Big Key (Ice Palace)': (False, 'BigKey', 0x96, 'A big key to the iceberg', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ice Palace'),
|
||||||
'Compass (Ice Palace)': (False, True, 'Compass', 0x86, 'Now you can find Kholdstare!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Ice Palace'),
|
'Compass (Ice Palace)': (False, 'Compass', 0x86, 'Now you can find Kholdstare!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Ice Palace'),
|
||||||
'Map (Ice Palace)': (False, True, 'Map', 0x76, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ice Palace'),
|
'Map (Ice Palace)': (False, 'Map', 0x76, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ice Palace'),
|
||||||
'Small Key (Misery Mire)': (False, False, 'SmallKey', 0xA7, 'A small key to the mire', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Misery Mire'),
|
'Small Key (Misery Mire)': (False, 'SmallKey', 0xA7, 'A small key to the mire', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Misery Mire'),
|
||||||
'Big Key (Misery Mire)': (False, False, 'BigKey', 0x98, 'A big key to the mire', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Misery Mire'),
|
'Big Key (Misery Mire)': (False, 'BigKey', 0x98, 'A big key to the mire', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Misery Mire'),
|
||||||
'Compass (Misery Mire)': (False, True, 'Compass', 0x88, 'Now you can find Vitreous!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Misery Mire'),
|
'Compass (Misery Mire)': (False, 'Compass', 0x88, 'Now you can find Vitreous!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Misery Mire'),
|
||||||
'Map (Misery Mire)': (False, True, 'Map', 0x78, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Misery Mire'),
|
'Map (Misery Mire)': (False, 'Map', 0x78, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Misery Mire'),
|
||||||
'Small Key (Turtle Rock)': (False, False, 'SmallKey', 0xAC, 'A small key to the pipe maze', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Turtle Rock'),
|
'Small Key (Turtle Rock)': (False, 'SmallKey', 0xAC, 'A small key to the pipe maze', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Turtle Rock'),
|
||||||
'Big Key (Turtle Rock)': (False, False, 'BigKey', 0x93, 'A big key to the pipe maze', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Turtle Rock'),
|
'Big Key (Turtle Rock)': (False, 'BigKey', 0x93, 'A big key to the pipe maze', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Turtle Rock'),
|
||||||
'Compass (Turtle Rock)': (False, True, 'Compass', 0x83, 'Now you can find Trinexx!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Turtle Rock'),
|
'Compass (Turtle Rock)': (False, 'Compass', 0x83, 'Now you can find Trinexx!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Turtle Rock'),
|
||||||
'Map (Turtle Rock)': (False, True, 'Map', 0x73, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Turtle Rock'),
|
'Map (Turtle Rock)': (False, 'Map', 0x73, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Turtle Rock'),
|
||||||
'Small Key (Ganons Tower)': (False, False, 'SmallKey', 0xAD, 'A small key to the evil tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ganon\'s Tower'),
|
'Small Key (Ganons Tower)': (False, 'SmallKey', 0xAD, 'A small key to the evil tower', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Ganon\'s Tower'),
|
||||||
'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 'A big key to the evil tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ganon\'s Tower'),
|
'Big Key (Ganons Tower)': (False, 'BigKey', 0x92, 'A big key to the evil tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Ganon\'s Tower'),
|
||||||
'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 'Now you can find Agahnim!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Ganon\'s Tower'),
|
'Compass (Ganons Tower)': (False, 'Compass', 0x82, 'Now you can find Agahnim!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Ganon\'s Tower'),
|
||||||
'Map (Ganons Tower)': (False, True, 'Map', 0x72, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ganon\'s Tower'),
|
'Map (Ganons Tower)': (False, 'Map', 0x72, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Ganon\'s Tower'),
|
||||||
'Small Key (Universal)': (False, True, None, 0xAF, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key'),
|
'Small Key (Universal)': (False, None, 0xAF, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key'),
|
||||||
'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'nothing'),
|
'Nothing': (False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again', 'nothing'),
|
||||||
'Bee Trap': (False, False, None, 0xB0, 'We will sting your face a whole lot!', 'and the sting buddies', 'the beekeeper kid', 'insects for sale', 'shroom pollenation', 'bottle boy has mad bees again', 'Friendship'),
|
'Bee Trap': (False, None, 0xB0, 'We will sting your face a whole lot!', 'and the sting buddies', 'the beekeeper kid', 'insects for sale', 'shroom pollenation', 'bottle boy has mad bees again', 'Friendship'),
|
||||||
'Red Potion': (False, False, None, 0x2E, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a red potion'),
|
'Faerie': (False, None, 0xB1, 'Save me and I will revive you', 'and the captive', 'the tingle kid','hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a faerie'),
|
||||||
'Green Potion': (False, False, None, 0x2F, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a green potion'),
|
'Good Bee': (False, None, 0xB2, 'Save me and I will sting you (sometimes)', 'and the captive', 'the tingle kid','hostage for sale', 'good dust and shrooms', 'bottle boy has friend again', 'a bee'),
|
||||||
'Blue Potion': (False, False, None, 0x30, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'),
|
'Magic Jar': (False, None, 0xB3, '', '', '','', '', '', ''),
|
||||||
'Bee': (False, False, None, 0x0E, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bee'),
|
'Apple': (False, None, 0xB4, '', '', '','', '', '', ''),
|
||||||
'Small Heart': (False, False, None, 0x42, 'Just a little\npiece of love!', 'and the heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart'),
|
# 'Hint': (False, None, 0xB5, '', '', '','', '', '', ''),
|
||||||
'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
# 'Bomb Trap': (False, None, 0xB6, '', '', '','', '', '', ''),
|
||||||
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
'Red Potion': (False, None, 0x2E, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a red potion'),
|
||||||
'Get Frog': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
'Green Potion': (False, None, 0x2F, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a green potion'),
|
||||||
'Return Smith': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
'Blue Potion': (False, None, 0x30, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a blue potion'),
|
||||||
'Pick Up Purple Chest': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
'Bee': (False, None, 0x0E, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bee'),
|
||||||
'Open Floodgate': (True, False, 'Event', None, None, None, None, None, None, None, None),
|
'Small Heart': (False, None, 0x42, 'Just a little\npiece of love!', 'and the heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart'),
|
||||||
|
'Beat Agahnim 1': (True, 'Event', None, None, None, None, None, None, None, None),
|
||||||
|
'Beat Agahnim 2': (True, 'Event', None, None, None, None, None, None, None, None),
|
||||||
|
'Get Frog': (True, 'Event', None, None, None, None, None, None, None, None),
|
||||||
|
'Return Smith': (True, 'Event', None, None, None, None, None, None, None, None),
|
||||||
|
'Pick Up Purple Chest': (True, 'Event', None, None, None, None, None, None, None, None),
|
||||||
|
'Open Floodgate': (True, 'Event', None, None, None, None, None, None, None, None),
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup_id_to_name = {data[3]: name for name, data in item_table.items()}
|
lookup_id_to_name = {data[3]: name for name, data in item_table.items()}
|
||||||
|
@ -228,3 +232,5 @@ progression_items = {name for name, data in item_table.items() if type(data[3])
|
||||||
item_name_groups['Everything'] = {name for name, data in item_table.items() if type(data[3]) == int}
|
item_name_groups['Everything'] = {name for name, data in item_table.items() if type(data[3]) == int}
|
||||||
item_name_groups['Progression Items'] = progression_items
|
item_name_groups['Progression Items'] = progression_items
|
||||||
item_name_groups['Non Progression Items'] = item_name_groups['Everything'] - progression_items
|
item_name_groups['Non Progression Items'] = item_name_groups['Everything'] - progression_items
|
||||||
|
|
||||||
|
trap_replaceable = item_name_groups['Rupees'] | {'Arrows (10)', 'Single Bomb', 'Bombs (3)', 'Bombs (10)', 'Nothing'}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import pickle
|
||||||
|
|
||||||
from BaseClasses import MultiWorld, CollectionState, Item, Region, Location
|
from BaseClasses import MultiWorld, CollectionState, Item, Region, Location
|
||||||
from worlds.alttp.Items import ItemFactory, item_table, item_name_groups
|
from worlds.alttp.Items import ItemFactory, item_table, item_name_groups
|
||||||
from worlds.alttp.Regions import create_regions, create_shops, mark_light_world_regions, \
|
from worlds.alttp.Regions import create_regions, mark_light_world_regions, \
|
||||||
lookup_vanilla_location_to_entrance
|
lookup_vanilla_location_to_entrance
|
||||||
from worlds.alttp.InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
from worlds.alttp.InvertedRegions import create_inverted_regions, mark_dark_world_regions
|
||||||
from worlds.alttp.EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
|
from worlds.alttp.EntranceShuffle import link_entrances, link_inverted_entrances, plando_connect
|
||||||
|
@ -84,6 +84,7 @@ def main(args, seed=None):
|
||||||
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
world.triforce_pieces_available = args.triforce_pieces_available.copy()
|
||||||
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
world.triforce_pieces_required = args.triforce_pieces_required.copy()
|
||||||
world.shop_shuffle = args.shop_shuffle.copy()
|
world.shop_shuffle = args.shop_shuffle.copy()
|
||||||
|
world.shop_shuffle_slots = args.shop_shuffle_slots.copy()
|
||||||
world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()}
|
world.progression_balancing = {player: not balance for player, balance in args.skip_progression_balancing.items()}
|
||||||
world.shuffle_prizes = args.shuffle_prizes.copy()
|
world.shuffle_prizes = args.shuffle_prizes.copy()
|
||||||
world.sprite_pool = args.sprite_pool.copy()
|
world.sprite_pool = args.sprite_pool.copy()
|
||||||
|
@ -111,6 +112,14 @@ def main(args, seed=None):
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
world.difficulty_requirements[player] = difficulties[world.difficulty[player]]
|
world.difficulty_requirements[player] = difficulties[world.difficulty[player]]
|
||||||
|
|
||||||
|
if world.open_pyramid[player] == 'goal':
|
||||||
|
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'}
|
||||||
|
elif world.open_pyramid[player] == 'auto':
|
||||||
|
world.open_pyramid[player] = world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} and \
|
||||||
|
(world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull'} or not world.shuffle_ganon)
|
||||||
|
else:
|
||||||
|
world.open_pyramid[player] = {'on': True, 'off': False, 'yes': True, 'no': False}.get(world.open_pyramid[player], world.open_pyramid[player])
|
||||||
|
|
||||||
for tok in filter(None, args.startinventory[player].split(',')):
|
for tok in filter(None, args.startinventory[player].split(',')):
|
||||||
item = ItemFactory(tok.strip(), player)
|
item = ItemFactory(tok.strip(), player)
|
||||||
if item:
|
if item:
|
||||||
|
@ -179,14 +188,14 @@ def main(args, seed=None):
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
set_rules(world, player)
|
set_rules(world, player)
|
||||||
|
|
||||||
logger.info('Placing Dungeon Prizes.')
|
|
||||||
|
|
||||||
fill_prizes(world)
|
|
||||||
|
|
||||||
logger.info("Running Item Plando")
|
logger.info("Running Item Plando")
|
||||||
|
|
||||||
distribute_planned(world)
|
distribute_planned(world)
|
||||||
|
|
||||||
|
logger.info('Placing Dungeon Prizes.')
|
||||||
|
|
||||||
|
fill_prizes(world)
|
||||||
|
|
||||||
logger.info('Placing Dungeon Items.')
|
logger.info('Placing Dungeon Items.')
|
||||||
|
|
||||||
if args.algorithm in ['balanced', 'vt26'] or any(
|
if args.algorithm in ['balanced', 'vt26'] or any(
|
||||||
|
@ -210,8 +219,14 @@ def main(args, seed=None):
|
||||||
if world.players > 1:
|
if world.players > 1:
|
||||||
balance_multiworld_progression(world)
|
balance_multiworld_progression(world)
|
||||||
|
|
||||||
|
logger.info("Filling Shop Slots")
|
||||||
|
|
||||||
|
ShopSlotFill(world)
|
||||||
|
|
||||||
logger.info('Patching ROM.')
|
logger.info('Patching ROM.')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
outfilebase = 'AP_%s' % (args.outputname if args.outputname else world.seed)
|
outfilebase = 'AP_%s' % (args.outputname if args.outputname else world.seed)
|
||||||
|
|
||||||
rom_names = []
|
rom_names = []
|
||||||
|
@ -227,7 +242,7 @@ def main(args, seed=None):
|
||||||
patch_rom(world, rom, player, team, use_enemizer)
|
patch_rom(world, rom, player, team, use_enemizer)
|
||||||
|
|
||||||
if use_enemizer:
|
if use_enemizer:
|
||||||
patch_enemizer(world, player, rom, args.enemizercli)
|
patch_enemizer(world, team, player, rom, args.enemizercli)
|
||||||
|
|
||||||
if args.race:
|
if args.race:
|
||||||
patch_race_rom(rom, world, player)
|
patch_race_rom(rom, world, player)
|
||||||
|
@ -246,7 +261,6 @@ def main(args, seed=None):
|
||||||
args.fastmenu[player], args.disablemusic[player], args.sprite[player],
|
args.fastmenu[player], args.disablemusic[player], args.sprite[player],
|
||||||
palettes_options, world, player, True)
|
palettes_options, world, player, True)
|
||||||
|
|
||||||
|
|
||||||
mcsb_name = ''
|
mcsb_name = ''
|
||||||
if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player],
|
if all([world.mapshuffle[player], world.compassshuffle[player], world.keyshuffle[player],
|
||||||
world.bigkeyshuffle[player]]):
|
world.bigkeyshuffle[player]]):
|
||||||
|
@ -282,7 +296,7 @@ def main(args, seed=None):
|
||||||
"progressive": world.progressive, # A
|
"progressive": world.progressive, # A
|
||||||
"hints": 'True' if world.hints[player] else 'False' # B
|
"hints": 'True' if world.hints[player] else 'False' # B
|
||||||
}
|
}
|
||||||
# 0 1 2 3 4 5 6 7 8 9 A B
|
# 0 1 2 3 4 5 6 7 8 9 A B
|
||||||
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (
|
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s' % (
|
||||||
# 0 1 2 3 4 5 6 7 8 9 A B C
|
# 0 1 2 3 4 5 6 7 8 9 A B C
|
||||||
# _noglitches_normal-normal-open-ganon-ohko_simple-balanced-keysanity-retro-prog_random-nohints
|
# _noglitches_normal-normal-open-ganon-ohko_simple-balanced-keysanity-retro-prog_random-nohints
|
||||||
|
@ -313,7 +327,7 @@ def main(args, seed=None):
|
||||||
|
|
||||||
pool = concurrent.futures.ThreadPoolExecutor()
|
pool = concurrent.futures.ThreadPoolExecutor()
|
||||||
multidata_task = None
|
multidata_task = None
|
||||||
check_beatability_task = pool.submit(world.can_beat_game)
|
check_accessibility_task = pool.submit(world.fulfills_accessibility)
|
||||||
if not args.suppress_rom:
|
if not args.suppress_rom:
|
||||||
|
|
||||||
rom_futures = []
|
rom_futures = []
|
||||||
|
@ -330,13 +344,14 @@ def main(args, seed=None):
|
||||||
return get_entrance_to_region(entrance.parent_region)
|
return get_entrance_to_region(entrance.parent_region)
|
||||||
|
|
||||||
# collect ER hint info
|
# collect ER hint info
|
||||||
er_hint_data = {player: {} for player in range(1, world.players + 1) if world.shuffle[player] != "vanilla"}
|
er_hint_data = {player: {} for player in range(1, world.players + 1) if world.shuffle[player] != "vanilla" or world.retro[player]}
|
||||||
from worlds.alttp.Regions import RegionType
|
from worlds.alttp.Regions import RegionType
|
||||||
for region in world.regions:
|
for region in world.regions:
|
||||||
if region.player in er_hint_data and region.locations:
|
if region.player in er_hint_data and region.locations:
|
||||||
main_entrance = get_entrance_to_region(region)
|
main_entrance = get_entrance_to_region(region)
|
||||||
for location in region.locations:
|
for location in region.locations:
|
||||||
if type(location.address) == int: # skips events and crystals
|
if type(location.address) == int: # skips events and crystals
|
||||||
|
if location.address >= SHOP_ID_START + 33: continue
|
||||||
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
|
if lookup_vanilla_location_to_entrance[location.address] != main_entrance.name:
|
||||||
er_hint_data[region.player][location.address] = main_entrance.name
|
er_hint_data[region.player][location.address] = main_entrance.name
|
||||||
|
|
||||||
|
@ -363,11 +378,30 @@ def main(args, seed=None):
|
||||||
checks_in_area[location.player]["Dark World"].append(location.address)
|
checks_in_area[location.player]["Dark World"].append(location.address)
|
||||||
checks_in_area[location.player]["Total"] += 1
|
checks_in_area[location.player]["Total"] += 1
|
||||||
|
|
||||||
|
oldmancaves = []
|
||||||
|
takeanyregions = ["Old Man Sword Cave", "Take-Any #1", "Take-Any #2", "Take-Any #3", "Take-Any #4"]
|
||||||
|
for index, take_any in enumerate(takeanyregions):
|
||||||
|
for region in [world.get_region(take_any, player) for player in range(1, world.players + 1) if world.retro[player]]:
|
||||||
|
item = ItemFactory(region.shop.inventory[(0 if take_any == "Old Man Sword Cave" else 1)]['item'], region.player)
|
||||||
|
player = region.player
|
||||||
|
location_id = SHOP_ID_START + total_shop_slots + index
|
||||||
|
|
||||||
|
main_entrance = get_entrance_to_region(region)
|
||||||
|
if main_entrance.parent_region.type == RegionType.LightWorld:
|
||||||
|
checks_in_area[player]["Light World"].append(location_id)
|
||||||
|
else:
|
||||||
|
checks_in_area[player]["Dark World"].append(location_id)
|
||||||
|
checks_in_area[player]["Total"] += 1
|
||||||
|
|
||||||
|
er_hint_data[player][location_id] = main_entrance.name
|
||||||
|
oldmancaves.append(((location_id, player), (item.code, player)))
|
||||||
|
|
||||||
precollected_items = [[] for player in range(world.players)]
|
precollected_items = [[] for player in range(world.players)]
|
||||||
for item in world.precollected_items:
|
for item in world.precollected_items:
|
||||||
precollected_items[item.player - 1].append(item.code)
|
precollected_items[item.player - 1].append(item.code)
|
||||||
|
|
||||||
|
FillDisabledShopSlots(world)
|
||||||
|
|
||||||
def write_multidata(roms):
|
def write_multidata(roms):
|
||||||
import base64
|
import base64
|
||||||
for future in roms:
|
for future in roms:
|
||||||
|
@ -398,8 +432,11 @@ def main(args, seed=None):
|
||||||
f.write(multidata)
|
f.write(multidata)
|
||||||
|
|
||||||
multidata_task = pool.submit(write_multidata, rom_futures)
|
multidata_task = pool.submit(write_multidata, rom_futures)
|
||||||
if not check_beatability_task.result():
|
if not check_accessibility_task.result():
|
||||||
raise Exception("Game appears unbeatable. Aborting.")
|
if not world.can_beat_game():
|
||||||
|
raise Exception("Game appears is unbeatable. Aborting.")
|
||||||
|
else:
|
||||||
|
logger.warning("Location Accessibility requirements not fulfilled.")
|
||||||
if not args.skip_playthrough:
|
if not args.skip_playthrough:
|
||||||
logger.info('Calculating playthrough.')
|
logger.info('Calculating playthrough.')
|
||||||
create_playthrough(world)
|
create_playthrough(world)
|
||||||
|
@ -413,6 +450,7 @@ def main(args, seed=None):
|
||||||
return world
|
return world
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def copy_world(world):
|
def copy_world(world):
|
||||||
# ToDo: Not good yet
|
# ToDo: Not good yet
|
||||||
ret = MultiWorld(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints)
|
ret = MultiWorld(world.players, world.shuffle, world.logic, world.mode, world.swords, world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm, world.accessibility, world.shuffle_ganon, world.retro, world.custom, world.customitemarray, world.hints)
|
||||||
|
@ -453,6 +491,7 @@ def copy_world(world):
|
||||||
ret.shufflepots = world.shufflepots.copy()
|
ret.shufflepots = world.shufflepots.copy()
|
||||||
ret.shuffle_prizes = world.shuffle_prizes.copy()
|
ret.shuffle_prizes = world.shuffle_prizes.copy()
|
||||||
ret.shop_shuffle = world.shop_shuffle.copy()
|
ret.shop_shuffle = world.shop_shuffle.copy()
|
||||||
|
ret.shop_shuffle_slots = world.shop_shuffle_slots.copy()
|
||||||
ret.dark_room_logic = world.dark_room_logic.copy()
|
ret.dark_room_logic = world.dark_room_logic.copy()
|
||||||
ret.restrict_dungeon_item_on_boss = world.restrict_dungeon_item_on_boss.copy()
|
ret.restrict_dungeon_item_on_boss = world.restrict_dungeon_item_on_boss.copy()
|
||||||
|
|
||||||
|
@ -487,7 +526,7 @@ def copy_world(world):
|
||||||
# fill locations
|
# fill locations
|
||||||
for location in world.get_locations():
|
for location in world.get_locations():
|
||||||
if location.item is not None:
|
if location.item is not None:
|
||||||
item = Item(location.item.name, location.item.advancement, location.item.priority, location.item.type, player = location.item.player)
|
item = Item(location.item.name, location.item.advancement, location.item.type, player = location.item.player)
|
||||||
ret.get_location(location.name, location.player).item = item
|
ret.get_location(location.name, location.player).item = item
|
||||||
item.location = ret.get_location(location.name, location.player)
|
item.location = ret.get_location(location.name, location.player)
|
||||||
item.world = ret
|
item.world = ret
|
||||||
|
@ -498,7 +537,7 @@ def copy_world(world):
|
||||||
|
|
||||||
# copy remaining itempool. No item in itempool should have an assigned location
|
# copy remaining itempool. No item in itempool should have an assigned location
|
||||||
for item in world.itempool:
|
for item in world.itempool:
|
||||||
ret.itempool.append(Item(item.name, item.advancement, item.priority, item.type, player = item.player))
|
ret.itempool.append(Item(item.name, item.advancement, item.type, player = item.player))
|
||||||
|
|
||||||
for item in world.precollected_items:
|
for item in world.precollected_items:
|
||||||
ret.push_precollected(ItemFactory(item.name, item.player))
|
ret.push_precollected(ItemFactory(item.name, item.player))
|
||||||
|
@ -525,7 +564,7 @@ def copy_dynamic_regions_and_locations(world, ret):
|
||||||
|
|
||||||
if region.shop:
|
if region.shop:
|
||||||
new_reg.shop = region.shop.__class__(new_reg, region.shop.room_id, region.shop.shopkeeper_config,
|
new_reg.shop = region.shop.__class__(new_reg, region.shop.room_id, region.shop.shopkeeper_config,
|
||||||
region.shop.custom, region.shop.locked)
|
region.shop.custom, region.shop.locked, region.shop.sram_offset)
|
||||||
ret.shops.append(new_reg.shop)
|
ret.shops.append(new_reg.shop)
|
||||||
|
|
||||||
for location in world.dynamic_locations:
|
for location in world.dynamic_locations:
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import collections
|
import collections
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
from BaseClasses import Region, Location, Entrance, RegionType, Shop, TakeAny, UpgradeShop, ShopType
|
from BaseClasses import Region, Location, Entrance, RegionType
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def create_regions(world, player):
|
def create_regions(world, player):
|
||||||
|
@ -365,38 +366,6 @@ def mark_light_world_regions(world, player: int):
|
||||||
queue.append(exit.connected_region)
|
queue.append(exit.connected_region)
|
||||||
|
|
||||||
|
|
||||||
def create_shops(world, player: int):
|
|
||||||
cls_mapping = {ShopType.UpgradeShop: UpgradeShop,
|
|
||||||
ShopType.Shop: Shop,
|
|
||||||
ShopType.TakeAny: TakeAny}
|
|
||||||
for region_name, (room_id, type, shopkeeper, custom, locked, inventory) in shop_table.items():
|
|
||||||
if world.mode[player] == 'inverted' and region_name == 'Dark Lake Hylia Shop':
|
|
||||||
locked = True
|
|
||||||
inventory = [('Blue Potion', 160), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
|
||||||
region = world.get_region(region_name, player)
|
|
||||||
shop = cls_mapping[type](region, room_id, shopkeeper, custom, locked)
|
|
||||||
region.shop = shop
|
|
||||||
world.shops.append(shop)
|
|
||||||
for index, item in enumerate(inventory):
|
|
||||||
shop.add_inventory(index, *item)
|
|
||||||
|
|
||||||
# (type, room_id, shopkeeper, custom, locked, [items])
|
|
||||||
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
|
||||||
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
|
||||||
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
|
||||||
shop_table = {
|
|
||||||
'Cave Shop (Dark Death Mountain)': (0x0112, ShopType.Shop, 0xC1, True, False, _basic_shop_defaults),
|
|
||||||
'Red Shield Shop': (0x0110, ShopType.Shop, 0xC1, True, False, [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)]),
|
|
||||||
'Dark Lake Hylia Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
|
||||||
'Dark World Lumberjack Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
|
||||||
'Village of Outcasts Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
|
||||||
'Dark World Potion Shop': (0x010F, ShopType.Shop, 0xC1, True, False, _dark_world_shop_defaults),
|
|
||||||
'Light World Death Mountain Shop': (0x00FF, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
|
||||||
'Kakariko Shop': (0x011F, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
|
||||||
'Cave Shop (Lake Hylia)': (0x0112, ShopType.Shop, 0xA0, True, False, _basic_shop_defaults),
|
|
||||||
'Potion Shop': (0x0109, ShopType.Shop, 0xFF, False, True, [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)]),
|
|
||||||
'Capacity Upgrade': (0x0115, ShopType.UpgradeShop, 0x04, True, True, [('Bomb Upgrade (+5)', 100, 7), ('Arrow Upgrade (+5)', 100, 7)])
|
|
||||||
}
|
|
||||||
|
|
||||||
old_location_address_to_new_location_address = {
|
old_location_address_to_new_location_address = {
|
||||||
0x2eb18: 0x18001b, # Bottle Merchant
|
0x2eb18: 0x18001b, # Bottle Merchant
|
||||||
|
@ -703,10 +672,13 @@ location_table: typing.Dict[str,
|
||||||
'Turtle Rock - Prize': (
|
'Turtle Rock - Prize': (
|
||||||
[0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
|
[0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
|
||||||
|
|
||||||
|
from Shops import shop_table_by_location_id, shop_table_by_location
|
||||||
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
||||||
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}, -1: "cheat console"}
|
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}, -1: "cheat console"}
|
||||||
|
lookup_id_to_name.update(shop_table_by_location_id)
|
||||||
lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int}
|
lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int}
|
||||||
lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}, "cheat console": -1}
|
lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}, "cheat console": -1}
|
||||||
|
lookup_name_to_id.update(shop_table_by_location)
|
||||||
|
|
||||||
lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 191256: 'Kings Grave Inner Rocks',
|
lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 191256: 'Kings Grave Inner Rocks',
|
||||||
1573194: 'Kings Grave Inner Rocks', 1573189: 'Kings Grave Inner Rocks',
|
1573194: 'Kings Grave Inner Rocks', 1573189: 'Kings Grave Inner Rocks',
|
||||||
|
@ -832,7 +804,18 @@ lookup_vanilla_location_to_entrance = {1572883: 'Kings Grave Inner Rocks', 19125
|
||||||
0x140064: 'Misery Mire',
|
0x140064: 'Misery Mire',
|
||||||
0x140058: 'Turtle Rock', 0x140007: 'Dark Death Mountain Ledge (West)',
|
0x140058: 'Turtle Rock', 0x140007: 'Dark Death Mountain Ledge (West)',
|
||||||
0x140040: 'Ganons Tower', 0x140043: 'Ganons Tower',
|
0x140040: 'Ganons Tower', 0x140043: 'Ganons Tower',
|
||||||
0x14003a: 'Ganons Tower', 0x14001f: 'Ganons Tower'}
|
0x14003a: 'Ganons Tower', 0x14001f: 'Ganons Tower',
|
||||||
|
0x400000: 'Cave Shop (Dark Death Mountain)', 0x400001: 'Cave Shop (Dark Death Mountain)', 0x400002: 'Cave Shop (Dark Death Mountain)',
|
||||||
|
0x400003: 'Red Shield Shop', 0x400004: 'Red Shield Shop', 0x400005: 'Red Shield Shop',
|
||||||
|
0x400006: 'Dark Lake Hylia Shop', 0x400007: 'Dark Lake Hylia Shop', 0x400008: 'Dark Lake Hylia Shop',
|
||||||
|
0x400009: 'Dark World Lumberjack Shop', 0x40000a: 'Dark World Lumberjack Shop', 0x40000b: 'Dark World Lumberjack Shop',
|
||||||
|
0x40000c: 'Village of Outcasts Shop', 0x40000d: 'Village of Outcasts Shop', 0x40000e: 'Village of Outcasts Shop',
|
||||||
|
0x40000f: 'Dark World Potion Shop', 0x400010: 'Dark World Potion Shop', 0x400011: 'Dark World Potion Shop',
|
||||||
|
0x400012: 'Light World Death Mountain Shop', 0x400013: 'Light World Death Mountain Shop', 0x400014: 'Light World Death Mountain Shop',
|
||||||
|
0x400015: 'Kakariko Shop', 0x400016: 'Kakariko Shop', 0x400017: 'Kakariko Shop',
|
||||||
|
0x400018: 'Cave Shop (Lake Hylia)', 0x400019: 'Cave Shop (Lake Hylia)', 0x40001a: 'Cave Shop (Lake Hylia)',
|
||||||
|
0x40001b: 'Potion Shop', 0x40001c: 'Potion Shop', 0x40001d: 'Potion Shop',
|
||||||
|
0x40001e: 'Capacity Upgrade', 0x40001f: 'Capacity Upgrade', 0x400020: 'Capacity Upgrade'}
|
||||||
|
|
||||||
lookup_prizes = {location for location in location_table if location.endswith(" - Prize")}
|
lookup_prizes = {location for location in location_table if location.endswith(" - Prize")}
|
||||||
lookup_boss_drops = {location for location in location_table if location.endswith(" - Boss")}
|
lookup_boss_drops = {location for location in location_table if location.endswith(" - Boss")}
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = '5a607e36a82bbd14180536c8ec3ae49b'
|
RANDOMIZERBASEHASH = '417f926edfb0f83cdaf74019a26c53e8'
|
||||||
|
|
||||||
import io
|
import io
|
||||||
import json
|
import json
|
||||||
|
@ -17,7 +17,8 @@ import xxtea
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from BaseClasses import CollectionState, ShopType, Region, Location
|
from BaseClasses import CollectionState, Region, Location
|
||||||
|
from Shops import ShopType, total_shop_slots
|
||||||
from worlds.alttp.Dungeons import dungeon_music_addresses
|
from worlds.alttp.Dungeons import dungeon_music_addresses
|
||||||
from worlds.alttp.Regions import location_table, old_location_address_to_new_location_address
|
from worlds.alttp.Regions import location_table, old_location_address_to_new_location_address
|
||||||
from worlds.alttp.Text import MultiByteTextMapper, text_addresses, Credits, TextTable
|
from worlds.alttp.Text import MultiByteTextMapper, text_addresses, Credits, TextTable
|
||||||
|
@ -38,6 +39,7 @@ try:
|
||||||
except:
|
except:
|
||||||
z3pr = None
|
z3pr = None
|
||||||
|
|
||||||
|
enemizer_logger = logging.getLogger("Enemizer")
|
||||||
|
|
||||||
class LocalRom(object):
|
class LocalRom(object):
|
||||||
|
|
||||||
|
@ -123,6 +125,9 @@ class LocalRom(object):
|
||||||
Patch.create_patch_file(local_path('basepatch.sfc'))
|
Patch.create_patch_file(local_path('basepatch.sfc'))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if not os.path.isfile(local_path('data', 'basepatch.bmbp')):
|
||||||
|
raise RuntimeError('Base patch unverified. Unable to continue.')
|
||||||
|
|
||||||
if os.path.isfile(local_path('data', 'basepatch.apbp')):
|
if os.path.isfile(local_path('data', 'basepatch.apbp')):
|
||||||
_, target, buffer = Patch.create_rom_bytes(local_path('data', 'basepatch.apbp'), ignore_version=True)
|
_, target, buffer = Patch.create_rom_bytes(local_path('data', 'basepatch.apbp'), ignore_version=True)
|
||||||
if self.verify(buffer):
|
if self.verify(buffer):
|
||||||
|
@ -130,6 +135,7 @@ class LocalRom(object):
|
||||||
with open(local_path('basepatch.sfc'), 'wb') as stream:
|
with open(local_path('basepatch.sfc'), 'wb') as stream:
|
||||||
stream.write(buffer)
|
stream.write(buffer)
|
||||||
return
|
return
|
||||||
|
raise RuntimeError('Base patch unverified. Unable to continue.')
|
||||||
|
|
||||||
raise RuntimeError('Could not find Base Patch. Unable to continue.')
|
raise RuntimeError('Could not find Base Patch. Unable to continue.')
|
||||||
|
|
||||||
|
@ -190,7 +196,7 @@ def check_enemizer(enemizercli):
|
||||||
if lib.startswith("EnemizerLibrary/"):
|
if lib.startswith("EnemizerLibrary/"):
|
||||||
version = lib.split("/")[-1]
|
version = lib.split("/")[-1]
|
||||||
version = tuple(int(element) for element in version.split("."))
|
version = tuple(int(element) for element in version.split("."))
|
||||||
logging.debug(f"Found Enemizer version {version}")
|
enemizer_logger.debug(f"Found Enemizer version {version}")
|
||||||
if version < (6, 4, 0):
|
if version < (6, 4, 0):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"Enemizer found at {enemizercli} is outdated ({info}), please update your Enemizer. "
|
f"Enemizer found at {enemizercli} is outdated ({info}), please update your Enemizer. "
|
||||||
|
@ -266,11 +272,11 @@ def apply_random_sprite_on_event(rom: LocalRom, sprite, local_random, allow_rand
|
||||||
rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette)
|
rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette)
|
||||||
|
|
||||||
|
|
||||||
def patch_enemizer(world, player: int, rom: LocalRom, enemizercli):
|
def patch_enemizer(world, team: int, player: int, rom: LocalRom, enemizercli):
|
||||||
check_enemizer(enemizercli)
|
check_enemizer(enemizercli)
|
||||||
randopatch_path = os.path.abspath(output_path(f'enemizer_randopatch_{player}.sfc'))
|
randopatch_path = os.path.abspath(output_path(f'enemizer_randopatch_{team}_{player}.sfc'))
|
||||||
options_path = os.path.abspath(output_path(f'enemizer_options_{player}.json'))
|
options_path = os.path.abspath(output_path(f'enemizer_options_{team}_{player}.json'))
|
||||||
enemizer_output_path = os.path.abspath(output_path(f'enemizer_output_{player}.sfc'))
|
enemizer_output_path = os.path.abspath(output_path(f'enemizer_output_{team}_{player}.sfc'))
|
||||||
|
|
||||||
# write options file for enemizer
|
# write options file for enemizer
|
||||||
options = {
|
options = {
|
||||||
|
@ -385,10 +391,13 @@ def patch_enemizer(world, player: int, rom: LocalRom, enemizercli):
|
||||||
stderr=subprocess.STDOUT,
|
stderr=subprocess.STDOUT,
|
||||||
universal_newlines=True)
|
universal_newlines=True)
|
||||||
|
|
||||||
logging.debug(
|
enemizer_logger.debug(
|
||||||
f"Enemizer attempt {i + 1} of {max_enemizer_tries} for player {player} using enemizer seed {enemizer_seed}")
|
f"Enemizer attempt {i + 1} of {max_enemizer_tries} for player {player} using enemizer seed {enemizer_seed}")
|
||||||
for stdout_line in iter(p_open.stdout.readline, ""):
|
for stdout_line in iter(p_open.stdout.readline, ""):
|
||||||
logging.debug(stdout_line.rstrip())
|
if i == max_enemizer_tries - 1:
|
||||||
|
enemizer_logger.warning(stdout_line.rstrip())
|
||||||
|
else:
|
||||||
|
enemizer_logger.debug(stdout_line.rstrip())
|
||||||
p_open.stdout.close()
|
p_open.stdout.close()
|
||||||
|
|
||||||
return_code = p_open.wait()
|
return_code = p_open.wait()
|
||||||
|
@ -677,15 +686,13 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
distinguished_prog_bow_loc.item.code = 0x65
|
distinguished_prog_bow_loc.item.code = 0x65
|
||||||
|
|
||||||
# patch items
|
# patch items
|
||||||
|
|
||||||
for location in world.get_locations():
|
for location in world.get_locations():
|
||||||
if location.player != player:
|
if location.player != player or location.address is None or location.shop_slot:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
itemid = location.item.code if location.item is not None else 0x5A
|
itemid = location.item.code if location.item is not None else 0x5A
|
||||||
|
|
||||||
if location.address is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not location.crystal:
|
if not location.crystal:
|
||||||
if location.item is not None:
|
if location.item is not None:
|
||||||
# Keys in their native dungeon should use the orignal item code for keys
|
# Keys in their native dungeon should use the orignal item code for keys
|
||||||
|
@ -724,6 +731,7 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
for music_address in music_addresses:
|
for music_address in music_addresses:
|
||||||
rom.write_byte(music_address, music)
|
rom.write_byte(music_address, music)
|
||||||
|
|
||||||
|
|
||||||
if world.mapshuffle[player]:
|
if world.mapshuffle[player]:
|
||||||
rom.write_byte(0x155C9, local_random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
rom.write_byte(0x155C9, local_random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
||||||
|
|
||||||
|
@ -783,6 +791,29 @@ def patch_rom(world, rom, player, team, enemized):
|
||||||
|
|
||||||
write_custom_shops(rom, world, player)
|
write_custom_shops(rom, world, player)
|
||||||
|
|
||||||
|
def credits_digit(num):
|
||||||
|
# top: $54 is 1, 55 2, etc , so 57=4, 5C=9
|
||||||
|
# bot: $7A is 1, 7B is 2, etc so 7D=4, 82=9 (zero unknown...)
|
||||||
|
return 0x53 + int(num), 0x79 + int(num)
|
||||||
|
|
||||||
|
credits_total = 216
|
||||||
|
if world.goal[player] == 'icerodhunt': # Impossible to get 216/216 with Ice rod hunt. Most possible is 215/216.
|
||||||
|
credits_total -= 1
|
||||||
|
if world.retro[player]: # Old man cave and Take any caves will count towards collection rate.
|
||||||
|
credits_total += 5
|
||||||
|
if world.shop_shuffle_slots[player]: # Potion shop only counts towards collection rate if included in the shuffle.
|
||||||
|
credits_total += 30 if 'w' in world.shop_shuffle[player] else 27
|
||||||
|
|
||||||
|
rom.write_byte(0x187010, credits_total) # dynamic credits
|
||||||
|
# collection rate address: 238C37
|
||||||
|
first_top, first_bot = credits_digit((credits_total / 100) % 10)
|
||||||
|
mid_top, mid_bot = credits_digit((credits_total / 10) % 10)
|
||||||
|
last_top, last_bot = credits_digit(credits_total % 10)
|
||||||
|
# top half
|
||||||
|
rom.write_bytes(0x118C46, [first_top, mid_top, last_top])
|
||||||
|
# bottom half
|
||||||
|
rom.write_bytes(0x118C64, [first_bot, mid_bot, last_bot])
|
||||||
|
|
||||||
# patch medallion requirements
|
# patch medallion requirements
|
||||||
if world.required_medallions[player][0] == 'Bombos':
|
if world.required_medallions[player][0] == 'Bombos':
|
||||||
rom.write_byte(0x180022, 0x00) # requirement
|
rom.write_byte(0x180022, 0x00) # requirement
|
||||||
|
@ -1548,31 +1579,44 @@ def patch_race_rom(rom, world, player):
|
||||||
|
|
||||||
|
|
||||||
def write_custom_shops(rom, world, player):
|
def write_custom_shops(rom, world, player):
|
||||||
shops = [shop for shop in world.shops if shop.custom and shop.region.player == player]
|
shops = sorted([shop for shop in world.shops if shop.custom and shop.region.player == player],
|
||||||
|
key=lambda shop: shop.sram_offset)
|
||||||
|
|
||||||
shop_data = bytearray()
|
shop_data = bytearray()
|
||||||
items_data = bytearray()
|
items_data = bytearray()
|
||||||
sram_offset = 0
|
retro_shop_slots = bytearray()
|
||||||
|
|
||||||
for shop_id, shop in enumerate(shops):
|
for shop_id, shop in enumerate(shops):
|
||||||
if shop_id == len(shops) - 1:
|
if shop_id == len(shops) - 1:
|
||||||
shop_id = 0xFF
|
shop_id = 0xFF
|
||||||
bytes = shop.get_bytes()
|
bytes = shop.get_bytes()
|
||||||
bytes[0] = shop_id
|
bytes[0] = shop_id
|
||||||
bytes[-1] = sram_offset
|
bytes[-1] = shop.sram_offset
|
||||||
if shop.type == ShopType.TakeAny:
|
|
||||||
sram_offset += 1
|
|
||||||
else:
|
|
||||||
sram_offset += shop.item_count
|
|
||||||
shop_data.extend(bytes)
|
shop_data.extend(bytes)
|
||||||
# [id][item][price-low][price-high][max][repl_id][repl_price-low][repl_price-high]
|
|
||||||
for item in shop.inventory:
|
arrow_mask = 0x00
|
||||||
|
for index, item in enumerate(shop.inventory):
|
||||||
|
slot = 0 if shop.type == ShopType.TakeAny else index
|
||||||
if item is None:
|
if item is None:
|
||||||
break
|
break
|
||||||
item_data = [shop_id, ItemFactory(item['item'], player).code] + int16_as_bytes(item['price']) + [
|
if world.shop_shuffle_slots[player] or shop.type == ShopType.TakeAny:
|
||||||
item['max'],
|
count_shop = (shop.region.name != 'Potion Shop' or 'w' in world.shop_shuffle[player]) and \
|
||||||
ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + int16_as_bytes(
|
shop.region.name != 'Capacity Upgrade'
|
||||||
item['replacement_price'])
|
rom.write_byte(0x186560 + shop.sram_offset + slot, 1 if count_shop else 0)
|
||||||
|
if item['item'] == 'Single Arrow' and item['player'] == 0:
|
||||||
|
arrow_mask |= 1 << index
|
||||||
|
retro_shop_slots.append(shop.sram_offset + slot)
|
||||||
|
|
||||||
|
# [id][item][price-low][price-high][max][repl_id][repl_price-low][repl_price-high][player]
|
||||||
|
for index, item in enumerate(shop.inventory):
|
||||||
|
slot = 0 if shop.type == ShopType.TakeAny else index
|
||||||
|
if item is None:
|
||||||
|
break
|
||||||
|
if item['item'] == 'Single Arrow' and item['player'] == 0 and world.retro[player]:
|
||||||
|
rom.write_byte(0x186500 + shop.sram_offset + slot, arrow_mask)
|
||||||
|
item_data = [shop_id, ItemFactory(item['item'], player).code] + int16_as_bytes(item['price']) + \
|
||||||
|
[item['max'], ItemFactory(item['replacement'], player).code if item['replacement'] else 0xFF] + \
|
||||||
|
int16_as_bytes(item['replacement_price']) + [0 if item['player'] == player else item['player']]
|
||||||
items_data.extend(item_data)
|
items_data.extend(item_data)
|
||||||
|
|
||||||
rom.write_bytes(0x184800, shop_data)
|
rom.write_bytes(0x184800, shop_data)
|
||||||
|
@ -1580,6 +1624,10 @@ def write_custom_shops(rom, world, player):
|
||||||
items_data.extend([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
|
items_data.extend([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
|
||||||
rom.write_bytes(0x184900, items_data)
|
rom.write_bytes(0x184900, items_data)
|
||||||
|
|
||||||
|
if world.retro[player]:
|
||||||
|
retro_shop_slots.append(0xFF)
|
||||||
|
rom.write_bytes(0x186540, retro_shop_slots)
|
||||||
|
|
||||||
|
|
||||||
def hud_format_text(text):
|
def hud_format_text(text):
|
||||||
output = bytes()
|
output = bytes()
|
||||||
|
@ -2621,7 +2669,7 @@ HintLocations = ['telepathic_tile_eastern_palace',
|
||||||
'telepathic_tile_castle_tower',
|
'telepathic_tile_castle_tower',
|
||||||
'telepathic_tile_ice_large_room',
|
'telepathic_tile_ice_large_room',
|
||||||
'telepathic_tile_turtle_rock',
|
'telepathic_tile_turtle_rock',
|
||||||
'telepathic_tile_ice_entrace',
|
'telepathic_tile_ice_entrance',
|
||||||
'telepathic_tile_ice_stalfos_knights_room',
|
'telepathic_tile_ice_stalfos_knights_room',
|
||||||
'telepathic_tile_tower_of_hera_entrance',
|
'telepathic_tile_tower_of_hera_entrance',
|
||||||
'telepathic_tile_south_east_darkworld_cave',
|
'telepathic_tile_south_east_darkworld_cave',
|
||||||
|
|
|
@ -86,7 +86,7 @@ def set_rules(world, player):
|
||||||
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.world.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or')
|
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.world.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or')
|
||||||
|
|
||||||
set_bunny_rules(world, player, world.mode[player] == 'inverted')
|
set_bunny_rules(world, player, world.mode[player] == 'inverted')
|
||||||
|
|
||||||
|
|
||||||
def mirrorless_path_to_castle_courtyard(world, player):
|
def mirrorless_path_to_castle_courtyard(world, player):
|
||||||
# If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch.
|
# If Agahnim is defeated then the courtyard needs to be accessible without using the mirror for the mirror offset glitch.
|
||||||
|
@ -869,7 +869,7 @@ def standard_rules(world, player):
|
||||||
def toss_junk_item(world, player):
|
def toss_junk_item(world, player):
|
||||||
items = ['Rupees (20)', 'Bombs (3)', 'Arrows (10)', 'Rupees (5)', 'Rupee (1)', 'Bombs (10)',
|
items = ['Rupees (20)', 'Bombs (3)', 'Arrows (10)', 'Rupees (5)', 'Rupee (1)', 'Bombs (10)',
|
||||||
'Single Arrow', 'Rupees (50)', 'Rupees (100)', 'Single Bomb', 'Bee', 'Bee Trap',
|
'Single Arrow', 'Rupees (50)', 'Rupees (100)', 'Single Bomb', 'Bee', 'Bee Trap',
|
||||||
'Rupees (300)']
|
'Rupees (300)', 'Nothing']
|
||||||
for item in items:
|
for item in items:
|
||||||
big20 = next((i for i in world.itempool if i.name == item and i.player == player), None)
|
big20 = next((i for i in world.itempool if i.name == item and i.player == player), None)
|
||||||
if big20:
|
if big20:
|
||||||
|
@ -948,7 +948,7 @@ def set_trock_key_rules(world, player):
|
||||||
if not can_reach_big_chest:
|
if not can_reach_big_chest:
|
||||||
# Must not go in the Chain Chomps chest - only 2 other chests available and 3+ keys required for all other chests
|
# Must not go in the Chain Chomps chest - only 2 other chests available and 3+ keys required for all other chests
|
||||||
forbid_item(world.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player)
|
forbid_item(world.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player)
|
||||||
if world.accessibility[player] == 'locations':
|
if world.accessibility[player] == 'locations' and world.goal[player] != 'icerodhunt':
|
||||||
if world.bigkeyshuffle[player] and can_reach_big_chest:
|
if world.bigkeyshuffle[player] and can_reach_big_chest:
|
||||||
# Must not go in the dungeon - all 3 available chests (Chomps, Big Chest, Crystaroller) must be keys to access laser bridge, and the big key is required first
|
# Must not go in the dungeon - all 3 available chests (Chomps, Big Chest, Crystaroller) must be keys to access laser bridge, and the big key is required first
|
||||||
for location in ['Turtle Rock - Chain Chomps', 'Turtle Rock - Compass Chest',
|
for location in ['Turtle Rock - Chain Chomps', 'Turtle Rock - Compass Chest',
|
||||||
|
|
|
@ -1709,7 +1709,7 @@ class TextTable(object):
|
||||||
text['telepathic_tile_castle_tower'] = CompressedTextMapper.convert("{NOBORDER}\nYou can reflect Agahnim's energy with Sword, Bug-net or Hammer.")
|
text['telepathic_tile_castle_tower'] = CompressedTextMapper.convert("{NOBORDER}\nYou can reflect Agahnim's energy with Sword, Bug-net or Hammer.")
|
||||||
text['telepathic_tile_ice_large_room'] = CompressedTextMapper.convert("{NOBORDER}\nAll right stop collaborate and listen\nIce is back with my brand new invention")
|
text['telepathic_tile_ice_large_room'] = CompressedTextMapper.convert("{NOBORDER}\nAll right stop collaborate and listen\nIce is back with my brand new invention")
|
||||||
text['telepathic_tile_turtle_rock'] = CompressedTextMapper.convert("{NOBORDER}\nYou shall not pass… without the red cane")
|
text['telepathic_tile_turtle_rock'] = CompressedTextMapper.convert("{NOBORDER}\nYou shall not pass… without the red cane")
|
||||||
text['telepathic_tile_ice_entrace'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.")
|
text['telepathic_tile_ice_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.")
|
||||||
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
|
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
|
||||||
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert(
|
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert(
|
||||||
"{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
|
"{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
|
||||||
|
|
Loading…
Reference in New Issue