0.6.1 Release

0.6.1 Release
This commit is contained in:
AmazingAmpharos 2018-03-30 23:22:45 -05:00 committed by GitHub
commit 33e076209e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
60 changed files with 1384 additions and 480 deletions

View File

@ -3,11 +3,11 @@ from enum import Enum, unique
import logging import logging
import json import json
from collections import OrderedDict from collections import OrderedDict
from Utils import int16_as_bytes
class World(object): class World(object):
def __init__(self, shuffle, logic, mode, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, check_beatable_only, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, custom, customitemarray): def __init__(self, shuffle, logic, mode, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, check_beatable_only, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray):
self.shuffle = shuffle self.shuffle = shuffle
self.logic = logic self.logic = logic
self.mode = mode self.mode = mode
@ -18,6 +18,7 @@ class World(object):
self.algorithm = algorithm self.algorithm = algorithm
self.dungeons = [] self.dungeons = []
self.regions = [] self.regions = []
self.shops = []
self.itempool = [] self.itempool = []
self.seed = None self.seed = None
self.state = CollectionState(self) self.state = CollectionState(self)
@ -44,7 +45,7 @@ class World(object):
self.aga_randomness = True self.aga_randomness = True
self.lock_aga_door_in_escape = False self.lock_aga_door_in_escape = False
self.fix_trock_doors = self.shuffle != 'vanilla' self.fix_trock_doors = self.shuffle != 'vanilla'
self.save_and_quite_from_boss = False self.save_and_quit_from_boss = False
self.check_beatable_only = check_beatable_only self.check_beatable_only = check_beatable_only
self.fix_skullwoods_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'] self.fix_skullwoods_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']
self.fix_palaceofdarkness_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'] self.fix_palaceofdarkness_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']
@ -56,11 +57,14 @@ class World(object):
self.fastmenu = fastmenu self.fastmenu = fastmenu
self.disable_music = disable_music self.disable_music = disable_music
self.keysanity = keysanity self.keysanity = keysanity
self.retro = retro
self.custom = custom self.custom = custom
self.customitemarray = customitemarray self.customitemarray = customitemarray
self.can_take_damage = True self.can_take_damage = True
self.difficulty_requirements = None self.difficulty_requirements = None
self.fix_fake_world = True self.fix_fake_world = True
self.dynamic_regions = []
self.dynamic_locations = []
self.spoiler = Spoiler(self) self.spoiler = Spoiler(self)
self.lamps_needed_for_dark_rooms = 1 self.lamps_needed_for_dark_rooms = 1
@ -183,6 +187,9 @@ class World(object):
self._cached_locations.extend(region.locations) self._cached_locations.extend(region.locations)
return self._cached_locations return self._cached_locations
def clear_location_cache(self):
self._cached_locations = None
def get_unfilled_locations(self): def get_unfilled_locations(self):
return [location for location in self.get_locations() if location.item is None] return [location for location in self.get_locations() if location.item is None]
@ -278,6 +285,7 @@ class World(object):
markbool(self.check_beatable_only) markbool(self.check_beatable_only)
markbool(self.shuffle_ganon) markbool(self.shuffle_ganon)
markbool(self.keysanity) markbool(self.keysanity)
markbool(self.retro)
assert id_value_max <= 0xFFFFFFFF assert id_value_max <= 0xFFFFFFFF
return id_value return id_value
@ -375,6 +383,19 @@ class CollectionState(object):
return item in self.prog_items return item in self.prog_items
return self.item_count(item) >= count return self.item_count(item) >= count
def has_key(self, item, count=1):
if self.world.retro:
return self.can_buy_unlimited('Small Key (Universal)')
if count == 1:
return item in self.prog_items
return self.item_count(item) >= count
def can_buy_unlimited(self, item):
for shop in self.world.shops:
if shop.has_unlimited(item) and shop.region.can_reach(self):
return True
return False
def item_count(self, item): def item_count(self, item):
return len([pritem for pritem in self.prog_items if pritem == item]) return len([pritem for pritem in self.prog_items if pritem == item])
@ -415,18 +436,25 @@ class CollectionState(object):
basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count()) basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count())
elif self.world.difficulty == 'insane' and not fullrefill: elif self.world.difficulty == 'insane' and not fullrefill:
basemagic = basemagic basemagic = basemagic
else: elif self.can_buy_unlimited('Green Potion') or self.can_buy_unlimited('Red Potion'):
basemagic = basemagic + basemagic * self.bottle_count() basemagic = basemagic + basemagic * self.bottle_count()
return basemagic >= smallmagic # FIXME bottle should really also have a requirement that we can reach some shop that sells green or blue potions return basemagic >= smallmagic
def can_kill_most_things(self, enemies=5): def can_kill_most_things(self, enemies=5):
return (self.has_blunt_weapon() return (self.has_blunt_weapon()
or self.has('Cane of Somaria') or self.has('Cane of Somaria')
or (self.has('Cane of Byrna') and (enemies < 6 or self.can_extend_Magic())) or (self.has('Cane of Byrna') and (enemies < 6 or self.can_extend_Magic()))
or self.has('Bow') or self.can_shoot_arrows()
or self.has('Fire Rod') or self.has('Fire Rod')
) )
def can_shoot_arrows(self):
if self.world.retro:
#TODO: need to decide how we want to handle wooden arrows longer-term (a can-buy-a check, or via dynamic shop location)
#FIXME: Should do something about hard+ ganon only silvers. For the moment, i believe they effective grant wooden, so we are safe
return self.has('Bow') and (self.has('Silver Arrows') or self.can_buy_unlimited('Single Arrow'))
return self.has('Bow')
def has_sword(self): def has_sword(self):
return self.has('Fighter Sword') or self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword') return self.has('Fighter Sword') or self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword')
@ -565,7 +593,6 @@ class RegionType(Enum):
return self in (RegionType.Cave, RegionType.Dungeon) return self in (RegionType.Cave, RegionType.Dungeon)
class Region(object): class Region(object):
def __init__(self, name, type): def __init__(self, name, type):
@ -575,6 +602,7 @@ class Region(object):
self.exits = [] self.exits = []
self.locations = [] self.locations = []
self.dungeon = None self.dungeon = None
self.shop = None
self.world = None self.world = None
self.is_light_world = False # will be set aftermaking connections. self.is_light_world = False # will be set aftermaking connections.
self.is_dark_world = False self.is_dark_world = False
@ -742,24 +770,121 @@ class Item(object):
class Crystal(Item): class Crystal(Item):
pass pass
@unique
class ShopType(Enum):
Shop = 0
TakeAny = 1
class Shop(object):
def __init__(self, region, room_id, type, shopkeeper_config, replaceable):
self.region = region
self.room_id = room_id
self.type = type
self.inventory = [None, None, None]
self.shopkeeper_config = shopkeeper_config
self.replaceable = replaceable
self.active = False
@property
def item_count(self):
return (3 if self.inventory[2] else
2 if self.inventory[1] else
1 if self.inventory[0] else
0)
def get_bytes(self):
# [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].addresses:
door_id = entrances[0].addresses+1
else:
door_id = 0
config |= 0x40 # ignore door id
if self.type == ShopType.TakeAny:
config |= 0x80
return [0x00]+int16_as_bytes(self.room_id)+[door_id, 0x00, config, self.shopkeeper_config, 0x00]
def has_unlimited(self, item):
for inv in self.inventory:
if inv is None:
continue
if inv['max'] != 0 and inv['replacement'] is not None and inv['replacement'] == item:
return True
elif inv['item'] is not None and inv['item'] == item:
return True
return False
def clear_inventory(self):
self.inventory = [None, None, None]
def add_inventory(self, slot, item, price, max=0, replacement=None, replacement_price=0, create_location=False):
self.inventory[slot] = {
'item': item,
'price': price,
'max': max,
'replacement': replacement,
'replacement_price': replacement_price,
'create_location': create_location
}
class Spoiler(object): class Spoiler(object):
def __init__(self, world): def __init__(self, world):
self.world = world self.world = world
self.entrances = [] self.entrances = OrderedDict()
self.medallions = {} self.medallions = {}
self.playthrough = {} self.playthrough = {}
self.locations = {} self.locations = {}
self.paths = {} self.paths = {}
self.metadata = {} self.metadata = {}
self.shops = []
def set_entrance(self, entrance, exit, direction): def set_entrance(self, entrance, exit, direction):
self.entrances.append(OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)])) self.entrances[(entrance, direction)] = OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)])
def parse_data(self): def parse_data(self):
self.medallions = OrderedDict([('Misery Mire', self.world.required_medallions[0]), ('Turtle Rock', self.world.required_medallions[1])]) self.medallions = OrderedDict([('Misery Mire', self.world.required_medallions[0]), ('Turtle Rock', self.world.required_medallions[1])])
self.locations = {'other locations': OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in self.world.get_locations()])}
self.locations = OrderedDict()
listed_locations = set()
lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld]
self.locations['Light World'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations])
listed_locations.update(lw_locations)
dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld]
self.locations['Dark World'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations])
listed_locations.update(dw_locations)
cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave]
self.locations['Caves'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations])
listed_locations.update(cave_locations)
for dungeon in self.world.dungeons:
dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon]
self.locations[dungeon.name] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations])
listed_locations.update(dungeon_locations)
other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations]
if other_locations:
self.locations['Other Locations'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in other_locations])
listed_locations.update(other_locations)
for shop in self.world.shops:
if not shop.active:
continue
shopdata = {'location': shop.region.name,
'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop'
}
for index, item in enumerate(shop.inventory):
if item is None:
continue
shopdata['item_{}'.format(index)] = "{}{}".format(item['item'], item['price']) if item['price'] else item['item']
self.shops.append(shopdata)
from Main import __version__ as ERVersion from Main import __version__ as ERVersion
self.metadata = {'version': ERVersion, self.metadata = {'version': ERVersion,
'seed': self.world.seed, 'seed': self.world.seed,
@ -781,9 +906,11 @@ class Spoiler(object):
def to_json(self): def to_json(self):
self.parse_data() self.parse_data()
out = OrderedDict() out = OrderedDict()
out['entrances'] = self.entrances out['Entrances'] = list(self.entrances.values())
out.update(self.locations) out.update(self.locations)
out['medallions'] = self.medallions out['Special'] = self.medallions
if self.shops:
out['Shops'] = self.shops
out['playthrough'] = self.playthrough out['playthrough'] = self.playthrough
out['paths'] = self.paths out['paths'] = self.paths
out['meta'] = self.metadata out['meta'] = self.metadata
@ -805,12 +932,14 @@ class Spoiler(object):
outfile.write('Keysanity enabled: %s' % ('Yes' if self.metadata['keysanity'] else 'No')) outfile.write('Keysanity enabled: %s' % ('Yes' if self.metadata['keysanity'] else 'No'))
if self.entrances: if self.entrances:
outfile.write('\n\nEntrances:\n\n') outfile.write('\n\nEntrances:\n\n')
outfile.write('\n'.join(['%s %s %s' % (entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.entrances])) outfile.write('\n'.join(['%s %s %s' % (entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.entrances.values()]))
outfile.write('\n\nMedallions') outfile.write('\n\nMedallions')
outfile.write('\n\nMisery Mire Medallion: %s' % self.medallions['Misery Mire']) outfile.write('\n\nMisery Mire Medallion: %s' % self.medallions['Misery Mire'])
outfile.write('\nTurtle Rock Medallion: %s' % self.medallions['Turtle Rock']) outfile.write('\nTurtle Rock Medallion: %s' % self.medallions['Turtle Rock'])
outfile.write('\n\nLocations:\n\n') outfile.write('\n\nLocations:\n\n')
outfile.write('\n'.join(['%s: %s' % (location, item) for (location, item) in self.locations['other locations'].items()])) outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()]))
outfile.write('\n\nShops:\n\n')
outfile.write('\n'.join("{} [{}]\n {}".format(shop['location'], shop['type'], "\n ".join(item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
outfile.write('\n\nPlaythrough:\n\n') outfile.write('\n\nPlaythrough:\n\n')
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()])) outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()]))
outfile.write('\n\nPaths:\n\n') outfile.write('\n\nPaths:\n\n')

View File

@ -7,7 +7,7 @@ from Items import ItemFactory
def create_dungeons(world): def create_dungeons(world):
def make_dungeon(name, dungeon_regions, big_key, small_keys, dungeon_items): def make_dungeon(name, dungeon_regions, big_key, small_keys, dungeon_items):
dungeon = Dungeon(name, dungeon_regions, big_key, small_keys, dungeon_items) dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro else small_keys, dungeon_items)
for region in dungeon.regions: for region in dungeon.regions:
world.get_region(region).dungeon = dungeon world.get_region(region).dungeon = dungeon
return dungeon return dungeon
@ -26,7 +26,7 @@ def create_dungeons(world):
TR = make_dungeon('Turtle Rock', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], ItemFactory('Big Key (Turtle Rock)'), ItemFactory(['Small Key (Turtle Rock)'] * 4), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'])) TR = make_dungeon('Turtle Rock', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], ItemFactory('Big Key (Turtle Rock)'), ItemFactory(['Small Key (Turtle Rock)'] * 4), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)']))
GT = make_dungeon('Ganons Tower', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)'), ItemFactory(['Small Key (Ganons Tower)'] * 4), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'])) GT = make_dungeon('Ganons Tower', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)'), ItemFactory(['Small Key (Ganons Tower)'] * 4), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)']))
world.dungeons = [TR, ES, EP, DP, ToH, AT, PoD, TT, SW, IP, MM, GT, SP] world.dungeons = [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]
def fill_dungeons(world): def fill_dungeons(world):
@ -34,7 +34,10 @@ def fill_dungeons(world):
all_state_base = world.get_all_state() all_state_base = world.get_all_state()
world.push_item(world.get_location('Skull Woods - Pinball Room'), ItemFactory('Small Key (Skull Woods)'), False) if world.retro:
world.push_item(world.get_location('Skull Woods - Pinball Room'), ItemFactory('Small Key (Universal)'), False)
else:
world.push_item(world.get_location('Skull Woods - Pinball Room'), ItemFactory('Small Key (Skull Woods)'), False)
world.get_location('Skull Woods - Pinball Room').event = True world.get_location('Skull Woods - Pinball Room').event = True
dungeons = [(list(dungeon.regions), dungeon.big_key, list(dungeon.small_keys), list(dungeon.dungeon_items)) for dungeon in world.dungeons] dungeons = [(list(dungeon.regions), dungeon.big_key, list(dungeon.small_keys), list(dungeon.dungeon_items)) for dungeon in world.dungeons]
@ -110,7 +113,10 @@ def fill_dungeons_restrictive(world, shuffled_locations):
all_state_base = world.get_all_state() all_state_base = world.get_all_state()
skull_woods_big_chest = world.get_location('Skull Woods - Pinball Room') skull_woods_big_chest = world.get_location('Skull Woods - Pinball Room')
world.push_item(skull_woods_big_chest, ItemFactory('Small Key (Skull Woods)'), False) if world.retro:
world.push_item(skull_woods_big_chest, ItemFactory('Small Key (Universal)'), False)
else:
world.push_item(skull_woods_big_chest, ItemFactory('Small Key (Skull Woods)'), False)
skull_woods_big_chest.event = True skull_woods_big_chest.event = True
shuffled_locations.remove(skull_woods_big_chest) shuffled_locations.remove(skull_woods_big_chest)

View File

@ -20,12 +20,14 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
def start(): def start():
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter) parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true') parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches'], parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'],
help='''\ help='''\
Select Enforcement of Item Requirements. (default: %(default)s) Select Enforcement of Item Requirements. (default: %(default)s)
No Glitches: No Glitches:
Minor Glitches: May require Fake Flippers, Bunny Revival Minor Glitches: May require Fake Flippers, Bunny Revival
and Dark Room Navigation. and Dark Room Navigation.
No Logic: Distribute items without regard for
item requirements.
''') ''')
parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'swordless'], parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'swordless'],
help='''\ help='''\
@ -165,6 +167,10 @@ def start():
Keys (and other dungeon items) are no longer restricted to Keys (and other dungeon items) are no longer restricted to
their dungeons, but can be anywhere their dungeons, but can be anywhere
''', action='store_true') ''', action='store_true')
parser.add_argument('--retro', help='''\
Keys are universal, shooting arrows costs rupees,
and a few other little things make this more like Zelda-1.
''', action='store_true')
parser.add_argument('--custom', default=False, help='Not supported.') parser.add_argument('--custom', default=False, help='Not supported.')
parser.add_argument('--customitemarray', default=False, help='Not supported.') parser.add_argument('--customitemarray', default=False, help='Not supported.')
parser.add_argument('--nodungeonitems', help='''\ parser.add_argument('--nodungeonitems', help='''\

View File

@ -1026,7 +1026,7 @@ def link_entrances(world):
if world.get_entrance('Dam').connected_region.name != 'Dam' or world.get_entrance('Swamp Palace').connected_region.name != 'Swamp Palace (Entrance)': if world.get_entrance('Dam').connected_region.name != 'Dam' or world.get_entrance('Swamp Palace').connected_region.name != 'Swamp Palace (Entrance)':
world.swamp_patch_required = True world.swamp_patch_required = True
# check for # check for potion shop location
if world.get_entrance('Potion Shop').connected_region.name != 'Potion Shop': if world.get_entrance('Potion Shop').connected_region.name != 'Potion Shop':
world.powder_patch_required = True world.powder_patch_required = True
@ -1744,6 +1744,7 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central
('Paradox Cave Push Block', 'Paradox Cave Front'), ('Paradox Cave Push Block', 'Paradox Cave Front'),
('Paradox Cave Bomb Jump', 'Paradox Cave'), ('Paradox Cave Bomb Jump', 'Paradox Cave'),
('Paradox Cave Drop', 'Paradox Cave Chest Area'), ('Paradox Cave Drop', 'Paradox Cave Chest Area'),
('Light World Death Mountain Shop', 'Light World Death Mountain Shop'),
('Fairy Ascension Rocks', 'Fairy Ascension Plateau'), ('Fairy Ascension Rocks', 'Fairy Ascension Plateau'),
('Fairy Ascension Mirror Spot', 'Fairy Ascension Plateau'), ('Fairy Ascension Mirror Spot', 'Fairy Ascension Plateau'),
('Fairy Ascension Drop', 'East Death Mountain (Bottom)'), ('Fairy Ascension Drop', 'East Death Mountain (Bottom)'),

17
Gui.py
View File

@ -62,6 +62,8 @@ def guiMain(args=None):
quickSwapCheckbutton = Checkbutton(checkBoxFrame, text="Enabled L/R Item quickswapping", variable=quickSwapVar) quickSwapCheckbutton = Checkbutton(checkBoxFrame, text="Enabled L/R Item quickswapping", variable=quickSwapVar)
keysanityVar = IntVar() keysanityVar = IntVar()
keysanityCheckbutton = Checkbutton(checkBoxFrame, text="Keysanity (keys anywhere)", variable=keysanityVar) keysanityCheckbutton = Checkbutton(checkBoxFrame, text="Keysanity (keys anywhere)", variable=keysanityVar)
retroVar = IntVar()
retroCheckbutton = Checkbutton(checkBoxFrame, text="Retro mode (universal keys)", variable=retroVar)
dungeonItemsVar = IntVar() dungeonItemsVar = IntVar()
dungeonItemsCheckbutton = Checkbutton(checkBoxFrame, text="Place Dungeon Items (Compasses/Maps)", onvalue=0, offvalue=1, variable=dungeonItemsVar) dungeonItemsCheckbutton = Checkbutton(checkBoxFrame, text="Place Dungeon Items (Compasses/Maps)", onvalue=0, offvalue=1, variable=dungeonItemsVar)
beatableOnlyVar = IntVar() beatableOnlyVar = IntVar()
@ -78,6 +80,7 @@ def guiMain(args=None):
suppressRomCheckbutton.pack(expand=True, anchor=W) suppressRomCheckbutton.pack(expand=True, anchor=W)
quickSwapCheckbutton.pack(expand=True, anchor=W) quickSwapCheckbutton.pack(expand=True, anchor=W)
keysanityCheckbutton.pack(expand=True, anchor=W) keysanityCheckbutton.pack(expand=True, anchor=W)
retroCheckbutton.pack(expand=True, anchor=W)
dungeonItemsCheckbutton.pack(expand=True, anchor=W) dungeonItemsCheckbutton.pack(expand=True, anchor=W)
beatableOnlyCheckbutton.pack(expand=True, anchor=W) beatableOnlyCheckbutton.pack(expand=True, anchor=W)
disableMusicCheckbutton.pack(expand=True, anchor=W) disableMusicCheckbutton.pack(expand=True, anchor=W)
@ -146,7 +149,7 @@ def guiMain(args=None):
logicFrame = Frame(drowDownFrame) logicFrame = Frame(drowDownFrame)
logicVar = StringVar() logicVar = StringVar()
logicVar.set('noglitches') logicVar.set('noglitches')
logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches') logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches', 'nologic')
logicOptionMenu.pack(side=RIGHT) logicOptionMenu.pack(side=RIGHT)
logicLabel = Label(logicFrame, text='Game logic') logicLabel = Label(logicFrame, text='Game logic')
logicLabel.pack(side=LEFT) logicLabel.pack(side=LEFT)
@ -262,6 +265,7 @@ def guiMain(args=None):
guiargs.create_spoiler = bool(createSpoilerVar.get()) guiargs.create_spoiler = bool(createSpoilerVar.get())
guiargs.suppress_rom = bool(suppressRomVar.get()) guiargs.suppress_rom = bool(suppressRomVar.get())
guiargs.keysanity = bool(keysanityVar.get()) guiargs.keysanity = bool(keysanityVar.get())
guiargs.retro = bool(retroVar.get())
guiargs.nodungeonitems = bool(dungeonItemsVar.get()) guiargs.nodungeonitems = bool(dungeonItemsVar.get())
guiargs.beatableonly = bool(beatableOnlyVar.get()) guiargs.beatableonly = bool(beatableOnlyVar.get())
guiargs.quickswap = bool(quickSwapVar.get()) guiargs.quickswap = bool(quickSwapVar.get())
@ -276,7 +280,7 @@ def guiMain(args=None):
int(redmailVar.get()), int(progmailVar.get()), int(halfmagicVar.get()), int(quartermagicVar.get()), int(bcap5Var.get()), int(bcap10Var.get()), int(acap5Var.get()), int(acap10Var.get()), int(redmailVar.get()), int(progmailVar.get()), int(halfmagicVar.get()), int(quartermagicVar.get()), int(bcap5Var.get()), int(bcap10Var.get()), int(acap5Var.get()), int(acap10Var.get()),
int(arrow1Var.get()), int(arrow10Var.get()), int(bomb1Var.get()), int(bomb3Var.get()), int(rupee1Var.get()), int(rupee5Var.get()), int(rupee20Var.get()), int(rupee50Var.get()), int(rupee100Var.get()), int(arrow1Var.get()), int(arrow10Var.get()), int(bomb1Var.get()), int(bomb3Var.get()), int(rupee1Var.get()), int(rupee5Var.get()), int(rupee20Var.get()), int(rupee50Var.get()), int(rupee100Var.get()),
int(rupee300Var.get()), int(rupoorVar.get()), int(blueclockVar.get()), int(greenclockVar.get()), int(redclockVar.get()), int(triforcepieceVar.get()), int(triforcecountVar.get()), int(rupee300Var.get()), int(rupoorVar.get()), int(blueclockVar.get()), int(greenclockVar.get()), int(redclockVar.get()), int(triforcepieceVar.get()), int(triforcecountVar.get()),
int(triforceVar.get()), int(rupoorcostVar.get())] int(triforceVar.get()), int(rupoorcostVar.get()), int(universalkeyVar.get())]
guiargs.rom = romVar.get() guiargs.rom = romVar.get()
guiargs.jsonout = None guiargs.jsonout = None
guiargs.sprite = sprite guiargs.sprite = sprite
@ -934,6 +938,14 @@ def guiMain(args=None):
redclockLabel.pack(anchor=W, side=LEFT, padx=(0,14)) redclockLabel.pack(anchor=W, side=LEFT, padx=(0,14))
redclockEntry.pack(anchor=E) redclockEntry.pack(anchor=E)
universalkeyFrame = Frame(itemList5)
universalkeyLabel = Label(universalkeyFrame, text='Universal Key')
universalkeyVar = StringVar(value='0')
universalkeyEntry = Entry(universalkeyFrame, textvariable=universalkeyVar, width=3, validate='all', vcmd=vcmd)
universalkeyFrame.pack()
universalkeyLabel.pack(anchor=W, side=LEFT, padx=(0,57))
universalkeyEntry.pack(anchor=E)
triforcepieceFrame = Frame(itemList5) triforcepieceFrame = Frame(itemList5)
triforcepieceLabel = Label(triforcepieceFrame, text='Triforce Piece') triforcepieceLabel = Label(triforcepieceFrame, text='Triforce Piece')
triforcepieceVar = StringVar(value='0') triforcepieceVar = StringVar(value='0')
@ -978,6 +990,7 @@ def guiMain(args=None):
createSpoilerVar.set(int(args.create_spoiler)) createSpoilerVar.set(int(args.create_spoiler))
suppressRomVar.set(int(args.suppress_rom)) suppressRomVar.set(int(args.suppress_rom))
keysanityVar.set(args.keysanity) keysanityVar.set(args.keysanity)
retroVar.set(args.retro)
if args.nodungeonitems: if args.nodungeonitems:
dungeonItemsVar.set(int(not args.nodungeonitems)) dungeonItemsVar.set(int(not args.nodungeonitems))
beatableOnlyVar.set(int(args.beatableonly)) beatableOnlyVar.set(int(args.beatableonly))

View File

@ -2,69 +2,70 @@ from collections import namedtuple
import logging import logging
import random import random
from Items import ItemFactory from BaseClasses import Region, RegionType, Shop, ShopType, Location
from Fill import FillError, fill_restrictive
from Dungeons import get_dungeon_item_pool from Dungeons import get_dungeon_item_pool
from EntranceShuffle import connect_entrance
from Fill import FillError, fill_restrictive
from Items import ItemFactory
#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.
#Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided. #Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
alwaysitems = ['Bombos', 'Book of Mudora', 'Bow', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp', alwaysitems = ['Bombos', 'Book of Mudora', 'Bow', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp',
'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Bug Catching Net', 'Cane of Byrna'] 'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Bug Catching Net', 'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang']
progressivegloves = ['Progressive Glove'] * 2 progressivegloves = ['Progressive Glove'] * 2
basicgloves = ['Power Glove', 'Titans Mitts'] basicgloves = ['Power Glove', 'Titans Mitts']
normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)'] normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)']
hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)'] hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)']
normalbaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows', 'Magic Upgrade (1/2)'] + ['Rupees (300)'] * 4 + normalbaseitems = (['Silver Arrows', 'Magic Upgrade (1/2)', 'Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] +
['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24) ['Rupees (300)'] * 4 + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
normalfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 normalfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6
normalsecond15extra = ['Bombs (3)'] * 10 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)'] normalsecond15extra = ['Bombs (3)'] * 9 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)'] + ['Bombs (10)']
normalthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Arrows (10)', 'Rupee (1)', 'Rupees (5)'] normalthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Arrows (10)', 'Rupee (1)', 'Rupees (5)']
normalfourth5extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2 + ['Rupees (5)'] normalfourth5extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2 + ['Rupees (5)']
normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2 normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
easybaseitems = (['Blue Boomerang', 'Red Boomerang', 'Sanctuary Heart Container'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 + ['Lamp'] * 2 + easybaseitems = (['Sanctuary Heart Container'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 + ['Lamp'] * 2 + ['Silver Arrows'] * 2 +
['Silver Arrows'] * 2 + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12) ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12)
easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)'] easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)']
easylimitedextra = ['Boss Heart Container'] * 3 # collapsing down the 12 pieces of heart easylimitedextra = ['Boss Heart Container'] * 3 # collapsing down the 12 pieces of heart
easyfirst15extra = ['Rupees (100)', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6 easyfirst15extra = ['Rupees (100)', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6
easysecond10extra = ['Bombs (3)'] * 8 + ['Rupee (1)', 'Rupees (50)'] easysecond10extra = ['Bombs (3)'] * 7 + ['Rupee (1)', 'Rupees (50)', 'Bombs (10)']
easythird5extra = ['Rupees (50)'] * 2 + ['Bombs (3)'] * 2 + ['Arrows (10)'] easythird5extra = ['Rupees (50)'] * 2 + ['Bombs (3)'] * 2 + ['Arrows (10)']
easyfinal25extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 14 + ['Rupee (1)'] + ['Arrows (10)'] * 4 + ['Rupees (5)'] * 2 easyfinal25extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 14 + ['Rupee (1)'] + ['Arrows (10)'] * 4 + ['Rupees (5)'] * 2
easytimedotherextra = ['Red Clock'] * 5 easytimedotherextra = ['Red Clock'] * 5
hardbaseitems = (['Silver Arrows', 'Single Arrow', 'Single Bomb'] + ['Rupees (300)'] + ['Rupees (100)'] * 3 + ['Rupees (50)'] * 5 + ['Bombs (3)'] * 5 + hardbaseitems = ['Silver Arrows', 'Single Arrow', 'Bombs (10)'] + ['Rupees (300)'] * 4 + ['Boss Heart Container'] * 6 + ['Piece of Heart'] * 20 + ['Rupees (5)'] * 7 + ['Bombs (3)'] * 4
['Boss Heart Container'] * 5 + ['Piece of Heart'] * 24) hardfirst20extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Bombs (3)'] * 5 + ['Rupees (5)'] * 10 + ['Arrows (10)', 'Rupee (1)']
hardfirst20extra = ['Single Bomb'] * 7 + ['Rupees (5)'] * 8 + ['Rupee (1)'] * 2 + ['Rupees (20)'] * 2 + ['Arrows (10)'] hardsecond10extra = ['Rupees (5)'] * 5 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)']
hardsecond10extra = ['Rupees (5)'] * 7 + ['Rupee (1)'] * 3 hardthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Rupees (5)'] * 3
hardthird10extra = ['Arrows (10)'] * 4 + ['Rupees (20)'] * 3 + ['Single Bomb'] * 3 hardfourth10extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 7 + ['Rupees (5)']
hardfourth10extra = ['Rupees (5)'] * 3 + ['Single Arrow'] * 5 + ['Single Bomb'] * 2 hardfinal20extra = ['Rupees (20)'] * 18 + ['Rupees (5)'] * 2
hardfinal20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 2 + ['Single Arrow'] * 14
expertbaseitems = (['Single Arrow', 'Rupees (300)', 'Rupees (100)', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 5 + expertbaseitems = (['Rupees (300)'] * 4 + ['Single Arrow', 'Silver Arrows', 'Boss Heart Container', 'Rupee (1)', 'Bombs (10)'] + ['Piece of Heart'] * 20 + ['Rupees (5)'] * 2 +
['Rupees (20)'] * 3 + ['Single Bomb'] * 10 + ['Piece of Heart'] * 24) ['Bombs (3)'] * 9 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2)
expertfirst15extra = ['Single Bomb'] * 7 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 5 expertfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Rupees (5)'] * 12
expertsecond15extra = ['Single Bomb'] * 6 + ['Single Arrow'] * 4 + ['Rupee (1)'] * 5 expertsecond15extra = ['Rupees (5)'] * 10 + ['Rupees (20)'] * 5
expertthird10extra = ['Rupees (5)'] * 3 + ['Single Bomb'] * 3 + ['Rupees (20)'] * 2 + ['Single Arrow'] * 2 expertthird10extra = ['Rupees (50)'] * 4 + ['Rupees (5)'] * 2 + ['Arrows (10)'] * 3 + ['Rupee (1)']
expertfourth5extra = ['Rupees (5)'] * 2 + ['Single Arrow'] * 3 expertfourth5extra = ['Rupees (5)'] * 5
expertfinal25extra = ['Single Bomb'] * 4 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 18 expertfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
insanebaseitems = (['Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 10 + ['Rupees (300)'] * 5 + ['Rupees (100)'] * 4 + insanebaseitems = ['Rupees (300)'] * 4 + ['Single Arrow', 'Bombs (10)', 'Rupee (1)'] + ['Rupees (5)'] * 24 + ['Bombs (3)'] * 9 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupees (20)'] * 5
['Rupee (1)'] * 8 + ['Rupees (20)'] * 4 + ['Single Bomb'] * 8 + ['Single Arrow'] * 6) insanefirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Rupees (5)'] * 12
insanefirst15extra = ['Single Bomb'] * 5 + ['Single Arrow'] * 4 + ['Rupee (1)'] * 5 + ['Rupees (20)'] insanesecond15extra = ['Rupees (5)'] * 10 + ['Rupees (20)'] * 5
insanesecond15extra = ['Single Bomb'] * 5 + ['Single Arrow'] * 5 + ['Rupee (1)'] * 5 insanethird10extra = ['Rupees (50)'] * 4 + ['Rupees (5)'] * 2 + ['Arrows (10)'] * 3 + ['Rupee (1)']
insanethird10extra = ['Single Bomb'] * 4 + ['Single Arrow'] * 3 + ['Rupee (1)'] * 3 insanefourth5extra = ['Rupees (5)'] * 5
insanefourth5extra = ['Single Bomb'] + ['Single Arrow'] * 2 + ['Rupee (1)'] * 2 insanefinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
insanefinal25extra = ['Single Bomb'] * 2 + ['Single Arrow'] * 10 + ['Rupee (1)'] * 7 + ['Rupees (20)'] * 6
Difficulty = namedtuple('Difficulty', Difficulty = namedtuple('Difficulty',
['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield', ['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield',
'basicshield', 'progressivearmor', 'basicarmor', 'swordless', 'basicshield', 'progressivearmor', 'basicarmor', 'swordless',
'progressivesword', 'basicsword', 'timedohko', 'timedother', 'progressivesword', 'basicsword', 'timedohko', 'timedother',
'triforcehunt', 'triforce_pieces_required', 'conditional_extras', 'triforcehunt', 'triforce_pieces_required', 'retro', 'conditional_extras',
'extras', 'progressive_sword_limit', 'progressive_shield_limit', 'extras', 'progressive_sword_limit', 'progressive_shield_limit',
'progressive_armor_limit', 'progressive_bottle_limit']) 'progressive_armor_limit', 'progressive_bottle_limit'])
@ -78,7 +79,7 @@ def easy_conditional_extras(timer, _goal, _mode, pool, placed_items):
return easytimedotherextra return easytimedotherextra
return [] return []
def no_conditonal_extras(*_args): def no_conditional_extras(*_args):
return [] return []
@ -99,7 +100,8 @@ difficulties = {
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30, triforcehunt = ['Triforce Piece'] * 30,
triforce_pieces_required = 20, triforce_pieces_required = 20,
conditional_extras = no_conditonal_extras, retro = ['Small Key (Universal)'] * 17 + ['Rupees (20)'] * 10,
conditional_extras = no_conditional_extras,
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra], extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
progressive_sword_limit = 4, progressive_sword_limit = 4,
progressive_shield_limit = 3, progressive_shield_limit = 3,
@ -122,6 +124,7 @@ difficulties = {
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5, # +5 more Red Clocks if there is room timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5, # +5 more Red Clocks if there is room
triforcehunt = ['Triforce Piece'] * 30, triforcehunt = ['Triforce Piece'] * 30,
triforce_pieces_required = 20, triforce_pieces_required = 20,
retro = ['Small Key (Universal)'] * 27,
conditional_extras = easy_conditional_extras, conditional_extras = easy_conditional_extras,
extras = [easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra], extras = [easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra],
progressive_sword_limit = 4, progressive_sword_limit = 4,
@ -145,41 +148,43 @@ difficulties = {
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30, triforcehunt = ['Triforce Piece'] * 30,
triforce_pieces_required = 20, triforce_pieces_required = 20,
conditional_extras = no_conditonal_extras, retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
conditional_extras = no_conditional_extras,
extras = [hardfirst20extra, hardsecond10extra, hardthird10extra, hardfourth10extra, hardfinal20extra], extras = [hardfirst20extra, hardsecond10extra, hardthird10extra, hardfourth10extra, hardfinal20extra],
progressive_sword_limit = 3, progressive_sword_limit = 3,
progressive_shield_limit = 2, progressive_shield_limit = 2,
progressive_armor_limit = 1, progressive_armor_limit = 1,
progressive_bottle_limit = 2, progressive_bottle_limit = 4,
), ),
'expert': Difficulty( 'expert': Difficulty(
baseitems = expertbaseitems, baseitems = expertbaseitems,
bottles = hardbottles, bottles = hardbottles,
bottle_count = 4, bottle_count = 4,
same_bottle = True, same_bottle = False,
progressiveshield = [], progressiveshield = ['Progressive Shield'] * 3,
basicshield = [], basicshield = ['Progressive Shield'] * 3, #only the first one will upgrade, making this equivalent to two blue shields
progressivearmor = [], progressivearmor = [],
basicarmor = [], basicarmor = [],
swordless = ['Rupees (20)'] * 3 + ['Silver Arrows'], swordless = ['Rupees (20)'] * 4,
progressivesword = ['Progressive Sword'] * 3, progressivesword = ['Progressive Sword'] * 3,
basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'], basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'],
timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5, timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5,
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30, triforcehunt = ['Triforce Piece'] * 30,
triforce_pieces_required = 20, triforce_pieces_required = 20,
conditional_extras = no_conditonal_extras, retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
conditional_extras = no_conditional_extras,
extras = [expertfirst15extra, expertsecond15extra, expertthird10extra, expertfourth5extra, expertfinal25extra], extras = [expertfirst15extra, expertsecond15extra, expertthird10extra, expertfourth5extra, expertfinal25extra],
progressive_sword_limit = 2, progressive_sword_limit = 2,
progressive_shield_limit = 0, progressive_shield_limit = 1,
progressive_armor_limit = 0, progressive_armor_limit = 0,
progressive_bottle_limit = 1, progressive_bottle_limit = 4,
), ),
'insane': Difficulty( 'insane': Difficulty(
baseitems = insanebaseitems, baseitems = insanebaseitems,
bottles = hardbottles, bottles = hardbottles,
bottle_count = 4, bottle_count = 4,
same_bottle = True, same_bottle = False,
progressiveshield = [], progressiveshield = [],
basicshield = [], basicshield = [],
progressivearmor = [], progressivearmor = [],
@ -191,12 +196,13 @@ difficulties = {
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10, timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30, triforcehunt = ['Triforce Piece'] * 30,
triforce_pieces_required = 20, triforce_pieces_required = 20,
conditional_extras = no_conditonal_extras, retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
conditional_extras = no_conditional_extras,
extras = [insanefirst15extra, insanesecond15extra, insanethird10extra, insanefourth5extra, insanefinal25extra], extras = [insanefirst15extra, insanesecond15extra, insanethird10extra, insanefourth5extra, insanefinal25extra],
progressive_sword_limit = 2, progressive_sword_limit = 2,
progressive_shield_limit = 0, progressive_shield_limit = 0,
progressive_armor_limit = 0, progressive_armor_limit = 0,
progressive_bottle_limit = 1, progressive_bottle_limit = 4,
), ),
} }
@ -214,13 +220,21 @@ def generate_itempool(world):
world.get_location('Agahnim 1').event = True world.get_location('Agahnim 1').event = True
world.push_item('Agahnim 2', ItemFactory('Beat Agahnim 2'), False) world.push_item('Agahnim 2', ItemFactory('Beat Agahnim 2'), False)
world.get_location('Agahnim 2').event = True world.get_location('Agahnim 2').event = True
world.push_item('Dark Blacksmith Ruins', ItemFactory('Pick Up Purple Chest'), False)
world.get_location('Dark Blacksmith Ruins').event = True
world.push_item('Frog', ItemFactory('Get Frog'), False)
world.get_location('Frog').event = True
world.push_item('Missing Smith', ItemFactory('Return Smith'), False)
world.get_location('Missing Smith').event = True
world.push_item('Floodgate', ItemFactory('Open Floodgate'), False)
world.get_location('Floodgate').event = True
# set up item pool # set up item pool
if world.custom: if world.custom:
(pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.customitemarray) (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.retro, world.customitemarray)
world.rupoor_cost = min(world.customitemarray[67], 9999) world.rupoor_cost = min(world.customitemarray[67], 9999)
else: else:
(pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode) (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.retro)
world.itempool = ItemFactory(pool) world.itempool = ItemFactory(pool)
for (location, item) in placed_items: for (location, item) in placed_items:
world.push_item(location, ItemFactory(item), False) world.push_item(location, ItemFactory(item), False)
@ -251,9 +265,84 @@ def generate_itempool(world):
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)] tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
world.required_medallions = (mm_medallion, tr_medallion) world.required_medallions = (mm_medallion, tr_medallion)
set_up_shops(world)
if world.retro:
set_up_take_anys(world)
create_dynamic_shop_locations(world)
# distribute crystals # distribute crystals
fill_prizes(world) fill_prizes(world)
take_any_locations = [
'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)',
'Bonk Fairy (Dark)', 'Lake Hylia Healer Fairy', 'Swamp Healer Fairy', 'Desert Healer Fairy',
'Dark Lake Hylia Healer Fairy', 'Dark Lake Hylia Ledge Healer Fairy', 'Dark Desert Healer Fairy',
'Dark Death Mountain Healer Fairy', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave',
'Kakariko Gamble Game', 'Capacity Upgrade', '50 Rupee Cave', 'Lost Woods Gamble', 'Hookshot Fairy',
'Palace of Darkness Hint', 'East Dark World Hint', 'Archery Game', 'Dark Lake Hylia Ledge Hint',
'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint']
def set_up_take_anys(world):
regions = random.sample(take_any_locations, 5)
old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave)
world.regions.append(old_man_take_any)
world.dynamic_regions.append(old_man_take_any)
reg = regions.pop()
entrance = world.get_region(reg).entrances[0]
connect_entrance(world, entrance, old_man_take_any)
entrance.target = 0x58
old_man_take_any.shop = Shop(old_man_take_any, 0x0112, ShopType.TakeAny, 0xE2, True)
world.shops.append(old_man_take_any.shop)
old_man_take_any.shop.active = True
swords = [item for item in world.itempool if item.type == 'Sword']
if swords:
sword = random.choice(swords)
world.itempool.remove(sword)
world.itempool.append(ItemFactory('Rupees (20)'))
old_man_take_any.shop.add_inventory(0, sword.name, 0, 0, create_location=True)
else:
old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0)
for num in range(4):
take_any = Region("Take-Any #{}".format(num+1), RegionType.Cave)
world.regions.append(take_any)
world.dynamic_regions.append(take_any)
target, room_id = random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)])
reg = regions.pop()
entrance = world.get_region(reg).entrances[0]
connect_entrance(world, entrance, take_any)
entrance.target = target
take_any.shop = Shop(take_any, room_id, ShopType.TakeAny, 0xE3, True)
world.shops.append(take_any.shop)
take_any.shop.active = True
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)
world.intialize_regions()
def create_dynamic_shop_locations(world):
for shop in world.shops:
for i, item in enumerate(shop.inventory):
if item is None:
continue
if item['create_location']:
loc = Location("{} Item {}".format(shop.region.name, i+1), parent=shop.region)
shop.region.locations.append(loc)
world.dynamic_locations.append(loc)
world.clear_location_cache()
world.push_item(loc, ItemFactory(item['item']), False)
loc.event = True
def fill_prizes(world, attempts=15): def fill_prizes(world, attempts=15):
crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6']) crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'])
crystal_locations = [world.get_location('Turtle Rock - Prize'), world.get_location('Eastern Palace - Prize'), world.get_location('Desert Palace - Prize'), world.get_location('Tower of Hera - Prize'), world.get_location('Palace of Darkness - Prize'), crystal_locations = [world.get_location('Turtle Rock - Prize'), world.get_location('Eastern Palace - Prize'), world.get_location('Desert Palace - Prize'), world.get_location('Tower of Hera - Prize'), world.get_location('Palace of Darkness - Prize'),
@ -281,9 +370,26 @@ 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):
# Changes to basic Shops
# TODO: move hard+ mode changes for sheilds here, utilizing the new shops
if world.retro:
rss = world.get_region('Red Shield Shop').shop
rss.active = True
rss.add_inventory(2, 'Single Arrow', 80)
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode): # Randomized changes to Shops
if world.retro:
for shop in random.sample([s for s in world.shops if s.replaceable], 5):
shop.active = True
shop.add_inventory(0, 'Single Arrow', 80)
shop.add_inventory(1, 'Small Key (Universal)', 100)
shop.add_inventory(2, 'Bombs (10)', 50)
#special shop types
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro):
pool = [] pool = []
placed_items = [] placed_items = []
clock_mode = None clock_mode = None
@ -382,9 +488,20 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode):
if goal == 'pedestal': if goal == 'pedestal':
placed_items.append(('Master Sword Pedestal', 'Triforce')) placed_items.append(('Master Sword Pedestal', 'Triforce'))
if retro:
pool = [item.replace('Single Arrow','Rupees (5)') for item in pool]
pool = [item.replace('Arrows (10)','Rupees (5)') for item in pool]
pool = [item.replace('Arrow Upgrade (+5)','Rupees (5)') for item in pool]
pool = [item.replace('Arrow Upgrade (+10)','Rupees (5)') for item in pool]
pool.extend(diff.retro)
if mode == 'standard':
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
placed_items.append((key_location, 'Small Key (Universal)'))
else:
pool.extend(['Small Key (Universal)'])
return (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) return (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, customitemarray): def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, retro, customitemarray):
pool = [] pool = []
placed_items = [] placed_items = []
clock_mode = None clock_mode = None
@ -401,6 +518,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, c
for x in range(0, 65): for x in range(0, 65):
itemtotal = itemtotal + customitemarray[x] itemtotal = itemtotal + customitemarray[x]
itemtotal = itemtotal + customitemarray[66] itemtotal = itemtotal + customitemarray[66]
itemtotal = itemtotal + customitemarray[68]
pool.extend(['Bow'] * customitemarray[0]) pool.extend(['Bow'] * customitemarray[0])
pool.extend(['Silver Arrows']* customitemarray[1]) pool.extend(['Silver Arrows']* customitemarray[1])
@ -508,9 +626,16 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, c
placed_items.append(('Link\'s Uncle', 'Progressive Sword')) placed_items.append(('Link\'s Uncle', 'Progressive Sword'))
pool.extend(['Fighter Sword'] * customitemarray[32]) pool.extend(['Fighter Sword'] * customitemarray[32])
pool.extend(['Progressive Sword'] * max((customitemarray[36] - 1), 0)) pool.extend(['Progressive Sword'] * max((customitemarray[36] - 1), 0))
if retro:
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
placed_items.append((key_location, 'Small Key (Universal)'))
pool.extend(['Small Key (Universal)'] * max((customitemarray[68] - 1), 0))
else:
pool.extend(['Small Key (Universal)'] * customitemarray[68])
else: else:
pool.extend(['Fighter Sword'] * customitemarray[32]) pool.extend(['Fighter Sword'] * customitemarray[32])
pool.extend(['Progressive Sword'] * customitemarray[36]) pool.extend(['Progressive Sword'] * customitemarray[36])
pool.extend(['Small Key (Universal)'] * customitemarray[68])
if shuffle == 'insanity_legacy': if shuffle == 'insanity_legacy':
placed_items.append(('Link\'s House', 'Magic Mirror')) placed_items.append(('Link\'s House', 'Magic Mirror'))
@ -521,6 +646,8 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, c
pool.extend(['Magic Mirror'] * customitemarray[22]) pool.extend(['Magic Mirror'] * customitemarray[22])
pool.extend(['Moon Pearl'] * customitemarray[28]) pool.extend(['Moon Pearl'] * customitemarray[28])
if retro:
itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in Retro Mode
if itemtotal < total_items_to_place: if itemtotal < total_items_to_place:
pool.extend(['Nothing'] * (total_items_to_place - itemtotal)) pool.extend(['Nothing'] * (total_items_to_place - itemtotal))
@ -534,15 +661,18 @@ def test():
for mode in ['open', 'standard', 'swordless']: for mode in ['open', 'standard', 'swordless']:
for progressive in ['on', 'off']: for progressive in ['on', 'off']:
for shuffle in ['full', 'insane']: for shuffle in ['full', 'insane']:
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode) for retro in [True, False]:
count = len(out[0]) + len(out[1]) out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro)
count = len(out[0]) + len(out[1])
correct_count = total_items_to_place correct_count = total_items_to_place
if goal in ['pedestal']: if goal in ['pedestal']:
# pedestal goals generate one extra item # pedestal goals generate one extra item
correct_count += 1 correct_count += 1
if retro:
correct_count += 28
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode)) assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, retro))
if __name__ == '__main__': if __name__ == '__main__':
test() test()

View File

@ -52,11 +52,11 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
'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'), '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'),
'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'), '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'),
'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'), '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'),
'Master Sword': (True, False, None, 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'), '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'),
'Tempered Sword': (True, False, None, 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'), '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'),
'Fighter Sword': (True, False, None, 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again'), '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'),
'Golden Sword': (True, False, None, 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again'), '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'),
'Progressive Sword': (True, False, None, 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'), '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'),
'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'), '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'),
'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'), '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'),
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], None, None, None, None, None, None), 'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], None, None, None, None, None, None),
@ -78,6 +78,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
'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 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'),
'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'), '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'),
'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'), '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'),
'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'),
'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 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 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 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'),
'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'), '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'),
@ -157,6 +158,17 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
'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'), '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'),
'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'), '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'),
'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'), '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'),
'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'),
'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again'), 'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again'),
'Red Potion': (False, False, None, 0x2E, None, None, None, None, None, None),
'Green Potion': (False, False, None, 0x2F, None, None, None, None, None, None),
'Blue Potion': (False, False, None, 0x30, None, None, None, None, None, None),
'Bee': (False, False, None, 0x0E, None, None, None, None, None, None),
'Small Heart': (False, False, None, 0x42, None, None, None, None, None, None),
'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None), 'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None),
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None)} 'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None),
'Get Frog': (True, False, 'Event', None, None, None, None, None, None, None),
'Return Smith': (True, False, 'Event', None, None, None, None, None, None, None),
'Pick Up Purple Chest': (True, False, 'Event', None, None, None, None, None, None, None),
'Open Floodgate': (True, False, 'Event', None, None, None, None, None, None, None),
}

70
Main.py
View File

@ -1,11 +1,12 @@
from collections import OrderedDict from collections import OrderedDict
import copy
from itertools import zip_longest from itertools import zip_longest
import json import json
import logging import logging
import random import random
import time import time
from BaseClasses import World, CollectionState, Item from BaseClasses import World, CollectionState, Item, Region, Location, Entrance, Shop
from Regions import create_regions, mark_light_world_regions from Regions import create_regions, mark_light_world_regions
from EntranceShuffle import link_entrances from EntranceShuffle import link_entrances
from Rom import patch_rom, Sprite, LocalRom, JsonRom from Rom import patch_rom, Sprite, LocalRom, JsonRom
@ -15,31 +16,31 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
from ItemList import generate_itempool, difficulties from ItemList import generate_itempool, difficulties
from Utils import output_path from Utils import output_path
__version__ = '0.6.0' __version__ = '0.6.1'
logic_hash = [26, 76, 4, 144, 72, 105, 234, 233, 12, 184, 95, 94, 100, 13, 15, 174, logic_hash = [215, 244, 99, 97, 253, 98, 31, 150, 207, 70, 50, 78, 59, 73, 221, 191,
186, 135, 130, 189, 246, 254, 123, 245, 85, 241, 101, 129, 70, 255, 55, 248, 21, 34, 200, 116, 77, 234, 89, 27, 228, 96, 16, 249, 56, 148, 3, 176,
43, 146, 23, 179, 243, 208, 230, 176, 9, 88, 239, 226, 222, 203, 244, 183, 17, 227, 24, 20, 238, 67, 37, 219, 62, 223, 60, 123, 246, 92, 164, 177,
205, 74, 44, 5, 122, 220, 206, 47, 221, 125, 138, 155, 98, 79, 238, 119, 211, 15, 245, 23, 75, 33, 190, 124, 144, 100, 87, 57, 86, 108, 80, 181,
30, 24, 159, 39, 253, 27, 33, 218, 62, 82, 200, 28, 141, 191, 93, 22, 6, 28, 2, 71, 182, 155, 222, 229, 90, 91, 32, 126, 25, 226, 133, 41,
192, 54, 227, 108, 48, 78, 242, 166, 60, 250, 75, 145, 49, 212, 41, 25, 132, 122, 10, 30, 53, 239, 112, 49, 104, 76, 209, 247, 139, 13, 173, 113,
127, 89, 178, 157, 19, 158, 177, 231, 207, 66, 172, 17, 133, 61, 109, 86, 159, 69, 145, 161, 11, 102, 149, 143, 129, 178, 45, 217, 196, 232, 208, 119,
57, 143, 142, 219, 148, 209, 181, 87, 163, 40, 81, 114, 240, 103, 31, 175, 94, 19, 35, 65, 170, 103, 55, 109, 5, 43, 118, 194, 180, 12, 206, 241,
237, 185, 18, 173, 168, 45, 216, 106, 161, 16, 151, 139, 104, 134, 110, 21, 8, 105, 210, 231, 179, 83, 137, 18, 212, 236, 225, 66, 63, 142, 138, 131,
32, 131, 118, 182, 215, 67, 3, 73, 171, 71, 150, 147, 223, 247, 42, 132, 192, 160, 1, 198, 153, 128, 106, 165, 39, 248, 167, 22, 74, 163, 140, 157,
107, 149, 232, 153, 10, 201, 156, 225, 116, 194, 187, 204, 46, 165, 124, 92, 214, 84, 154, 127, 195, 172, 136, 168, 68, 134, 152, 95, 111, 235, 26, 42,
7, 0, 251, 126, 162, 80, 90, 154, 252, 197, 188, 52, 137, 117, 198, 63, 135, 186, 250, 7, 72, 58, 4, 9, 193, 101, 52, 44, 187, 183, 171, 184,
167, 38, 136, 96, 58, 11, 1, 115, 229, 224, 37, 112, 170, 59, 68, 196, 197, 130, 47, 189, 81, 203, 51, 110, 146, 175, 213, 88, 79, 93, 64, 107,
36, 64, 91, 213, 14, 180, 190, 164, 8, 56, 214, 77, 202, 193, 97, 84, 121, 237, 0, 46, 120, 141, 199, 158, 174, 114, 205, 201, 151, 185, 242, 29,
152, 83, 236, 211, 20, 217, 2, 228, 140, 69, 121, 111, 113, 128, 210, 51, 162, 117, 85, 54, 14, 202, 216, 169, 230, 252, 188, 251, 36, 233, 147, 82,
53, 6, 235, 34, 102, 29, 120, 35, 50, 65, 160, 249, 99, 169, 199, 195] 115, 61, 255, 38, 220, 218, 40, 224, 48, 125, 204, 156, 240, 254, 166, 243]
def main(args, seed=None): def main(args, seed=None):
start = time.clock() start = time.clock()
# initialize the world # initialize the world
world = World(args.shuffle, args.logic, args.mode, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.beatableonly, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.custom, args.customitemarray) world = World(args.shuffle, args.logic, args.mode, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.beatableonly, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray)
logger = logging.getLogger('') logger = logging.getLogger('')
if seed is None: if seed is None:
random.seed(None) random.seed(None)
@ -111,7 +112,7 @@ def main(args, seed=None):
else: else:
sprite = None sprite = None
outfilebase = 'ER_%s_%s-%s-%s%s_%s-%s%s%s%s_%s' % (world.logic, world.difficulty, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-shuffleganon" if world.shuffle_ganon else "", world.seed) outfilebase = 'ER_%s_%s-%s-%s%s_%s-%s%s%s%s_%s' % (world.logic, world.difficulty, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", world.seed)
if not args.suppress_rom: if not args.suppress_rom:
if args.jsonout: if args.jsonout:
@ -139,7 +140,7 @@ def gt_filler(world):
def copy_world(world): def copy_world(world):
# ToDo: Not good yet # ToDo: Not good yet
ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.custom, world.customitemarray) ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray)
ret.required_medallions = list(world.required_medallions) ret.required_medallions = list(world.required_medallions)
ret.swamp_patch_required = world.swamp_patch_required ret.swamp_patch_required = world.swamp_patch_required
ret.ganon_at_pyramid = world.ganon_at_pyramid ret.ganon_at_pyramid = world.ganon_at_pyramid
@ -157,6 +158,13 @@ def copy_world(world):
create_regions(ret) create_regions(ret)
create_dungeons(ret) create_dungeons(ret)
copy_dynamic_regions_and_locations(world, ret)
for shop in world.shops:
copied_shop = ret.get_region(shop.region.name).shop
copied_shop.active = shop.active
copied_shop.inventory = copy.copy(shop.inventory)
# connect copied world # connect copied world
for region in world.regions: for region in world.regions:
copied_region = ret.get_region(region.name) copied_region = ret.get_region(region.name)
@ -185,6 +193,24 @@ def copy_world(world):
return ret return ret
def copy_dynamic_regions_and_locations(world, ret):
for region in world.dynamic_regions:
new_reg = Region(region.name, region.type)
ret.regions.append(new_reg)
ret.dynamic_regions.append(new_reg)
# Note: ideally exits should be copied here, but the current use case (Take anys) do not require this
if region.shop:
new_reg.shop = Shop(new_reg, region.shop.room_id, region.shop.type, region.shop.shopkeeper_config, region.shop.replaceable)
ret.shops.append(new_reg.shop)
for location in world.dynamic_locations:
new_loc = Location(location.name, location.address, location.crystal, location.hint_text, location.parent_region)
new_reg = ret.get_region(location.parent_region.name)
new_reg.locations.append(new_loc)
def create_playthrough(world): def create_playthrough(world):
# create a copy as we will modify it # create a copy as we will modify it
old_world = world old_world = world
@ -294,8 +320,6 @@ def create_playthrough(world):
old_world.spoiler.paths = {location.name : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere} old_world.spoiler.paths = {location.name : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere}
if any(exit == 'Pyramid Fairy' for path in old_world.spoiler.paths.values() for (_, exit) in path): if any(exit == 'Pyramid Fairy' for path in old_world.spoiler.paths.values() for (_, exit) in path):
old_world.spoiler.paths['Big Bomb Shop'] = get_path(state, world.get_region('Big Bomb Shop')) old_world.spoiler.paths['Big Bomb Shop'] = get_path(state, world.get_region('Big Bomb Shop'))
if any(exit == 'Swamp Palace Moat' for path in old_world.spoiler.paths.values() for (_, exit) in path) or 'Sunken Treasure' in old_world.required_locations:
old_world.spoiler.paths['Dam'] = get_path(state, world.get_region('Dam'))
# we can finally output our playthrough # we can finally output our playthrough
old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)]) old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)])

View File

@ -33,7 +33,7 @@ def main(args):
start_time = time.clock() start_time = time.clock()
# initialize the world # initialize the world
world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False, False, None) world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False, False, False, None)
logger = logging.getLogger('') logger = logging.getLogger('')
hasher = hashlib.md5() hasher = hashlib.md5()

View File

@ -54,6 +54,10 @@ The game can be completed without knowing how to perform glitches of any kind.
May require Fake Flippers, Bunny Revival. May require Fake Flippers, Bunny Revival.
### No Logic
Items are placed without regard for progression or the seed being possible. Major glitches are likely required.
## Game Goal ## Game Goal
### Ganon ### Ganon
@ -259,7 +263,7 @@ generate spoilers for statistical analysis.
## Enable L/R button quickswapping ## Enable L/R button quickswapping
Use to enable quick item swap with L/R buttons Use to enable quick item swap with L/R buttons. Press L and R together to switch the state of items like the Mushroom/Powder pair.
## Keysanity ## Keysanity
@ -269,6 +273,16 @@ is traditionally a guaranteed Small Key still is. These items will be distribute
the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell
for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize. for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize.
## Retro
This setting turns all Small Keys into universal Small Keys that can be used in any dungeon and are distributed across the world.
The Bow now consumed rupees to shoot; the cost is 10 rupees per Wood Arrow and 50 per Silver Arrow. Shooting Wood Arrows requires
the purchase of an arrow item from shops, and to account for this and the dynamic use of keys, both Wood Arrows and Small Keys will
be added to several shops around the world. Four "take any" caves are added that allow the player to choose between an extra Heart
Container and a Bottle being filled with Blue Potion, and one of the four swords from the item pool is placed into a special cave as
well. The five caves that are removed for these will be randomly selected single entrance caves that did not contain any items or any shops.
In further concert with the Bow changes, all arrows under pots, in chests, and elsewhere in the seed will be replaced with rupees.
## Place Dungeon Items ## Place Dungeon Items
If not set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world. If not set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world.
@ -311,7 +325,7 @@ Show the help message and exit.
Output a Spoiler File (default: False) Output a Spoiler File (default: False)
``` ```
--logic [{noglitches,minorglitches}] --logic [{noglitches,minorglitches,nologic}]
``` ```
Select the game logic (default: noglitches) Select the game logic (default: noglitches)
@ -353,7 +367,7 @@ Select the setting for progressive equipment. (default: on)
Select item distribution algorithm. (default: balanced) Select item distribution algorithm. (default: balanced)
``` ```
--shuffle [{default,simple,restricted,full,madness,insanity,dungeonsfull,dungeonssimple}] --shuffle [{default,simple,restricted,full,crossed,insanity,restricted_legacy,full_legacy,madness_legacy,insanity_legacy,dungeonsfull,dungeonssimple}]
``` ```
Select entrance shuffle algorithm. (default: full) Select entrance shuffle algorithm. (default: full)
@ -407,6 +421,12 @@ Disables game music, resulting in the game sound being just the SFX. (default: F
Enable Keysanity (default: False) Enable Keysanity (default: False)
```
--retro
```
Enable Retro mode (default: False)
``` ```
--nodungeonitems --nodungeonitems
``` ```
@ -420,6 +440,12 @@ This may lead to different amount of itempool items being placed in a dungeon th
Select frequency of beeps when on low health. (default: normal) Select frequency of beeps when on low health. (default: normal)
```
--heartcolor [{red,blue,green,yellow}]
```
Select the color of Link\'s heart meter. (default: red)
``` ```
--sprite SPRITE --sprite SPRITE
``` ```

View File

@ -1,5 +1,5 @@
import collections import collections
from BaseClasses import Region, Location, Entrance, RegionType from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
def create_regions(world): def create_regions(world):
@ -25,10 +25,10 @@ def create_regions(world):
create_cave_region('Hyrule Castle Secret Entrance', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']), create_cave_region('Hyrule Castle Secret Entrance', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']),
create_lw_region('Zoras River', ['King Zora', 'Zora\'s Ledge']), create_lw_region('Zoras River', ['King Zora', 'Zora\'s Ledge']),
create_cave_region('Waterfall of Wishing', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']), create_cave_region('Waterfall of Wishing', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']),
create_lw_region('Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']), create_lw_region('Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
create_cave_region('Kings Grave', ['King\'s Tomb']), create_cave_region('Kings Grave', ['King\'s Tomb']),
create_cave_region('North Fairy Cave', None, ['North Fairy Cave Exit']), create_cave_region('North Fairy Cave', None, ['North Fairy Cave Exit']),
create_cave_region('Dam', ['Floodgate Chest']), create_cave_region('Dam', ['Floodgate', 'Floodgate Chest']),
create_cave_region('Links House', ['Link\'s House'], ['Links House Exit']), create_cave_region('Links House', ['Link\'s House'], ['Links House Exit']),
create_cave_region('Chris Houlihan Room', None, ['Chris Houlihan Room Exit']), create_cave_region('Chris Houlihan Room', None, ['Chris Houlihan Room Exit']),
create_cave_region('Tavern', ['Kakariko Tavern']), create_cave_region('Tavern', ['Kakariko Tavern']),
@ -57,7 +57,7 @@ def create_regions(world):
create_cave_region('Kakariko Well (top)', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle', create_cave_region('Kakariko Well (top)', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']), 'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
create_cave_region('Kakariko Well (bottom)', None, ['Kakariko Well Exit']), create_cave_region('Kakariko Well (bottom)', None, ['Kakariko Well Exit']),
create_cave_region('Blacksmiths Hut', ['Blacksmith']), create_cave_region('Blacksmiths Hut', ['Blacksmith', 'Missing Smith']),
create_lw_region('Bat Cave Drop Ledge', None, ['Bat Cave Drop']), create_lw_region('Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
create_cave_region('Bat Cave (right)', ['Magic Bat'], ['Bat Cave Door']), create_cave_region('Bat Cave (right)', ['Magic Bat'], ['Bat Cave Door']),
create_cave_region('Bat Cave (left)', None, ['Bat Cave Exit']), create_cave_region('Bat Cave (left)', None, ['Bat Cave Exit']),
@ -95,7 +95,7 @@ def create_regions(world):
create_lw_region('Desert Palace Lone Stairs', None, ['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']), create_lw_region('Desert Palace Lone Stairs', None, ['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']),
create_lw_region('Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks']), create_lw_region('Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks']),
create_dungeon_region('Desert Palace Main (Outer)', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'], create_dungeon_region('Desert Palace Main (Outer)', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']), ['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']),
create_dungeon_region('Desert Palace Main (Inner)', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']), create_dungeon_region('Desert Palace Main (Inner)', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
create_dungeon_region('Desert Palace East', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']), create_dungeon_region('Desert Palace East', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
create_dungeon_region('Desert Palace North', ['Desert Palace - Lanmolas', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']), create_dungeon_region('Desert Palace North', ['Desert Palace - Lanmolas', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']),
@ -125,7 +125,7 @@ def create_regions(world):
create_cave_region('Spectacle Rock Cave (Peak)', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']), create_cave_region('Spectacle Rock Cave (Peak)', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
create_lw_region('East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']), create_lw_region('East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']),
create_cave_region('Hookshot Fairy'), create_cave_region('Hookshot Fairy'),
create_cave_region('Paradox Cave Front', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)']), create_cave_region('Paradox Cave Front', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']),
create_cave_region('Paradox Cave Chest Area', ['Paradox Cave Lower - Far Left', create_cave_region('Paradox Cave Chest Area', ['Paradox Cave Lower - Far Left',
'Paradox Cave Lower - Left', 'Paradox Cave Lower - Left',
'Paradox Cave Lower - Right', 'Paradox Cave Lower - Right',
@ -135,6 +135,7 @@ def create_regions(world):
'Paradox Cave Upper - Right'], 'Paradox Cave Upper - Right'],
['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']), ['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']),
create_cave_region('Paradox Cave', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']), create_cave_region('Paradox Cave', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']),
create_cave_region('Light World Death Mountain Shop'),
create_lw_region('East Death Mountain (Top)', None, ['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access', 'East Death Mountain Drop', 'Turtle Rock Teleporter', 'Fairy Ascension Ledge']), create_lw_region('East Death Mountain (Top)', None, ['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access', 'East Death Mountain Drop', 'Turtle Rock Teleporter', 'Fairy Ascension Ledge']),
create_lw_region('Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop']), create_lw_region('Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop']),
create_cave_region('Spiral Cave (Top)', ['Spiral Cave'], ['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']), create_cave_region('Spiral Cave (Top)', ['Spiral Cave'], ['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']),
@ -164,10 +165,10 @@ def create_regions(world):
create_cave_region('Dark Lake Hylia Ledge Spike Cave'), create_cave_region('Dark Lake Hylia Ledge Spike Cave'),
create_cave_region('Hype Cave', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', create_cave_region('Hype Cave', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left',
'Hype Cave - Bottom', 'Hype Cave - Generous Guy']), 'Hype Cave - Bottom', 'Hype Cave - Generous Guy']),
create_dw_region('West Dark World', None, ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot', 'Bumper Cave Entrance Rock', create_dw_region('West Dark World', ['Frog'], ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot', 'Bumper Cave Entrance Rock',
'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Dark World Lumberjack Shop']), 'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Dark World Lumberjack Shop']),
create_dw_region('Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop']), create_dw_region('Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop']),
create_dw_region('Hammer Peg Area', None, ['Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Peg Area Rocks']), create_dw_region('Hammer Peg Area', ['Dark Blacksmith Ruins'], ['Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Peg Area Rocks']),
create_dw_region('Bumper Cave Entrance', None, ['Bumper Cave (Bottom)', 'Bumper Cave Entrance Mirror Spot', 'Bumper Cave Entrance Drop']), create_dw_region('Bumper Cave Entrance', None, ['Bumper Cave (Bottom)', 'Bumper Cave Entrance Mirror Spot', 'Bumper Cave Entrance Drop']),
create_cave_region('Fortune Teller (Dark)'), create_cave_region('Fortune Teller (Dark)'),
create_cave_region('Village of Outcasts Shop'), create_cave_region('Village of Outcasts Shop'),
@ -290,6 +291,14 @@ def create_regions(world):
create_dw_region('Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop']) create_dw_region('Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop'])
] ]
for region_name, (room_id, shopkeeper, replaceable) in shop_table.items():
region = world.get_region(region_name)
shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable)
region.shop = shop
world.shops.append(shop)
for index, (item, price) in enumerate(default_shop_contents[region_name]):
shop.add_inventory(index, item, price)
world.intialize_regions() world.intialize_regions()
def create_lw_region(name, locations=None, exits=None): def create_lw_region(name, locations=None, exits=None):
@ -347,6 +356,38 @@ def mark_light_world_regions(world):
seen.add(exit.connected_region) seen.add(exit.connected_region)
queue.append(exit.connected_region) queue.append(exit.connected_region)
# (room_id, shopkeeper, replaceable)
shop_table = {
'Cave Shop (Dark Death Mountain)': (0x0112, 0x51, True),
'Red Shield Shop': (0x0110, 0x51, True),
'Dark Lake Hylia Shop': (0x010F, 0x51, True),
'Dark World Lumberjack Shop': (0x010F, 0x51, True),
'Village of Outcasts Shop': (0x010F, 0x51, True),
'Dark World Potion Shop': (0x010F, 0x51, True),
'Light World Death Mountain Shop': (0x00FF, 0x51, True),
'Kakariko Shop': (0x011F, 0x51, True),
'Cave Shop (Lake Hylia)': (0x0112, 0x51, True),
'Potion Shop': (0x0109, 0xFF, False),
# Bomb Shop not currently modeled as a shop, due to special nature of items
}
# region, [item]
# slot, item, price, max=0, replacement=None, replacement_price=0
# item = (item, price)
_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)]
default_shop_contents = {
'Cave Shop (Dark Death Mountain)': _basic_shop_defaults,
'Red Shield Shop': [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)],
'Dark Lake Hylia Shop': _dark_world_shop_defaults,
'Dark World Lumberjack Shop': _dark_world_shop_defaults,
'Village of Outcasts Shop': _dark_world_shop_defaults,
'Dark World Potion Shop': _dark_world_shop_defaults,
'Light World Death Mountain Shop': _basic_shop_defaults,
'Kakariko Shop': _basic_shop_defaults,
'Cave Shop (Lake Hylia)': _basic_shop_defaults,
'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
}
location_table = {'Mushroom': (0x180013, False, 'in the woods'), location_table = {'Mushroom': (0x180013, False, 'in the woods'),
'Bottle Merchant': (0x2EB18, False, 'with a merchant'), 'Bottle Merchant': (0x2EB18, False, 'with a merchant'),
@ -567,6 +608,10 @@ location_table = {'Mushroom': (0x180013, False, 'in the woods'),
'Ganon': (None, False, 'from me'), 'Ganon': (None, False, 'from me'),
'Agahnim 1': (None, False, 'from my wizardry form'), 'Agahnim 1': (None, False, 'from my wizardry form'),
'Agahnim 2': (None, False, 'from my wizardry form'), 'Agahnim 2': (None, False, 'from my wizardry form'),
'Floodgate': (None, False, None),
'Frog': (None, False, None),
'Missing Smith': (None, False, None),
'Dark Blacksmith Ruins': (None, False, None),
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'), 'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'),
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'), 'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'),
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'), 'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'),

125
Rom.py
View File

@ -6,16 +6,17 @@ import os
import struct import struct
import random import random
from BaseClasses import ShopType
from Dungeons import dungeon_music_addresses from Dungeons import dungeon_music_addresses
from Text import string_to_alttp_text, text_addresses, Credits from Text import MultiByteTextMapper, text_addresses, Credits
from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Utils import local_path from Utils import local_path, int16_as_bytes, int32_as_bytes
from Items import ItemFactory from Items import ItemFactory
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'dc5840f0d1ef7b51009c5625a054b3dd' RANDOMIZERBASEHASH = '354bad0d01b6284c89f05c9b414d48c1'
class JsonRom(object): class JsonRom(object):
@ -259,15 +260,6 @@ class Sprite(object):
# split into palettes of 15 colors # split into palettes of 15 colors
return array_chunk(palette_as_colors, 15) return array_chunk(palette_as_colors, 15)
def int16_as_bytes(value):
value = value & 0xFFFF
return [value & 0xFF, (value >> 8) & 0xFF]
def int32_as_bytes(value):
value = value & 0xFFFFFFFF
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]
def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None): def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
# patch items # patch items
for location in world.get_locations(): for location in world.get_locations():
@ -355,6 +347,7 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
# patch door table # patch door table
rom.write_byte(0xDBB73 + exit.addresses, exit.target) rom.write_byte(0xDBB73 + exit.addresses, exit.target)
write_custom_shops(rom, world)
# patch medallion requirements # patch medallion requirements
if world.required_medallions[0] == 'Bombos': if world.required_medallions[0] == 'Bombos':
@ -411,7 +404,7 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
# Powdered Fairies Prize # Powdered Fairies Prize
rom.write_byte(0x36DD0, 0xD8) # One Heart rom.write_byte(0x36DD0, 0xD8) # One Heart
# potion heal amount # potion heal amount
rom.write_byte(0x180084, 0x28) # Five Hearts rom.write_byte(0x180084, 0x38) # Seven Hearts
# potion magic restore amount # potion magic restore amount
rom.write_byte(0x180085, 0x40) # Half Magic rom.write_byte(0x180085, 0x40) # Half Magic
#Cape magic cost #Cape magic cost
@ -423,6 +416,10 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
overflow_replacement = GREEN_TWENTY_RUPEES overflow_replacement = GREEN_TWENTY_RUPEES
# Rupoor negative value # Rupoor negative value
rom.write_int16_to_rom(0x180036, world.rupoor_cost) rom.write_int16_to_rom(0x180036, world.rupoor_cost)
# Set stun items
rom.write_byte(0x180180, 0x02) # Hookshot only
# Make silver arrows only usable against Ganon
rom.write_byte(0x180181, 0x01)
#Make Blue Shield more expensive #Make Blue Shield more expensive
rom.write_bytes(0xF73D2, [0xFC, 0xFF]) rom.write_bytes(0xF73D2, [0xFC, 0xFF])
rom.write_bytes(0xF73DA, [0x04, 0x00]) rom.write_bytes(0xF73DA, [0x04, 0x00])
@ -443,20 +440,24 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
rom.write_byte(0xF723F, 0xE7) rom.write_byte(0xF723F, 0xE7)
elif world.difficulty == 'expert': elif world.difficulty == 'expert':
# Powdered Fairies Prize # Powdered Fairies Prize
rom.write_byte(0x36DD0, 0x79) # Bees rom.write_byte(0x36DD0, 0xD8) # One Heart
# potion heal amount # potion heal amount
rom.write_byte(0x180084, 0x08) # One Heart rom.write_byte(0x180084, 0x08) # One Heart
# potion magic restore amount # potion magic restore amount
rom.write_byte(0x180085, 0x20) # Quarter Magic rom.write_byte(0x180085, 0x20) # Quarter Magic
#Cape magic cost #Cape magic cost
rom.write_bytes(0x3ADA7, [0x02, 0x02, 0x02]) rom.write_bytes(0x3ADA7, [0x01, 0x01, 0x01])
# Byrna Invulnerability: off # Byrna Invulnerability: off
rom.write_byte(0x18004F, 0x00) rom.write_byte(0x18004F, 0x00)
#Disable catching fairies #Disable catching fairies
rom.write_byte(0x34FD6, 0x80) rom.write_byte(0x34FD6, 0x80)
overflow_replacement = GREEN_TWENTY_RUPEES overflow_replacement = GREEN_TWENTY_RUPEES
# Rupoor negative value # Rupoor negative value
rom.write_int16_to_rom(0x180036, 20) rom.write_int16_to_rom(0x180036, world.rupoor_cost)
# Set stun items
rom.write_byte(0x180180, 0x00) # Nothing
# Make silver arrows only usable against Ganon
rom.write_byte(0x180181, 0x01)
#Make Blue Shield more expensive #Make Blue Shield more expensive
rom.write_bytes(0xF73D2, [0xFC, 0xFF]) rom.write_bytes(0xF73D2, [0xFC, 0xFF])
rom.write_bytes(0xF73DA, [0x04, 0x00]) rom.write_bytes(0xF73DA, [0x04, 0x00])
@ -483,14 +484,18 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
# potion magic restore amount # potion magic restore amount
rom.write_byte(0x180085, 0x00) # No healing rom.write_byte(0x180085, 0x00) # No healing
#Cape magic cost #Cape magic cost
rom.write_bytes(0x3ADA7, [0x02, 0x02, 0x02]) rom.write_bytes(0x3ADA7, [0x01, 0x01, 0x01])
# Byrna Invulnerability: off # Byrna Invulnerability: off
rom.write_byte(0x18004F, 0x00) rom.write_byte(0x18004F, 0x00)
#Disable catching fairies #Disable catching fairies
rom.write_byte(0x34FD6, 0x80) rom.write_byte(0x34FD6, 0x80)
overflow_replacement = GREEN_TWENTY_RUPEES overflow_replacement = GREEN_TWENTY_RUPEES
# Rupoor negative value # Rupoor negative value
rom.write_int16_to_rom(0x180036, 9999) rom.write_int16_to_rom(0x180036, world.rupoor_cost)
# Set stun items
rom.write_byte(0x180180, 0x00) # Nothing
# Make silver arrows only usable against Ganon
rom.write_byte(0x180181, 0x01)
#Make Blue Shield more expensive #Make Blue Shield more expensive
rom.write_bytes(0xF73D2, [0xFC, 0xFF]) rom.write_bytes(0xF73D2, [0xFC, 0xFF])
rom.write_bytes(0xF73DA, [0x04, 0x00]) rom.write_bytes(0xF73DA, [0x04, 0x00])
@ -522,14 +527,25 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
rom.write_byte(0x18004F, 0x01) rom.write_byte(0x18004F, 0x01)
#Enable catching fairies #Enable catching fairies
rom.write_byte(0x34FD6, 0xF0) rom.write_byte(0x34FD6, 0xF0)
# Rupoor negative value
rom.write_int16_to_rom(0x180036, world.rupoor_cost)
# Set stun items
rom.write_byte(0x180180, 0x03) # All standard items
# Make silver arrows freely usable
rom.write_byte(0x180181, 0x00)
#Set overflow items for progressive equipment #Set overflow items for progressive equipment
if world.goal == 'triforcehunt': if world.timer in ['timed', 'timed-countdown', 'timed-ohko']:
overflow_replacement = TRIFORCE_PIECE
elif world.timer in ['timed', 'timed-countdown', 'timed-ohko']:
overflow_replacement = GREEN_CLOCK overflow_replacement = GREEN_CLOCK
else: else:
overflow_replacement = GREEN_TWENTY_RUPEES overflow_replacement = GREEN_TWENTY_RUPEES
if world.difficulty in ['easy']:
rom.write_byte(0x180182, 0x03) # auto equip silvers on pickup and at ganon
elif world.retro and world.difficulty in ['hard','expert', 'insane']: #FIXME: this is temporary for v29 baserom
rom.write_byte(0x180182, 0x03) # auto equip silvers on pickup and at ganon
else:
rom.write_byte(0x180182, 0x01) # auto equip silvers on pickup
#Byrna residual magic cost #Byrna residual magic cost
rom.write_bytes(0x45C42, [0x04, 0x02, 0x01]) rom.write_bytes(0x45C42, [0x04, 0x02, 0x01])
@ -549,6 +565,19 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF, prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF,
0xDC, 0xDC, 0xDC, 0xDD, 0xDC, 0xDC, 0xDE, 0xDC, 0xE1, 0xD8, 0xE1, 0xE2, 0xE1, 0xD8, 0xE1, 0xE2, 0xDF, 0xD9, 0xD8, 0xE1, 0xDF, 0xDC, 0xD9, 0xD8, 0xDC, 0xDC, 0xDC, 0xDD, 0xDC, 0xDC, 0xDE, 0xDC, 0xE1, 0xD8, 0xE1, 0xE2, 0xE1, 0xD8, 0xE1, 0xE2, 0xDF, 0xD9, 0xD8, 0xE1, 0xDF, 0xDC, 0xD9, 0xD8,
0xD8, 0xE3, 0xE0, 0xDB, 0xDE, 0xD8, 0xDB, 0xE2, 0xD9, 0xDA, 0xDB, 0xD9, 0xDB, 0xD9, 0xDB] 0xD8, 0xE3, 0xE0, 0xDB, 0xDE, 0xD8, 0xDB, 0xE2, 0xD9, 0xDA, 0xDB, 0xD9, 0xDB, 0xD9, 0xDB]
dig_prizes = [0xB2, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8,
0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA,
0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC,
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE,
0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2,
0xE3, 0xE3, 0xE3, 0xE3, 0xE3]
if world.retro:
prize_replacements = {0xE1: 0xDA, #5 Arrows -> Blue Rupee
0xE2: 0xDB} #10 Arrows -> Red Rupee
prizes = [prize_replacements.get(prize,prize) for prize in prizes]
dig_prizes = [prize_replacements.get(prize,prize) for prize in dig_prizes]
rom.write_bytes(0x180100, dig_prizes)
random.shuffle(prizes) random.shuffle(prizes)
# write tree pull prizes # write tree pull prizes
@ -726,9 +755,7 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror
rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp
rom.write_byte(0x180174, 0x01 if world.fix_fake_world else 0x00) rom.write_byte(0x180174, 0x01 if world.fix_fake_world else 0x00)
rom.write_byte(0x180175, 0x00) # Arrow mode: normal rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles
rom.write_int16_to_rom(0x180176, 0) # Wood Arrow Cost (rupee arrow mode)
rom.write_int16_to_rom(0x180178, 0) # Silver Arrow Cost (rupee arrow mode)
rom.write_byte(0x180034, 0x0A) # starting max bombs rom.write_byte(0x180034, 0x0A) # starting max bombs
rom.write_byte(0x180035, 30) # starting max arrows rom.write_byte(0x180035, 30) # starting max arrows
for x in range(0x183000, 0x18304F): for x in range(0x183000, 0x18304F):
@ -769,11 +796,20 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
rom.write_byte(0x18003C, 0x00) rom.write_byte(0x18003C, 0x00)
rom.write_byte(0x180045, 0xFF if world.keysanity else 0x00) # free roaming items in menu rom.write_byte(0x180045, 0xFF if world.keysanity else 0x00) # free roaming items in menu
rom.write_byte(0x180172, 0x01 if world.retro else 0x00) # universal keys
rom.write_byte(0x180175, 0x01 if world.retro else 0x00) # rupee bow
rom.write_byte(0x180176, 0x0A if world.retro else 0x00) # wood arrow cost
rom.write_byte(0x180178, 0x32 if world.retro else 0x00) # silver arrow cost
rom.write_byte(0x301FC, 0xDA if world.retro else 0xE1) # rupees replace arrows under pots
rom.write_byte(0x30052, 0xDB if world.retro else 0xE2) # replace arrows in fish prize from bottle merchant
rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if world.retro else [0xAF, 0x77, 0xF3, 0x7E]) # Thief steals rupees instead of arrows
rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if world.retro else [0xAF, 0x77, 0xF3, 0x7E]) # Pikit steals rupees instead of arrows
rom.write_bytes(0xEDA5, [0x35, 0x41] if world.retro else [0x43, 0x44]) # Chest game gives rupees instead of arrows
digging_game_rng = random.randint(1, 30) # set rng for digging game digging_game_rng = random.randint(1, 30) # set rng for digging game
rom.write_byte(0x180020, digging_game_rng) rom.write_byte(0x180020, digging_game_rng)
rom.write_byte(0xEFD95, digging_game_rng) rom.write_byte(0xEFD95, digging_game_rng)
rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
rom.write_byte(0x180042, 0x01 if world.save_and_quite_from_boss else 0x00) # Allow Save and Quite after boss kill rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill
# remove shield from uncle # remove shield from uncle
rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E]) rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
@ -784,15 +820,12 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
rom.write_bytes(0x6D2FB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E]) rom.write_bytes(0x6D2FB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E]) rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
# Shop table
rom.write_bytes(0x184800, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved # patch swamp: Need to enable permanent drain of water as dam or swamp were moved
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required else 0x00) rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required else 0x00)
# powder patch: remove the need to leave the scrren after powder, since it causes problems for potion shop at race game # powder patch: remove the need to leave the scrren after powder, since it causes problems for potion shop at race game
# temporarally we are just nopping out this check we will conver this to a rom fix soon. # temporarally we are just nopping out this check we will conver this to a rom fix soon.
rom.write_bytes(0x02F539,[0xEA,0xEA,0xEA,0xEA,0xEA] if world.powder_patch_required else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F]) rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F])
# allow smith into multi-entrance caves in appropriate shuffles # allow smith into multi-entrance caves in appropriate shuffles
if world.shuffle in ['restricted', 'full', 'crossed', 'insanity']: if world.shuffle in ['restricted', 'full', 'crossed', 'insanity']:
@ -827,6 +860,38 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
return rom return rom
def write_custom_shops(rom, world):
shops = [shop for shop in world.shops if shop.replaceable and shop.active]
shop_data = bytearray()
items_data = bytearray()
sram_offset = 0
for shop_id, shop in enumerate(shops):
if shop_id == len(shops) - 1:
shop_id = 0xFF
bytes = shop.get_bytes()
bytes[0] = shop_id
bytes[-1] = sram_offset
if shop.type == ShopType.TakeAny:
sram_offset += 1
else:
sram_offset += shop.item_count
shop_data.extend(bytes)
# [id][item][price-low][price-high][max][repl_id][repl_price-low][repl_price-high]
for item in shop.inventory:
if item is None:
break
item_data = [shop_id, ItemFactory(item['item']).code] + int16_as_bytes(item['price']) + [item['max'], ItemFactory(item['replacement']).code if item['replacement'] else 0xFF] + int16_as_bytes(item['replacement_price'])
items_data.extend(item_data)
rom.write_bytes(0x184800, shop_data)
items_data.extend([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
rom.write_bytes(0x184900, items_data)
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite): def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite):
# enable instant item menu # enable instant item menu
@ -955,7 +1020,7 @@ def write_sprite(rom, sprite):
def write_string_to_rom(rom, target, string): def write_string_to_rom(rom, target, string):
address, maxbytes = text_addresses[target] address, maxbytes = text_addresses[target]
rom.write_bytes(address, string_to_alttp_text(string, maxbytes)) rom.write_bytes(address, MultiByteTextMapper.convert(string, maxbytes))
def write_strings(rom, world): def write_strings(rom, world):

173
Rules.py
View File

@ -3,6 +3,15 @@ import logging
def set_rules(world): def set_rules(world):
if world.logic == 'nologic':
logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
world.get_region('Links House').can_reach = lambda state: True
world.get_region('Sanctuary').can_reach = lambda state: True
old_rule = world.get_region('Old Man House').can_reach
world.get_region('Old Man House').can_reach = lambda state: state.can_reach('Old Man', 'Location') or old_rule(state)
return
global_rules(world) global_rules(world)
if world.mode == 'open': if world.mode == 'open':
@ -110,11 +119,14 @@ def global_rules(world):
set_rule(world.get_entrance('South Hyrule Teleporter'), lambda state: state.has('Hammer') and state.can_lift_rocks() and state.has_Pearl()) # bunny cannot use hammer set_rule(world.get_entrance('South Hyrule Teleporter'), lambda state: state.has('Hammer') and state.can_lift_rocks() and state.has_Pearl()) # bunny cannot use hammer
set_rule(world.get_entrance('Kakariko Teleporter'), lambda state: ((state.has('Hammer') and state.can_lift_rocks()) or state.can_lift_heavy_rocks()) and state.has_Pearl()) # bunny cannot lift bushes set_rule(world.get_entrance('Kakariko Teleporter'), lambda state: ((state.has('Hammer') and state.can_lift_rocks()) or state.can_lift_heavy_rocks()) and state.has_Pearl()) # bunny cannot lift bushes
set_rule(world.get_location('Flute Spot'), lambda state: state.has('Shovel')) set_rule(world.get_location('Flute Spot'), lambda state: state.has('Shovel'))
set_rule(world.get_location('Purple Chest'), lambda state: state.can_reach('Blacksmith', 'Location')) # Can S&Q with chest set_rule(world.get_location('Dark Blacksmith Ruins'), lambda state: state.has('Return Smith'))
set_rule(world.get_location('Purple Chest'), lambda state: state.has('Pick Up Purple Chest')) # Can S&Q with chest
set_rule(world.get_location('Zora\'s Ledge'), lambda state: state.has('Flippers')) set_rule(world.get_location('Zora\'s Ledge'), lambda state: state.has('Flippers'))
set_rule(world.get_entrance('Waterfall of Wishing'), lambda state: state.has('Flippers')) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo set_rule(world.get_entrance('Waterfall of Wishing'), lambda state: state.has('Flippers')) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo
set_rule(world.get_location('Blacksmith'), lambda state: state.can_lift_heavy_rocks() and state.can_reach('West Dark World')) # Can S&Q with smith set_rule(world.get_location('Frog'), lambda state: state.can_lift_heavy_rocks()) # will get automatic moon pearl requirement
set_rule(world.get_location('Missing Smith'), lambda state: state.has('Get Frog')) # Can S&Q with smith
set_rule(world.get_location('Blacksmith'), lambda state: state.has('Return Smith'))
set_rule(world.get_location('Magic Bat'), lambda state: state.has('Magic Powder')) set_rule(world.get_location('Magic Bat'), lambda state: state.has('Magic Powder'))
set_rule(world.get_location('Sick Kid'), lambda state: state.has_bottle()) set_rule(world.get_location('Sick Kid'), lambda state: state.has_bottle())
set_rule(world.get_location('Library'), lambda state: state.has_Boots()) set_rule(world.get_location('Library'), lambda state: state.has_Boots())
@ -125,8 +137,8 @@ def global_rules(world):
set_rule(world.get_location('Master Sword Pedestal'), lambda state: state.has('Red Pendant') and state.has('Blue Pendant') and state.has('Green Pendant')) set_rule(world.get_location('Master Sword Pedestal'), lambda state: state.has('Red Pendant') and state.has('Blue Pendant') and state.has('Green Pendant'))
set_rule(world.get_location('Sahasrahla'), lambda state: state.has('Green Pendant')) set_rule(world.get_location('Sahasrahla'), lambda state: state.has('Green Pendant'))
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has_beam_sword() or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has_beam_sword() or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has_sword() and state.has('Small Key (Agahnims Tower)', 2)) set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has_sword() and state.has_key('Small Key (Agahnims Tower)', 2))
set_rule(world.get_location('Castle Tower - Dark Maze'), lambda state: state.has('Small Key (Agahnims Tower)')) set_rule(world.get_location('Castle Tower - Dark Maze'), lambda state: state.has_key('Small Key (Agahnims Tower)'))
set_rule(world.get_entrance('Top of Pyramid'), lambda state: state.has('Beat Agahnim 1')) set_rule(world.get_entrance('Top of Pyramid'), lambda state: state.has('Beat Agahnim 1'))
set_rule(world.get_entrance('Old Man Cave Exit (West)'), lambda state: False) # drop cannot be climbed up set_rule(world.get_entrance('Old Man Cave Exit (West)'), lambda state: False) # drop cannot be climbed up
set_rule(world.get_entrance('Broken Bridge (West)'), lambda state: state.has('Hookshot')) set_rule(world.get_entrance('Broken Bridge (West)'), lambda state: state.has('Hookshot'))
@ -212,29 +224,29 @@ def global_rules(world):
set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_sword() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword required to cast magic (!) set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_sword() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword required to cast magic (!)
set_rule(world.get_location('Mimic Cave'), lambda state: state.has('Hammer')) set_rule(world.get_location('Mimic Cave'), lambda state: state.has('Hammer'))
set_rule(world.get_entrance('Sewers Door'), lambda state: state.has('Small Key (Escape)')) set_rule(world.get_entrance('Sewers Door'), lambda state: state.has_key('Small Key (Escape)'))
set_rule(world.get_entrance('Sewers Back Door'), lambda state: state.has('Small Key (Escape)')) set_rule(world.get_entrance('Sewers Back Door'), lambda state: state.has_key('Small Key (Escape)'))
set_rule(world.get_location('Eastern Palace - Big Chest'), lambda state: state.has('Big Key (Eastern Palace)')) set_rule(world.get_location('Eastern Palace - Big Chest'), lambda state: state.has('Big Key (Eastern Palace)'))
set_rule(world.get_location('Eastern Palace - Armos Knights'), lambda state: state.has('Bow') and state.has('Big Key (Eastern Palace)')) set_rule(world.get_location('Eastern Palace - Armos Knights'), lambda state: state.can_shoot_arrows() and state.has('Big Key (Eastern Palace)'))
set_rule(world.get_location('Eastern Palace - Prize'), lambda state: state.has('Bow') and state.has('Big Key (Eastern Palace)')) set_rule(world.get_location('Eastern Palace - Prize'), lambda state: state.can_shoot_arrows() and state.has('Big Key (Eastern Palace)'))
for location in ['Eastern Palace - Armos Knights', 'Eastern Palace - Big Chest']: for location in ['Eastern Palace - Armos Knights', 'Eastern Palace - Big Chest']:
forbid_item(world.get_location(location), 'Big Key (Eastern Palace)') forbid_item(world.get_location(location), 'Big Key (Eastern Palace)')
set_rule(world.get_location('Desert Palace - Big Chest'), lambda state: state.has('Big Key (Desert Palace)')) set_rule(world.get_location('Desert Palace - Big Chest'), lambda state: state.has('Big Key (Desert Palace)'))
set_rule(world.get_location('Desert Palace - Torch'), lambda state: state.has_Boots()) set_rule(world.get_location('Desert Palace - Torch'), lambda state: state.has_Boots())
set_rule(world.get_entrance('Desert Palace East Wing'), lambda state: state.has('Small Key (Desert Palace)')) set_rule(world.get_entrance('Desert Palace East Wing'), lambda state: state.has_key('Small Key (Desert Palace)'))
set_rule(world.get_location('Desert Palace - Prize'), lambda state: state.has('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and set_rule(world.get_location('Desert Palace - Prize'), lambda state: state.has_key('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow'))) (state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.can_shoot_arrows()))
set_rule(world.get_location('Desert Palace - Lanmolas'), lambda state: state.has('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and set_rule(world.get_location('Desert Palace - Lanmolas'), lambda state: state.has_key('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow'))) (state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.can_shoot_arrows()))
for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Chest']: for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Chest']:
forbid_item(world.get_location(location), 'Big Key (Desert Palace)') forbid_item(world.get_location(location), 'Big Key (Desert Palace)')
for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Key Chest', 'Desert Palace - Compass Chest']: for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Key Chest', 'Desert Palace - Compass Chest']:
forbid_item(world.get_location(location), 'Small Key (Desert Palace)') forbid_item(world.get_location(location), 'Small Key (Desert Palace)')
set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.has('Small Key (Tower of Hera)') or item_name(state, 'Tower of Hera - Big Key Chest') == 'Small Key (Tower of Hera)') set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.has_key('Small Key (Tower of Hera)') or item_name(state, 'Tower of Hera - Big Key Chest') == 'Small Key (Tower of Hera)')
set_rule(world.get_entrance('Tower of Hera Big Key Door'), lambda state: state.has('Big Key (Tower of Hera)')) set_rule(world.get_entrance('Tower of Hera Big Key Door'), lambda state: state.has('Big Key (Tower of Hera)'))
set_rule(world.get_location('Tower of Hera - Big Chest'), lambda state: state.has('Big Key (Tower of Hera)')) set_rule(world.get_location('Tower of Hera - Big Chest'), lambda state: state.has('Big Key (Tower of Hera)'))
set_rule(world.get_location('Tower of Hera - Big Key Chest'), lambda state: state.has_fire_source()) set_rule(world.get_location('Tower of Hera - Big Key Chest'), lambda state: state.has_fire_source())
@ -243,11 +255,13 @@ def global_rules(world):
set_rule(world.get_location('Tower of Hera - Prize'), lambda state: state.has_blunt_weapon()) set_rule(world.get_location('Tower of Hera - Prize'), lambda state: state.has_blunt_weapon())
for location in ['Tower of Hera - Moldorm', 'Tower of Hera - Big Chest', 'Tower of Hera - Compass Chest']: for location in ['Tower of Hera - Moldorm', 'Tower of Hera - Big Chest', 'Tower of Hera - Compass Chest']:
forbid_item(world.get_location(location), 'Big Key (Tower of Hera)') forbid_item(world.get_location(location), 'Big Key (Tower of Hera)')
for location in ['Tower of Hera - Big Key Chest']: # for location in ['Tower of Hera - Big Key Chest']:
forbid_item(world.get_location(location), 'Small Key (Tower of Hera)') # forbid_item(world.get_location(location), 'Small Key (Tower of Hera)')
set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.can_reach('Dam')) set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.has('Open Floodgate'))
set_rule(world.get_entrance('Swamp Palace Small Key Door'), lambda state: state.has('Small Key (Swamp Palace)')) add_rule(world.get_location('Sunken Treasure'), lambda state: state.has('Open Floodgate'))
set_rule(world.get_entrance('Swamp Palace Small Key Door'), lambda state: state.has_key('Small Key (Swamp Palace)'))
set_rule(world.get_entrance('Swamp Palace (Center)'), lambda state: state.has('Hammer')) set_rule(world.get_entrance('Swamp Palace (Center)'), lambda state: state.has('Hammer'))
set_rule(world.get_location('Swamp Palace - Big Chest'), lambda state: state.has('Big Key (Swamp Palace)') or item_name(state, 'Swamp Palace - Big Chest') == 'Big Key (Swamp Palace)') set_rule(world.get_location('Swamp Palace - Big Chest'), lambda state: state.has('Big Key (Swamp Palace)') or item_name(state, 'Swamp Palace - Big Chest') == 'Big Key (Swamp Palace)')
set_always_allow(world.get_location('Swamp Palace - Big Chest'), lambda state, item: item.name == 'Big Key (Swamp Palace)') set_always_allow(world.get_location('Swamp Palace - Big Chest'), lambda state, item: item.name == 'Big Key (Swamp Palace)')
@ -258,48 +272,48 @@ def global_rules(world):
forbid_item(world.get_location(location), 'Big Key (Swamp Palace)') forbid_item(world.get_location(location), 'Big Key (Swamp Palace)')
set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.has('Big Key (Thieves Town)')) set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.has('Big Key (Thieves Town)'))
set_rule(world.get_entrance('Blind Fight'), lambda state: state.has('Small Key (Thieves Town)') and (state.has_blunt_weapon() or state.has('Cane of Somaria') or state.has('Cane of Byrna'))) set_rule(world.get_entrance('Blind Fight'), lambda state: state.has_key('Small Key (Thieves Town)') and (state.has_blunt_weapon() or state.has('Cane of Somaria') or state.has('Cane of Byrna')))
set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: (state.has('Small Key (Thieves Town)') or item_name(state, 'Thieves\' Town - Big Chest') == 'Small Key (Thieves Town)') and state.has('Hammer')) set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: (state.has_key('Small Key (Thieves Town)') or item_name(state, 'Thieves\' Town - Big Chest') == 'Small Key (Thieves Town)') and state.has('Hammer'))
set_always_allow(world.get_location('Thieves\' Town - Big Chest'), lambda state, item: item.name == 'Small Key (Thieves Town)' and state.has('Hammer')) set_always_allow(world.get_location('Thieves\' Town - Big Chest'), lambda state, item: item.name == 'Small Key (Thieves Town)' and state.has('Hammer'))
set_rule(world.get_location('Thieves\' Town - Attic'), lambda state: state.has('Small Key (Thieves Town)')) set_rule(world.get_location('Thieves\' Town - Attic'), lambda state: state.has_key('Small Key (Thieves Town)'))
for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves Town - Blind']: for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves Town - Blind']:
forbid_item(world.get_location(location), 'Big Key (Thieves Town)') forbid_item(world.get_location(location), 'Big Key (Thieves Town)')
for location in ['Thieves\' Town - Attic', 'Thieves Town - Blind']: for location in ['Thieves\' Town - Attic', 'Thieves Town - Blind']:
forbid_item(world.get_location(location), 'Small Key (Thieves Town)') forbid_item(world.get_location(location), 'Small Key (Thieves Town)')
set_rule(world.get_entrance('Skull Woods First Section South Door'), lambda state: state.has('Small Key (Skull Woods)')) set_rule(world.get_entrance('Skull Woods First Section South Door'), lambda state: state.has_key('Small Key (Skull Woods)'))
set_rule(world.get_entrance('Skull Woods First Section (Right) North Door'), lambda state: state.has('Small Key (Skull Woods)')) set_rule(world.get_entrance('Skull Woods First Section (Right) North Door'), lambda state: state.has_key('Small Key (Skull Woods)'))
set_rule(world.get_entrance('Skull Woods First Section West Door'), lambda state: state.has('Small Key (Skull Woods)', 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section set_rule(world.get_entrance('Skull Woods First Section West Door'), lambda state: state.has_key('Small Key (Skull Woods)', 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section
set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit'), lambda state: state.has('Small Key (Skull Woods)', 2)) set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit'), lambda state: state.has_key('Small Key (Skull Woods)', 2))
set_rule(world.get_location('Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)') or item_name(state, 'Skull Woods - Big Chest') == 'Big Key (Skull Woods)') set_rule(world.get_location('Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)') or item_name(state, 'Skull Woods - Big Chest') == 'Big Key (Skull Woods)')
set_always_allow(world.get_location('Skull Woods - Big Chest'), lambda state, item: item.name == 'Big Key (Skull Woods)') set_always_allow(world.get_location('Skull Woods - Big Chest'), lambda state, item: item.name == 'Big Key (Skull Woods)')
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and state.has_sword()) # sword required for curtain set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has_key('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and state.has_sword()) # sword required for curtain
for location in ['Skull Woods - Mothula']: for location in ['Skull Woods - Mothula']:
forbid_item(world.get_location(location), 'Small Key (Skull Woods)') forbid_item(world.get_location(location), 'Small Key (Skull Woods)')
set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or (state.has('Bombos') and state.has_sword())) set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or (state.has('Bombos') and state.has_sword()))
set_rule(world.get_location('Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)')) set_rule(world.get_location('Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)'))
set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.has('Big Key (Ice Palace)') and (state.has('Small Key (Ice Palace)', 2) or (state.has('Cane of Somaria') and state.has('Small Key (Ice Palace)', 1)))) set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.has('Big Key (Ice Palace)') and (state.has_key('Small Key (Ice Palace)', 2) or (state.has('Cane of Somaria') and state.has_key('Small Key (Ice Palace)', 1))))
set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has('Small Key (Ice Palace)'))) and (state.world.can_take_damage or state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna'))) set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has_key('Small Key (Ice Palace)'))) and (state.world.can_take_damage or state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna')))
set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer')) set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer'))
for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']: for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']:
forbid_item(world.get_location(location), 'Big Key (Ice Palace)') forbid_item(world.get_location(location), 'Big Key (Ice Palace)')
set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: (state.has_Boots() or state.has('Hookshot')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.has('Bow'))) # need to defeat wizzrobes, bombs don't work ... set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: (state.has_Boots() or state.has('Hookshot')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.can_shoot_arrows())) # need to defeat wizzrobes, bombs don't work ...
set_rule(world.get_location('Misery Mire - Big Chest'), lambda state: state.has('Big Key (Misery Mire)')) set_rule(world.get_location('Misery Mire - Big Chest'), lambda state: state.has('Big Key (Misery Mire)'))
set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: (state.world.can_take_damage and state.has_hearts(4)) or state.has('Cane of Byrna') or state.has('Cape')) set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: (state.world.can_take_damage and state.has_hearts(4)) or state.has('Cane of Byrna') or state.has('Cape'))
set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)')) set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)'))
# you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ... # you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ...
# big key gives backdoor access to that from the teleporter in the north west # big key gives backdoor access to that from the teleporter in the north west
set_rule(world.get_location('Misery Mire - Map Chest'), lambda state: state.has('Small Key (Misery Mire)', 1) or state.has('Big Key (Misery Mire)')) set_rule(world.get_location('Misery Mire - Map Chest'), lambda state: state.has_key('Small Key (Misery Mire)', 1) or state.has('Big Key (Misery Mire)'))
# in addition, you can open the door to the map room before getting access to a color switch, so this is locked behing 2 small keys or the big key... # in addition, you can open the door to the map room before getting access to a color switch, so this is locked behing 2 small keys or the big key...
set_rule(world.get_location('Misery Mire - Main Lobby'), lambda state: state.has('Small Key (Misery Mire)', 2) or state.has('Big Key (Misery Mire)')) set_rule(world.get_location('Misery Mire - Main Lobby'), lambda state: state.has_key('Small Key (Misery Mire)', 2) or state.has_key('Big Key (Misery Mire)'))
# we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet # we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet
set_rule(world.get_entrance('Misery Mire (West)'), lambda state: state.has('Small Key (Misery Mire)', 2) if ((item_name(state, 'Misery Mire - Compass Chest') in ['Big Key (Misery Mire)']) or set_rule(world.get_entrance('Misery Mire (West)'), lambda state: state.has_key('Small Key (Misery Mire)', 2) if ((item_name(state, 'Misery Mire - Compass Chest') in ['Big Key (Misery Mire)']) or
(item_name(state, 'Misery Mire - Big Key Chest') in ['Big Key (Misery Mire)'])) else state.has('Small Key (Misery Mire)', 3)) (item_name(state, 'Misery Mire - Big Key Chest') in ['Big Key (Misery Mire)'])) else state.has_key('Small Key (Misery Mire)', 3))
set_rule(world.get_location('Misery Mire - Compass Chest'), lambda state: state.has_fire_source()) set_rule(world.get_location('Misery Mire - Compass Chest'), lambda state: state.has_fire_source())
set_rule(world.get_location('Misery Mire - Big Key Chest'), lambda state: state.has_fire_source()) set_rule(world.get_location('Misery Mire - Big Key Chest'), lambda state: state.has_fire_source())
set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria') and (state.has('Bow') or state.has_blunt_weapon())) set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria') and (state.can_shoot_arrows() or state.has_blunt_weapon()))
for location in ['Misery Mire - Big Chest', 'Misery Mire - Vitreous']: for location in ['Misery Mire - Big Chest', 'Misery Mire - Vitreous']:
forbid_item(world.get_location(location), 'Big Key (Misery Mire)') forbid_item(world.get_location(location), 'Big Key (Misery Mire)')
@ -311,31 +325,31 @@ def global_rules(world):
set_rule(world.get_location('Turtle Rock - Big Chest'), lambda state: state.has('Big Key (Turtle Rock)') and (state.has('Cane of Somaria') or state.has('Hookshot'))) set_rule(world.get_location('Turtle Rock - Big Chest'), lambda state: state.has('Big Key (Turtle Rock)') and (state.has('Cane of Somaria') or state.has('Hookshot')))
set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)'), lambda state: state.has('Cane of Somaria') or state.has('Hookshot')) set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)'), lambda state: state.has('Cane of Somaria') or state.has('Hookshot'))
set_rule(world.get_entrance('Turtle Rock Big Key Door'), lambda state: state.has('Big Key (Turtle Rock)')) set_rule(world.get_entrance('Turtle Rock Big Key Door'), lambda state: state.has('Big Key (Turtle Rock)'))
set_rule(world.get_entrance('Turtle Rock Dark Room Staircase'), lambda state: state.has('Small Key (Turtle Rock)', 3)) set_rule(world.get_entrance('Turtle Rock Dark Room Staircase'), lambda state: state.has_key('Small Key (Turtle Rock)', 3))
set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)'), lambda state: state.has('Cane of Somaria')) set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)'), lambda state: state.has('Cane of Somaria'))
set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)'), lambda state: state.has('Cane of Somaria')) set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)'), lambda state: state.has('Cane of Somaria'))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield')) set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
set_rule(world.get_entrance('Turtle Rock (Trinexx)'), lambda state: state.has('Small Key (Turtle Rock)', 4) and state.has('Big Key (Turtle Rock)') and state.has('Cane of Somaria') and state.has('Fire Rod') and state.has('Ice Rod') and set_rule(world.get_entrance('Turtle Rock (Trinexx)'), lambda state: state.has_key('Small Key (Turtle Rock)', 4) and state.has('Big Key (Turtle Rock)') and state.has('Cane of Somaria') and state.has('Fire Rod') and state.has('Ice Rod') and
(state.has('Hammer') or state.has_beam_sword() or (state.has_sword() and state.can_extend_magic(32)))) (state.has('Hammer') or state.has_beam_sword() or (state.has_sword() and state.can_extend_magic(32))))
set_trock_key_rules(world) set_trock_key_rules(world)
set_rule(world.get_entrance('Palace of Darkness Bonk Wall'), lambda state: state.has('Bow')) set_rule(world.get_entrance('Palace of Darkness Bonk Wall'), lambda state: state.can_shoot_arrows())
set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop'), lambda state: state.has('Hammer')) set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop'), lambda state: state.has('Hammer'))
set_rule(world.get_entrance('Palace of Darkness Bridge Room'), lambda state: state.has('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area set_rule(world.get_entrance('Palace of Darkness Bridge Room'), lambda state: state.has_key('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area
set_rule(world.get_entrance('Palace of Darkness Big Key Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) and state.has('Big Key (Palace of Darkness)') and state.has('Bow') and state.has('Hammer')) set_rule(world.get_entrance('Palace of Darkness Big Key Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) and state.has('Big Key (Palace of Darkness)') and state.can_shoot_arrows() and state.has('Hammer'))
set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has('Small Key (Palace of Darkness)', 4)) set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has_key('Small Key (Palace of Darkness)', 4))
set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)')) set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)'))
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Big Key Chest') in ['Small Key (Palace of Darkness)'] and state.has('Small Key (Palace of Darkness)', 3))) set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Big Key Chest') in ['Small Key (Palace of Darkness)'] and state.has_key('Small Key (Palace of Darkness)', 3)))
set_always_allow(world.get_location('Palace of Darkness - Big Key Chest'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has('Small Key (Palace of Darkness)', 5)) set_always_allow(world.get_location('Palace of Darkness - Big Key Chest'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has_key('Small Key (Palace of Darkness)', 5))
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Harmless Hellway') in ['Small Key (Palace of Darkness)'] and state.has('Small Key (Palace of Darkness)', 4))) set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Harmless Hellway') in ['Small Key (Palace of Darkness)'] and state.has_key('Small Key (Palace of Darkness)', 4)))
set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has('Small Key (Palace of Darkness)', 5)) set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has_key('Small Key (Palace of Darkness)', 5))
set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6)) set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6))
# these key rules are conservative, you might be able to get away with more lenient rules # these key rules are conservative, you might be able to get away with more lenient rules
randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'] randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right']
@ -345,34 +359,34 @@ def global_rules(world):
set_rule(world.get_entrance('Ganons Tower (Tile Room)'), lambda state: state.has('Cane of Somaria')) set_rule(world.get_entrance('Ganons Tower (Tile Room)'), lambda state: state.has('Cane of Somaria'))
set_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hammer')) set_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hammer'))
set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 4) or (item_name(state, 'Ganons Tower - Map Chest') in ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)'] and state.has('Small Key (Ganons Tower)', 3))) set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has_key('Small Key (Ganons Tower)', 4) or (item_name(state, 'Ganons Tower - Map Chest') in ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)'] and state.has_key('Small Key (Ganons Tower)', 3)))
set_always_allow(world.get_location('Ganons Tower - Map Chest'), lambda state, item: item.name == 'Small Key (Ganons Tower)' and state.has('Small Key (Ganons Tower)', 3)) set_always_allow(world.get_location('Ganons Tower - Map Chest'), lambda state, item: item.name == 'Small Key (Ganons Tower)' and state.has_key('Small Key (Ganons Tower)', 3))
# It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere. We reflect this in the chest requirements. # It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere. We reflect this in the chest requirements.
# However we need to leave these at the lower values to derive that with 3 keys it is always possible to reach Bob and Ice Armos. # However we need to leave these at the lower values to derive that with 3 keys it is always possible to reach Bob and Ice Armos.
set_rule(world.get_entrance('Ganons Tower (Double Switch Room)'), lambda state: state.has('Small Key (Ganons Tower)', 2)) set_rule(world.get_entrance('Ganons Tower (Double Switch Room)'), lambda state: state.has_key('Small Key (Ganons Tower)', 2))
# It is possible to need more than 3 keys .... # It is possible to need more than 3 keys ....
set_rule(world.get_entrance('Ganons Tower (Firesnake Room)'), lambda state: state.has('Small Key (Ganons Tower)', 3)) set_rule(world.get_entrance('Ganons Tower (Firesnake Room)'), lambda state: state.has_key('Small Key (Ganons Tower)', 3))
#The actual requirements for these rooms to avoid key-lock #The actual requirements for these rooms to avoid key-lock
set_rule(world.get_location('Ganons Tower - Firesnake Room'), lambda state: state.has('Small Key (Ganons Tower)', 3) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has('Small Key (Ganons Tower)', 2))) set_rule(world.get_location('Ganons Tower - Firesnake Room'), lambda state: state.has_key('Small Key (Ganons Tower)', 3) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has_key('Small Key (Ganons Tower)', 2)))
for location in randomizer_room_chests: for location in randomizer_room_chests:
set_rule(world.get_location(location), lambda state: state.has('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has('Small Key (Ganons Tower)', 3))) set_rule(world.get_location(location), lambda state: state.has_key('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has_key('Small Key (Ganons Tower)', 3)))
# Once again it is possible to need more than 3 keys... # Once again it is possible to need more than 3 keys...
set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door'), lambda state: state.has('Small Key (Ganons Tower)', 3) and state.has('Fire Rod')) set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door'), lambda state: state.has_key('Small Key (Ganons Tower)', 3) and state.has('Fire Rod'))
# Actual requirements # Actual requirements
for location in compass_room_chests: for location in compass_room_chests:
set_rule(world.get_location(location), lambda state: state.has('Fire Rod') and (state.has('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', compass_room_chests) and state.has('Small Key (Ganons Tower)', 3)))) set_rule(world.get_location(location), lambda state: state.has('Fire Rod') and (state.has_key('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', compass_room_chests) and state.has_key('Small Key (Ganons Tower)', 3))))
set_rule(world.get_location('Ganons Tower - Big Chest'), lambda state: state.has('Big Key (Ganons Tower)')) set_rule(world.get_location('Ganons Tower - Big Chest'), lambda state: state.has('Big Key (Ganons Tower)'))
set_rule(world.get_location('Ganons Tower - Big Key Room - Left'), lambda state: state.has('Bow') or state.has_blunt_weapon()) set_rule(world.get_location('Ganons Tower - Big Key Room - Left'), lambda state: state.can_shoot_arrows() or state.has_blunt_weapon())
set_rule(world.get_location('Ganons Tower - Big Key Chest'), lambda state: state.has('Bow') or state.has_blunt_weapon()) set_rule(world.get_location('Ganons Tower - Big Key Chest'), lambda state: state.can_shoot_arrows() or state.has_blunt_weapon())
set_rule(world.get_location('Ganons Tower - Big Key Room - Right'), lambda state: state.has('Bow') or state.has_blunt_weapon()) set_rule(world.get_location('Ganons Tower - Big Key Room - Right'), lambda state: state.can_shoot_arrows() or state.has_blunt_weapon())
set_rule(world.get_entrance('Ganons Tower Big Key Door'), lambda state: state.has('Big Key (Ganons Tower)') and state.has('Bow')) set_rule(world.get_entrance('Ganons Tower Big Key Door'), lambda state: state.has('Big Key (Ganons Tower)') and state.can_shoot_arrows())
set_rule(world.get_entrance('Ganons Tower Torch Rooms'), lambda state: state.has_fire_source()) set_rule(world.get_entrance('Ganons Tower Torch Rooms'), lambda state: state.has_fire_source())
set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest'), lambda state: state.has('Small Key (Ganons Tower)', 3)) set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest'), lambda state: state.has_key('Small Key (Ganons Tower)', 3))
set_rule(world.get_entrance('Ganons Tower Moldorm Door'), lambda state: state.has('Small Key (Ganons Tower)', 4)) set_rule(world.get_entrance('Ganons Tower Moldorm Door'), lambda state: state.has_key('Small Key (Ganons Tower)', 4))
set_rule(world.get_entrance('Ganons Tower Moldorm Gap'), lambda state: state.has('Hookshot') and state.has_blunt_weapon()) set_rule(world.get_entrance('Ganons Tower Moldorm Gap'), lambda state: state.has('Hookshot') and state.has_blunt_weapon())
set_rule(world.get_location('Agahnim 2'), lambda state: state.has_sword() or state.has('Hammer') or state.has('Bug Catching Net')) set_rule(world.get_location('Agahnim 2'), lambda state: state.has_sword() or state.has('Hammer') or state.has('Bug Catching Net'))
set_rule(world.get_entrance('Pyramid Hole'), lambda state: state.has('Beat Agahnim 2')) set_rule(world.get_entrance('Pyramid Hole'), lambda state: state.has('Beat Agahnim 2'))
@ -382,7 +396,7 @@ def global_rules(world):
set_rule(world.get_location('Ganon'), lambda state: state.has_beam_sword() and state.has_fire_source() and state.has('Crystal 1') and state.has('Crystal 2') set_rule(world.get_location('Ganon'), lambda state: state.has_beam_sword() and state.has_fire_source() and state.has('Crystal 1') and state.has('Crystal 2')
and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7') and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7')
and (state.has('Tempered Sword') or state.has('Golden Sword') or (state.has('Silver Arrows') and state.has('Bow')) or state.has('Lamp') or state.can_extend_magic(12))) # need to light torch a sufficient amount of times and (state.has('Tempered Sword') or state.has('Golden Sword') or (state.has('Silver Arrows') and state.can_shoot_arrows()) or state.has('Lamp') or state.can_extend_magic(12))) # need to light torch a sufficient amount of times
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has_beam_sword()) # need to damage ganon to get tiles to drop set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has_beam_sword()) # need to damage ganon to get tiles to drop
@ -447,8 +461,8 @@ def open_rules(world):
forbid_item(world.get_location('Hyrule Castle - Boomerang Chest'), 'Small Key (Escape)') forbid_item(world.get_location('Hyrule Castle - Boomerang Chest'), 'Small Key (Escape)')
forbid_item(world.get_location('Hyrule Castle - Zelda\'s Chest'), 'Small Key (Escape)') forbid_item(world.get_location('Hyrule Castle - Zelda\'s Chest'), 'Small Key (Escape)')
set_rule(world.get_location('Hyrule Castle - Boomerang Chest'), lambda state: state.has('Small Key (Escape)')) set_rule(world.get_location('Hyrule Castle - Boomerang Chest'), lambda state: state.has_key('Small Key (Escape)'))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest'), lambda state: state.has('Small Key (Escape)')) set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest'), lambda state: state.has_key('Small Key (Escape)'))
def swordless_rules(world): def swordless_rules(world):
@ -459,15 +473,15 @@ def swordless_rules(world):
open_rules(world) open_rules(world)
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has('Hammer') or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has('Hammer') or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
set_rule(world.get_entrance('Agahnim 1'), lambda state: (state.has('Hammer') or (state.has('Bug Catching Net') and (state.has('Fire Rod') or state.has('Bow') or state.has('Cane of Somaria')))) and state.has('Small Key (Agahnims Tower)', 2)) set_rule(world.get_entrance('Agahnim 1'), lambda state: (state.has('Hammer') or (state.has('Bug Catching Net') and (state.has('Fire Rod') or state.can_shoot_arrows() or state.has('Cane of Somaria')))) and state.has_key('Small Key (Agahnims Tower)', 2))
set_rule(world.get_location('Ether Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer')) set_rule(world.get_location('Ether Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer'))
set_rule(world.get_location('Bombos Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer') and state.has_Mirror()) set_rule(world.get_location('Bombos Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer') and state.has_Mirror())
set_rule(world.get_entrance('Misery Mire'), lambda state: state.has_Pearl() and state.has_misery_mire_medallion()) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Misery Mire'), lambda state: state.has_Pearl() and state.has_misery_mire_medallion()) # sword not required to use medallion for opening in swordless (!)
set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword not required to use medallion for opening in swordless (!) set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword not required to use medallion for opening in swordless (!)
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has('Small Key (Skull Woods)', 3) and state.has('Fire Rod')) # no curtain set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has_key('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and (state.has('Hammer') or state.can_extend_magic(10))) # no curtain
set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos')) #in swordless mode bombos pads are present in the relevant parts of ice palace set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos')) #in swordless mode bombos pads are present in the relevant parts of ice palace
set_rule(world.get_location('Agahnim 2'), lambda state: state.has('Hammer') or state.has('Bug Catching Net')) set_rule(world.get_location('Agahnim 2'), lambda state: state.has('Hammer') or state.has('Bug Catching Net'))
set_rule(world.get_location('Ganon'), lambda state: state.has('Hammer') and state.has_fire_source() and state.has('Silver Arrows') and state.has('Bow') and state.has('Crystal 1') and state.has('Crystal 2') set_rule(world.get_location('Ganon'), lambda state: state.has('Hammer') and state.has_fire_source() and state.has('Silver Arrows') and state.can_shoot_arrows() and state.has('Crystal 1') and state.has('Crystal 2')
and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7')) and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7'))
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has('Hammer')) # need to damage ganon to get tiles to drop set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has('Hammer')) # need to damage ganon to get tiles to drop
@ -475,7 +489,7 @@ def swordless_rules(world):
def standard_rules(world): def standard_rules(world):
for loc in ['Sanctuary','Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle', for loc in ['Sanctuary','Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
'Sewers - Secret Room - Right']: 'Sewers - Secret Room - Right']:
add_rule(world.get_location(loc), lambda state: state.can_kill_most_things() and state.has('Small Key (Escape)')) add_rule(world.get_location(loc), lambda state: state.can_kill_most_things() and state.has_key('Small Key (Escape)'))
# easiest way to enforce key placement not relevant for open # easiest way to enforce key placement not relevant for open
set_rule(world.get_location('Sewers - Dark Cross'), lambda state: state.can_kill_most_things()) set_rule(world.get_location('Sewers - Dark Cross'), lambda state: state.can_kill_most_things())
@ -500,18 +514,18 @@ def set_trock_key_rules(world):
# if we have backdoor access we can waste a key on the trinexx door, then have no lamp to reverse traverse the maze room. We simply require an additional key just to be super safe then. The backdoor access to the chest is otherwise free # if we have backdoor access we can waste a key on the trinexx door, then have no lamp to reverse traverse the maze room. We simply require an additional key just to be super safe then. The backdoor access to the chest is otherwise free
if not can_reach_back: if not can_reach_back:
set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 1)) set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has_key('Small Key (Turtle Rock)', 1))
else: else:
set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 2)) set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has_key('Small Key (Turtle Rock)', 2))
# if we have front access this transition is useless. If we don't, it's a dead end so cannot hold any small keys # if we have front access this transition is useless. If we don't, it's a dead end so cannot hold any small keys
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)'), lambda state: state.has('Small Key (Turtle Rock)', 4)) set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)'), lambda state: state.has_key('Small Key (Turtle Rock)', 4))
# this is just the pokey room with one more key # this is just the pokey room with one more key
if not can_reach_back: if not can_reach_back:
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 2)) set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has_key('Small Key (Turtle Rock)', 2))
else: else:
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 3)) set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has_key('Small Key (Turtle Rock)', 3))
# the most complicated one # the most complicated one
def tr_big_key_chest_keys_needed(state): def tr_big_key_chest_keys_needed(state):
@ -527,8 +541,8 @@ def set_trock_key_rules(world):
# otherwise we could potentially have opened every other door already, so we need all 4 keys. # otherwise we could potentially have opened every other door already, so we need all 4 keys.
return 4 return 4
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', tr_big_key_chest_keys_needed(state))) set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has_key('Small Key (Turtle Rock)', tr_big_key_chest_keys_needed(state)))
set_always_allow(world.get_location('Turtle Rock - Big Key Chest'), lambda state, item: item.name == 'Small Key (Turtle Rock)' and state.has('Small Key (Turtle Rock)', 3)) set_always_allow(world.get_location('Turtle Rock - Big Key Chest'), lambda state, item: item.name == 'Small Key (Turtle Rock)' and state.has_key('Small Key (Turtle Rock)', 3))
# set big key restrictions # set big key restrictions
non_big_key_locations = ['Turtle Rock - Big Chest', 'Turtle Rock - Trinexx'] non_big_key_locations = ['Turtle Rock - Big Chest', 'Turtle Rock - Trinexx']
@ -780,13 +794,8 @@ def set_bunny_rules(world):
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)', bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)',
'Turtle Rock (Eye Bridge)', 'Sewers', 'Pyramid', 'Spiral Cave (Top)', 'Desert Palace Main (Inner)'] 'Turtle Rock (Eye Bridge)', 'Sewers', 'Pyramid', 'Spiral Cave (Top)', 'Desert Palace Main (Inner)']
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge'] bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins']
if world.get_region('Dam').is_dark_world:
# if Dam is is dark world, then it is required to have the pearl to get the sunken item
add_rule(world.get_location('Sunken Treasure'), lambda state: state.has_Pearl())
# similarly we need perl to get across the swamp palace moat
add_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has_Pearl())
def path_to_access_rule(path, entrance): def path_to_access_rule(path, entrance):
return lambda state: state.can_reach(entrance) and all(rule(state) for rule in path) return lambda state: state.can_reach(entrance) and all(rule(state) for rule in path)
@ -805,7 +814,7 @@ def set_bunny_rules(world):
# We will search entrances recursively until we find # We will search entrances recursively until we find
# one that leads to an exclusively light world region # one that leads to an exclusively light world region
# for each such entrance a new option ios aded that consist of: # for each such entrance a new option is added that consist of:
# a) being able to reach it, and # a) being able to reach it, and
# b) being able to access all entrances from there to `region` # b) being able to access all entrances from there to `region`
seen = set([region]) seen = set([region])
@ -836,6 +845,10 @@ def set_bunny_rules(world):
for exit in region.exits: for exit in region.exits:
add_rule(exit, rule) add_rule(exit, rule)
paradox_shop = world.get_region('Light World Death Mountain Shop')
if paradox_shop.is_dark_world:
add_rule(paradox_shop.entrances[0], get_rule_to_add(paradox_shop))
# Add requirements for all locations that are actually in the dark world, except those available to the bunny # Add requirements for all locations that are actually in the dark world, except those available to the bunny
for location in world.get_locations(): for location in world.get_locations():
if location.parent_region.is_dark_world: if location.parent_region.is_dark_world:

924
Text.py
View File

@ -1,3 +1,4 @@
# -*- coding: UTF-8 -*-
text_addresses = {'Pedestal': (0x180300, 256), text_addresses = {'Pedestal': (0x180300, 256),
'Triforce': (0x180400, 256), 'Triforce': (0x180400, 256),
'Uncle': (0x180500, 256), 'Uncle': (0x180500, 256),
@ -137,10 +138,13 @@ Ganon1_texts = [
"Did you know?\nThe biggest\nand heaviest\ncheese ever\nproduced\nweighed\n57,518 pounds\nand was 32\nfeet long.", "Did you know?\nThe biggest\nand heaviest\ncheese ever\nproduced\nweighed\n57,518 pounds\nand was 32\nfeet long.",
"Now there was\na time, When\nyou loved me\nso. I couldn't\ndo wrong,\nAnd now you\nneed to know.\nSo How you\nlike me now?", "Now there was\na time, When\nyou loved me\nso. I couldn't\ndo wrong,\nAnd now you\nneed to know.\nSo How you\nlike me now?",
"Did you know?\nNutrition\nexperts\nrecommend that\nat least half\nof our daily\ngrains come\nfrom whole\ngrain products", "Did you know?\nNutrition\nexperts\nrecommend that\nat least half\nof our daily\ngrains come\nfrom whole\ngrain products",
"The Hemiptera\nor true bugs\nare an order\nof insects\ncovering 50k\nto 80k species\nlike aphids,\ncicadas, and\nshield bugs.",
"Thanks for\ndropping in,\nthe first\npassengers\nin a hot\nair balloon.\nwere a duck,\na sheep,\nand a rooster.",
"You think you\nare so smart?\n\nI bet you\ndidn't know\nYou can't hum\nwhile holding\nyour nose\nclosed.",
"grumble,\n\ngrumble…\ngrumble,\n\ngrumble…\nSeriously you\nwere supposed\nto bring food",
"Join me hero,\nand I shall\nmake your face\nthe greatest\nin the dark\nworld!\n\nOr else you\nwill die!",
] ]
TavernMan_texts = [ TavernMan_texts = [
'Did you know that talking to random NPCs wastes time in a race? I hope this information may be of use to you in the future.'
] + [
"What do you\ncall a blind\ndinosaur?\nadoyouthink-\nhesaurus\n", "What do you\ncall a blind\ndinosaur?\nadoyouthink-\nhesaurus\n",
"A blind man\nwalks into\na bar.\nAnd a table.\nAnd a chair.\n", "A blind man\nwalks into\na bar.\nAnd a table.\nAnd a chair.\n",
"What do ducks\nlike to eat?\n\nQuackers!\n", "What do ducks\nlike to eat?\n\nQuackers!\n",
@ -433,249 +437,183 @@ class SceneLargeCreditLine(SceneCreditLine):
buf += LargeCreditBottomMapper.convert(self.text) buf += LargeCreditBottomMapper.convert(self.text)
return buf return buf
class MultiByteTextMapper(object):
@classmethod
def convert(cls, text, maxbytes=256):
outbuf = MultiByteCoreTextMapper.convert(text)
def string_to_alttp_text(s, maxbytes=256): # check for max length
lines = s.upper().split('\n') if len(outbuf) > maxbytes - 2:
outbuf = bytearray() outbuf = outbuf[:maxbytes - 2]
lineindex = 0 # Note: this could crash if the last byte is part of a two byte command
# depedning on how well the command handles a value of 0x7F.
# Should probably do something about this.
while lines: outbuf.append(0x7F)
linespace = 14 outbuf.append(0x7F)
line = lines.pop(0) return outbuf
words = line.split(' ')
outbuf.append(0x74 if lineindex == 0 else 0x75 if lineindex == 1 else 0x76) # line starter
while words:
word = words.pop(0)
# sanity check: if the word we have is more than 14 characters, we take as much as we can still fit and push the rest back for later
if len(word) > 14:
if linespace < 14:
word = ' ' + word
word_first = word[:linespace]
words.insert(0, word[linespace:])
lines.insert(0, ' '.join(words))
write_word(outbuf, word_first) class MultiByteCoreTextMapper(object):
special_commands = {
"{SPEED0}": [0x7A, 0x00],
"{SPEED2}": [0x7A, 0x02],
"{SPEED6}": [0x7A, 0x06],
"{PAUSE1}": [0x78, 0x01],
"{PAUSE3}": [0x78, 0x03],
"{PAUSE5}": [0x78, 0x05],
"{PAUSE7}": [0x78, 0x07],
"{PAUSE9}": [0x78, 0x09],
"{INPUT}": [0x7E],
"{CHOICE}": [0x68],
"{ITEMSELECT}": [0x69],
"{CHOICE2}": [0x71],
"{CHOICE3}": [0x72],
"{HARP}": [0x79, 0x2D],
"{MENU}": [0x6D, 0x00],
"{BOTTOM}": [0x6D, 0x00],
"{NOBORDER}": [0x6B, 0x02],
"{CHANGEPIC}": [0x67, 0x67],
"{CHANGEMUSIC}": [0x67],
"{INTRO}": [0x6E, 0x00, 0x77, 0x07, 0x7A, 0x03, 0x6B, 0x02, 0x67],
"{NOTEXT}": [0x6E, 0x00, 0x6B, 0x04],
"{IBOX}": [0x6B, 0x02, 0x77, 0x07, 0x7A, 0x03],
}
@classmethod
def convert(cls, text, pause=True, wrap=14):
text = text.upper()
lines = text.split('\n')
outbuf = bytearray()
lineindex = 0
is_intro = '{INTRO}' in text
while lines:
linespace = wrap
line = lines.pop(0)
if line.startswith('{'):
outbuf.extend(cls.special_commands[line])
continue
words = line.split(' ')
outbuf.append(0x74 if lineindex == 0 else 0x75 if lineindex == 1 else 0x76) # line starter
while words:
word = words.pop(0)
# sanity check: if the word we have is more than 14 characters, we take as much as we can still fit and push the rest back for later
if cls.wordlen(word) > wrap:
if linespace < wrap:
word = ' ' + word
(word_first, word_rest) = cls.splitword(word, linespace)
words.insert(0, word_rest)
lines.insert(0, ' '.join(words))
outbuf.extend(RawMBTextMapper.convert(word_first))
break
if cls.wordlen(word) <= (linespace if linespace == wrap else linespace - 1):
if linespace < wrap:
word = ' ' + word
linespace -= cls.wordlen(word)
outbuf.extend(RawMBTextMapper.convert(word))
else:
# ran out of space, push word and lines back and continue with next line
words.insert(0, word)
lines.insert(0, ' '.join(words))
break
if is_intro and lineindex < 3:
outbuf.extend([0xFF]*linespace)
has_more_lines = len(lines) > 1 or (lines and not lines[0].startswith('{'))
lineindex += 1
if pause and lineindex % 3 == 0 and has_more_lines:
outbuf.append(0x7E)
if lineindex >= 3 and has_more_lines:
outbuf.append(0x73)
return outbuf
@classmethod
def wordlen(cls, word):
l = 0
offset = 0
while offset < len(word):
c_len, offset = cls.charlen(word, offset)
l += c_len
return l
@classmethod
def splitword(cls, word, length):
l = 0
offset = 0
while True:
c_len, new_offset = cls.charlen(word, offset)
if l+c_len > length:
break break
l += c_len
offset = new_offset
return (word[0:offset], word[offset:])
if len(word) <= (linespace if linespace == 14 else linespace - 1): @classmethod
if linespace < 14: def charlen(cls, word, offset):
word = ' ' + word c = word[offset]
linespace -= len(word) if c in ['>', '¼', '½', '']:
write_word(outbuf, word) return (2, offset+1)
if c in ['@']:
return (4, offset+1)
if c in ['', '', '', '']:
return (2, offset+1)
return (1, offset+1)
class CompressedTextMapper(object):
two_byte_commands = [
0x6B, 0x6C, 0x6D, 0x6E,
0x77, 0x78, 0x79, 0x7A
]
specially_coded_commands = {
0x73: 0xF6,
0x74: 0xF7,
0x75: 0xF8,
0x76: 0xF9,
0x7E: 0xFA,
0x7A: 0xFC,
}
@classmethod
def convert(cls, text, pause=True, max_bytes_expanded=0x800, wrap=14):
inbuf = MultiByteCoreTextMapper.convert(text, pause, wrap)
# Links name will need 8 bytes in the target buffer
# and two will be used by the terminator
# (Variables will use 2 bytes, but they start as 2 bytes)
bufsize = len(inbuf) + 7 * inbuf.count(0x6A) + 2
if bufsize > max_bytes_expanded:
raise ValueError("Uncompressed string too long for buffer")
inbuf.reverse()
outbuf = bytearray()
outbuf.append(0xfb) # terminator for previous record
while inbuf:
val = inbuf.pop()
if val == 0xFF:
outbuf.append(val)
elif val == 0x00:
outbuf.append(inbuf.pop())
elif val == 0x01: #kanji
outbuf.append(0xFD)
outbuf.append(inbuf.pop())
elif val >= 0x67:
if val in cls.specially_coded_commands:
outbuf.append(cls.specially_coded_commands[val])
else:
outbuf.append(0xFE)
outbuf.append(val)
if val in cls.two_byte_commands:
outbuf.append(inbuf.pop())
else: else:
# ran out of space, push word and lines back and continue with next line raise ValueError("Unexpected byte found in uncompressed string")
words.insert(0, word) return outbuf
lines.insert(0, ' '.join(words))
break
lineindex += 1 class CharTextMapper(object):
if lineindex % 3 == 0 and lines:
outbuf.append(0x7E)
if lineindex >= 3 and lines:
outbuf.append(0x73)
# check for max length
if len(outbuf) > maxbytes - 1:
outbuf = outbuf[:maxbytes - 1]
# make sure we interpret the end of box character
if outbuf[-1] == 0x00:
outbuf[-1] = 0x73
outbuf.append(0x7F)
return outbuf
def write_word(buf, word):
for char in word:
buf.extend([0x00, char_to_alttp_char(char)])
char_map = {' ': 0xFF,
'?': 0xC6,
'!': 0xC7,
',': 0xC8,
'-': 0xC9,
'': 0xCC,
'.': 0xCD,
'~': 0xCE,
'': 0xCE,
"'": 0xD8,
'': 0xD8,
'': 0xE0,
'': 0xE1,
'': 0xE2,
'': 0xE3,
'': 0x00,
'': 0x01,
'': 0x02,
'': 0x03,
'': 0x04,
'': 0x05,
'': 0x06,
'': 0x07,
'': 0x08,
'': 0x09,
'': 0x0A,
'': 0x0B,
'': 0x0C,
'': 0x0D,
'': 0x0E,
'': 0x0F,
'': 0x10,
'': 0x11,
'': 0x12,
'': 0x13,
'': 0x14,
'': 0x15,
'': 0x16,
'': 0x17,
'': 0x18,
'': 0x19,
'': 0x1A,
'': 0x1B,
'': 0x1C,
'': 0x1D,
'': 0x1E,
'': 0x1F,
'': 0x20,
'': 0x21,
'': 0x22,
'': 0x23,
'': 0x24,
'': 0x25,
'': 0x26,
'': 0x27,
'': 0x28,
'': 0x29,
'': 0x2A,
'': 0x2B,
'': 0x2C,
'': 0x2D,
'': 0x2E,
'': 0x2F,
'': 0x30,
'': 0x31,
'': 0x32,
'': 0x33,
'': 0x34,
'': 0x35,
'': 0x36,
'': 0x37,
'': 0x38,
'': 0x39,
'': 0x3A,
'': 0x3B,
'': 0x3C,
'': 0x3D,
'': 0x3E,
'': 0x3F,
'': 0x40,
'': 0x41,
'': 0x42,
'': 0x43,
'': 0x44,
'': 0x45,
'': 0x46,
'': 0x47,
'': 0x48,
'': 0x49,
'': 0x4A,
'': 0x4B,
'': 0x4C,
'': 0x4D,
'': 0x4E,
'': 0x4F,
'': 0x50,
'': 0x51,
'': 0x52,
'': 0x53,
'': 0x54,
'': 0x55,
'': 0x56,
'': 0x57,
'': 0x58,
'': 0x59,
'': 0x5A,
'': 0x5B,
'': 0x5C,
'': 0x5D,
'': 0x5E,
'': 0x5F,
'': 0x60,
'': 0x61,
'': 0x62,
'': 0x63,
'': 0x64,
'': 0x65,
'': 0x66,
'': 0x67,
'': 0x68,
'': 0x69,
'': 0x6A,
'': 0x6B,
'': 0x6C,
'': 0x6D,
'': 0x6E,
'': 0x6F,
'': 0x70,
'': 0x71,
'': 0x72,
'': 0x73,
'': 0x74,
'': 0x75,
'': 0x76,
'': 0x77,
'': 0x78,
'': 0x79,
'': 0x7A,
'': 0x7B,
'': 0x7C,
'': 0x7D,
'': 0x7E,
'': 0x80,
'': 0x81,
'': 0x82,
'': 0x83,
'': 0x84,
'': 0x85,
'': 0x86,
'': 0x87,
'': 0x88,
'': 0x89,
'': 0x8A,
'': 0x8B,
'': 0x8C,
'': 0x8D,
'': 0x8E,
'': 0x8F,
'': 0x90,
'': 0x91,
'': 0x92,
'': 0x93,
'': 0x94,
'': 0x95,
'': 0x96,
'': 0x97,
'': 0x98,
'': 0x99,
'': 0x9A,
'': 0x9B,
'': 0x9C,
'': 0x9D,
'': 0x9E,
'': 0x9F}
def char_to_alttp_char(char):
if 0x30 <= ord(char) <= 0x39:
return ord(char) + 0x70
if 0x41 <= ord(char) <= 0x5A:
return ord(char) + 0x69
return char_map.get(char, 0xFF)
class TextMapper(object):
number_offset = None number_offset = None
alpha_offset = 0 alpha_offset = 0
char_map = {} char_map = {}
@ -695,44 +633,532 @@ class TextMapper(object):
buf.append(cls.map_char(char)) buf.append(cls.map_char(char))
return buf return buf
class RawMBTextMapper(CharTextMapper):
char_map = {' ': 0xFF,
'': 0xC4,
'': 0xC5,
'?': 0xC6,
'!': 0xC7,
',': 0xC8,
'-': 0xC9,
"🡄": 0xCA,
"🡆": 0xCB,
'': 0xCC,
'.': 0xCD,
'~': 0xCE,
'': 0xCE,
'@': [0x6A], # Links name (only works if compressed)
'>': [0x00, 0xD2, 0x00, 0xD3], # Link's face
"'": 0xD8,
'': 0xD8,
'%': 0xDD, # Hylian Bird
'^': 0xDE, # Hylian Ankh
'=': 0xDF, # Hylian Wavy Lines
'': 0xE0,
'': 0xE1,
'': 0xE2,
'': 0xE3,
'': 0xE4, # Cursor
'¼': [0x00, 0xE5, 0x00, 0xE7], # ¼ heart
'½': [0x00, 0xE6, 0x00, 0xE7], # ½ heart
'¾': [0x00, 0xE8, 0x00, 0xE9], # ¾ heart
'': [0x00, 0xEA, 0x00, 0xEB], # full heart
'': [0x6C, 0x00], # var 0
'': [0x6C, 0x01], # var 1
'': [0x6C, 0x02], # var 2
'': [0x6C, 0x03], # var 3
'': 0x00,
'': 0x01,
'': 0x02,
'': 0x03,
'': 0x04,
'': 0x05,
'': 0x06,
'': 0x07,
'': 0x08,
'': 0x09,
'': 0x0A,
'': 0x0B,
'': 0x0C,
'': 0x0D,
'': 0x0E,
'': 0x0F,
'': 0x10,
'': 0x11,
'': 0x12,
'': 0x13,
'': 0x14,
'': 0x15,
'': 0x16,
'': 0x17,
'': 0x18,
'': 0x19,
'': 0x1A,
'': 0x1B,
'': 0x1C,
'': 0x1D,
'': 0x1E,
'': 0x1F,
'': 0x20,
'': 0x21,
'': 0x22,
'': 0x23,
'': 0x24,
'': 0x25,
'': 0x26,
'': 0x27,
'': 0x28,
'': 0x29,
'': 0x2A,
'': 0x2B,
'': 0x2C,
'': 0x2D,
'': 0x2E,
'': 0x2F,
'': 0x30,
'': 0x31,
'': 0x32,
'': 0x33,
'': 0x34,
'': 0x35,
'': 0x36,
'': 0x37,
'': 0x38,
'': 0x39,
'': 0x3A,
'': 0x3B,
'': 0x3C,
'': 0x3D,
'': 0x3E,
'': 0x3F,
'': 0x40,
'': 0x41,
'': 0x42,
'': 0x43,
'': 0x44,
'': 0x45,
'': 0x46,
'': 0x47,
'': 0x48,
'': 0x49,
'': 0x4A,
'': 0x4B,
'': 0x4C,
'': 0x4D,
'': 0x4E,
'': 0x4F,
'': 0x50,
'': 0x51,
'': 0x52,
'': 0x53,
'': 0x54,
'': 0x55,
'': 0x56,
'': 0x57,
'': 0x58,
'': 0x59,
'': 0x5A,
'': 0x5B,
'': 0x5C,
'': 0x5D,
'': 0x5E,
'': 0x5F,
'': 0x60,
'': 0x61,
'': 0x62,
'': 0x63,
'': 0x64,
'': 0x65,
'': 0x66,
'': 0x67,
'': 0x68,
'': 0x69,
'': 0x6A,
'': 0x6B,
'': 0x6C,
'': 0x6D,
'': 0x6E,
'': 0x6F,
'': 0x70,
'': 0x71,
'': 0x72,
'': 0x73,
'': 0x74,
'': 0x75,
'': 0x76,
'': 0x77,
'': 0x78,
'': 0x79,
'': 0x7A,
'': 0x7B,
'': 0x7C,
'': 0x7D,
'': 0x7E,
'': 0x80,
'': 0x81,
'': 0x82,
'': 0x83,
'': 0x84,
'': 0x85,
'': 0x86,
'': 0x87,
'': 0x88,
'': 0x89,
'': 0x8A,
'': 0x8B,
'': 0x8C,
'': 0x8D,
'': 0x8E,
'': 0x8F,
'': 0x90,
'': 0x91,
'': 0x92,
'': 0x93,
'': 0x94,
'': 0x95,
'': 0x96,
'': 0x97,
'': 0x98,
'': 0x99,
'': 0x9A,
'': 0x9B,
'': 0x9C,
'': 0x9D,
'': 0x9E,
'': 0x9F}
class GoldCreditMapper(TextMapper): kanji = {"": 0x00,
"": 0x01,
"": 0x02,
"": 0x03,
"": 0x04,
"": 0x05,
"": 0x06,
"": 0x07,
"": 0x08,
"": 0x09,
"": 0x0A,
"": 0x0B,
"": 0x0C,
"": 0x0D,
"": 0x0E,
"": 0x0F,
"": 0x10,
"": 0x11,
"": 0x12,
"": 0x13,
"": 0x14,
"": 0x15,
"": 0x16,
"": 0x17,
"": 0x18,
"": 0x19,
"": 0x1A,
"": 0x1B,
"": 0x1C,
"": 0x1D,
"": 0x1E,
"": 0x1F,
"": 0x20,
"": 0x21,
"": 0x22,
"": 0x23,
"": 0x24,
"": 0x25,
"": 0x26,
"": 0x27,
"": 0x28,
"": 0x29,
"": 0x2A,
"": 0x2B,
"退": 0x2C,
"": 0x2D,
"": 0x2E,
"": 0x2F,
"": 0x30,
"": 0x31,
"": 0x32,
"": 0x33,
"": 0x34,
"": 0x35,
"": 0x36,
"": 0x37,
"": 0x38,
"": 0x39,
"": 0x3A,
"": 0x3B,
"": 0x3C,
"": 0x3D,
"": 0x3E,
"": 0x3F,
"": 0x40,
"": 0x41,
"": 0x42,
"": 0x43,
"": 0x44,
"": 0x45,
"": 0x46,
"": 0x47,
"": 0x48,
"": 0x49,
"": 0x4A,
"": 0x4B,
"": 0x4C,
"": 0x4D,
"": 0x4E,
"": 0x4F,
"": 0x50,
"": 0x51,
"": 0x52,
"": 0x53,
"": 0x54,
"": 0x55,
"": 0x56,
"": 0x57,
"": 0x58,
"": 0x59,
"": 0x5A,
"": 0x5B,
"": 0x5C,
"": 0x5D,
"": 0x5E,
"": 0x5F,
"": 0x60,
"": 0x61,
"": 0x62,
"": 0x63,
"": 0x64,
"": 0x65,
"": 0x66,
"": 0x67,
"": 0x68,
"": 0x69,
"": 0x6A,
"": 0x6B,
"": 0x6C,
"": 0x6D,
"": 0x6E,
"": 0x6F,
"": 0x70,
"": 0x71,
"": 0x72,
"": 0x73,
"": 0x74,
"": 0x75,
"": 0x76,
"": 0x77,
"": 0x78,
"": 0x79,
"": 0x7A,
"": 0x7B,
"": 0x7C,
"": 0x7D,
"": 0x7E,
"": 0x7F,
"": 0x80,
"": 0x81,
"": 0x82,
"": 0x83,
"": 0x84,
"": 0x85,
"": 0x86,
"": 0x87,
"": 0x88,
"使": 0x89,
"": 0x8A,
"": 0x8B,
"": 0x8C,
"": 0x8D,
"": 0x8E,
"": 0x8F,
"殿": 0x90,
"": 0x91,
"": 0x92,
"": 0x93,
"": 0x94,
"": 0x95,
"": 0x96,
"": 0x97,
"": 0x98,
"": 0x99,
"": 0x9A,
"": 0x9B,
"": 0x9C,
"": 0x9D,
"": 0x9E,
"": 0x9F,
"": 0xA0,
"": 0xA1,
"姿": 0xA2,
"": 0xA3,
"": 0xA4,
"": 0xA5,
"": 0xA6,
"": 0xA7,
"": 0xA8,
"": 0xA9,
"": 0xAA,
"": 0xAB,
"": 0xAC,
"": 0xAD,
"": 0xAE,
"": 0xAF,
"": 0xB0,
"": 0xB1,
"": 0xB2,
"": 0xB3,
"": 0xB4,
"": 0xB5,
"": 0xB6,
"": 0xB7,
"": 0xB8,
"": 0xB9,
"": 0xBA,
"": 0xBB,
"": 0xBC,
"": 0xBD,
"": 0xBE,
"": 0xBF,
"": 0xC0,
"": 0xC1,
"": 0xC2,
"": 0xC3,
"": 0xC4,
"": 0xC5,
"": 0xC6,
"": 0xC7,
"": 0xC8,
"": 0xC9,
"": 0xCA,
"": 0xCB,
"": 0xCC,
"": 0xCD,
"": 0xCE,
"": 0xCF,
"": 0xD0,
"": 0xD1,
"": 0xD2,
"": 0xD3,
"": 0xD4,
"": 0xD5,
"": 0xD6,
"": 0xD7,
"": 0xD8,
"": 0xD9,
"": 0xDA,
"": 0xDB,
"": 0xDC,
"": 0xDD,
"": 0xDE,
"": 0xDF,
"": 0xE0,
"": 0xE1,
"": 0xE2,
"": 0xE3,
"": 0xE4,
"": 0xE5,
"": 0xE6,
"": 0xE7,
"": 0xE8,
"": 0xE9,
"": 0xEA,
"": 0xEB,
"": 0xEC,
"": 0xED,
"": 0xEE,
"": 0xEF,
"": 0xF0,
"": 0xF1,
"": 0xF2,
"": 0xF3,
#"力": 0xF4,
"": 0xF5,
"": 0xF6,
"": 0xF7,
"": 0xF8,
"": 0xF9,
"": 0xFA,
"": 0xFB,
"": 0xFC,
"": 0xFD,
"": 0xFE,
"": 0xFF}
alpha_offset = 0x49
number_offset = 0x70
@classmethod
def map_char(cls, char):
if char in cls.kanji:
return [0x01, cls.kanji[char]]
return super().map_char(char)
@classmethod
def convert(cls, text):
buf = bytearray()
for char in text.lower():
res = cls.map_char(char)
if isinstance(res, int):
buf.extend([0x00, res])
else:
buf.extend(res)
return buf
class GoldCreditMapper(CharTextMapper):
char_map = {' ': 0x9F, char_map = {' ': 0x9F,
',': 0x34, ',': 0x34,
'.': 0x37, "'": 0x35,
'-': 0x36, '-': 0x36,
"'": 0x35} '.': 0x37,}
alpha_offset = -0x47 alpha_offset = -0x47
class GreenCreditMapper(TextMapper): class GreenCreditMapper(CharTextMapper):
char_map = {' ': 0x9F, char_map = {' ': 0x9F,
'.': 0x52} '·': 0x52}
alpha_offset = -0x29 alpha_offset = -0x29
class RedCreditMapper(TextMapper): class RedCreditMapper(CharTextMapper):
char_map = {' ': 0x9F} #fixme char_map = {' ': 0x9F}
alpha_offset = -0x61 alpha_offset = -0x61
class LargeCreditTopMapper(TextMapper): class LargeCreditTopMapper(CharTextMapper):
char_map = {' ': 0x9F, char_map = {' ': 0x9F,
"'": 0x77, "'": 0x77,
'!': 0x78, '!': 0x78,
'.': 0xA0, '.': 0xA0,
'#': 0xA1, '#': 0xA1,
'/': 0xA2, '/': 0xA2,
':': 0xA3} ':': 0xA3,
',': 0xA4,
'?': 0xA5,
'=': 0xA6,
'"': 0xA7,
'-': 0xA8,
'·': 0xA9,
'': 0xA9,
'': 0xAA,
'': 0xAB,}
alpha_offset = -0x04 alpha_offset = -0x04
number_offset = 0x23 number_offset = 0x23
class LargeCreditBottomMapper(TextMapper): class LargeCreditBottomMapper(CharTextMapper):
char_map = {' ': 0x9F, char_map = {' ': 0x9F,
"'": 0x9D, "'": 0x9D,
'!': 0x9E, '!': 0x9E,
'.': 0xC0, '.': 0xC0,
'#': 0xC1, '#': 0xC1,
'/': 0xC2, '/': 0xC2,
':': 0xC3} ':': 0xC3,
',': 0xC4,
'?': 0xC5,
'=': 0xC6,
'"': 0xC7,
'-': 0xC8,
'·': 0xC9,
'': 0xC9,
'': 0xCA,
'': 0xCB,}
alpha_offset = 0x22 alpha_offset = 0x22
number_offset = 0x49 number_offset = 0x49

View File

@ -2,6 +2,14 @@ import os
import subprocess import subprocess
import sys import sys
def int16_as_bytes(value):
value = value & 0xFFFF
return [value & 0xFF, (value >> 8) & 0xFF]
def int32_as_bytes(value):
value = value & 0xFFFFFFFF
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]
def is_bundled(): def is_bundled():
return getattr(sys, 'frozen', False) return getattr(sys, 'frozen', False)

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.