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 json
from collections import OrderedDict
from Utils import int16_as_bytes
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.logic = logic
self.mode = mode
@ -18,6 +18,7 @@ class World(object):
self.algorithm = algorithm
self.dungeons = []
self.regions = []
self.shops = []
self.itempool = []
self.seed = None
self.state = CollectionState(self)
@ -44,7 +45,7 @@ class World(object):
self.aga_randomness = True
self.lock_aga_door_in_escape = False
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.fix_skullwoods_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.disable_music = disable_music
self.keysanity = keysanity
self.retro = retro
self.custom = custom
self.customitemarray = customitemarray
self.can_take_damage = True
self.difficulty_requirements = None
self.fix_fake_world = True
self.dynamic_regions = []
self.dynamic_locations = []
self.spoiler = Spoiler(self)
self.lamps_needed_for_dark_rooms = 1
@ -183,6 +187,9 @@ class World(object):
self._cached_locations.extend(region.locations)
return self._cached_locations
def clear_location_cache(self):
self._cached_locations = None
def get_unfilled_locations(self):
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.shuffle_ganon)
markbool(self.keysanity)
markbool(self.retro)
assert id_value_max <= 0xFFFFFFFF
return id_value
@ -375,6 +383,19 @@ class CollectionState(object):
return item in self.prog_items
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):
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())
elif self.world.difficulty == 'insane' and not fullrefill:
basemagic = basemagic
else:
elif self.can_buy_unlimited('Green Potion') or self.can_buy_unlimited('Red Potion'):
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):
return (self.has_blunt_weapon()
or self.has('Cane of Somaria')
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')
)
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):
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)
class Region(object):
def __init__(self, name, type):
@ -575,6 +602,7 @@ class Region(object):
self.exits = []
self.locations = []
self.dungeon = None
self.shop = None
self.world = None
self.is_light_world = False # will be set aftermaking connections.
self.is_dark_world = False
@ -742,24 +770,121 @@ class Item(object):
class Crystal(Item):
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):
def __init__(self, world):
self.world = world
self.entrances = []
self.entrances = OrderedDict()
self.medallions = {}
self.playthrough = {}
self.locations = {}
self.paths = {}
self.metadata = {}
self.shops = []
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):
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
self.metadata = {'version': ERVersion,
'seed': self.world.seed,
@ -781,9 +906,11 @@ class Spoiler(object):
def to_json(self):
self.parse_data()
out = OrderedDict()
out['entrances'] = self.entrances
out['Entrances'] = list(self.entrances.values())
out.update(self.locations)
out['medallions'] = self.medallions
out['Special'] = self.medallions
if self.shops:
out['Shops'] = self.shops
out['playthrough'] = self.playthrough
out['paths'] = self.paths
out['meta'] = self.metadata
@ -805,12 +932,14 @@ class Spoiler(object):
outfile.write('Keysanity enabled: %s' % ('Yes' if self.metadata['keysanity'] else 'No'))
if self.entrances:
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\nMisery Mire Medallion: %s' % self.medallions['Misery Mire'])
outfile.write('\nTurtle Rock Medallion: %s' % self.medallions['Turtle Rock'])
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'.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')

View File

@ -7,7 +7,7 @@ from Items import ItemFactory
def create_dungeons(world):
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:
world.get_region(region).dungeon = 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)']))
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):
@ -34,6 +34,9 @@ def fill_dungeons(world):
all_state_base = world.get_all_state()
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
@ -110,6 +113,9 @@ def fill_dungeons_restrictive(world, shuffled_locations):
all_state_base = world.get_all_state()
skull_woods_big_chest = world.get_location('Skull Woods - Pinball Room')
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
shuffled_locations.remove(skull_woods_big_chest)

View File

@ -20,12 +20,14 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
def start():
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
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='''\
Select Enforcement of Item Requirements. (default: %(default)s)
No Glitches:
Minor Glitches: May require Fake Flippers, Bunny Revival
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'],
help='''\
@ -165,6 +167,10 @@ def start():
Keys (and other dungeon items) are no longer restricted to
their dungeons, but can be anywhere
''', 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('--customitemarray', default=False, help='Not supported.')
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)':
world.swamp_patch_required = True
# check for
# check for potion shop location
if world.get_entrance('Potion Shop').connected_region.name != 'Potion Shop':
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 Bomb Jump', 'Paradox Cave'),
('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 Mirror Spot', 'Fairy Ascension Plateau'),
('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)
keysanityVar = IntVar()
keysanityCheckbutton = Checkbutton(checkBoxFrame, text="Keysanity (keys anywhere)", variable=keysanityVar)
retroVar = IntVar()
retroCheckbutton = Checkbutton(checkBoxFrame, text="Retro mode (universal keys)", variable=retroVar)
dungeonItemsVar = IntVar()
dungeonItemsCheckbutton = Checkbutton(checkBoxFrame, text="Place Dungeon Items (Compasses/Maps)", onvalue=0, offvalue=1, variable=dungeonItemsVar)
beatableOnlyVar = IntVar()
@ -78,6 +80,7 @@ def guiMain(args=None):
suppressRomCheckbutton.pack(expand=True, anchor=W)
quickSwapCheckbutton.pack(expand=True, anchor=W)
keysanityCheckbutton.pack(expand=True, anchor=W)
retroCheckbutton.pack(expand=True, anchor=W)
dungeonItemsCheckbutton.pack(expand=True, anchor=W)
beatableOnlyCheckbutton.pack(expand=True, anchor=W)
disableMusicCheckbutton.pack(expand=True, anchor=W)
@ -146,7 +149,7 @@ def guiMain(args=None):
logicFrame = Frame(drowDownFrame)
logicVar = StringVar()
logicVar.set('noglitches')
logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches')
logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches', 'nologic')
logicOptionMenu.pack(side=RIGHT)
logicLabel = Label(logicFrame, text='Game logic')
logicLabel.pack(side=LEFT)
@ -262,6 +265,7 @@ def guiMain(args=None):
guiargs.create_spoiler = bool(createSpoilerVar.get())
guiargs.suppress_rom = bool(suppressRomVar.get())
guiargs.keysanity = bool(keysanityVar.get())
guiargs.retro = bool(retroVar.get())
guiargs.nodungeonitems = bool(dungeonItemsVar.get())
guiargs.beatableonly = bool(beatableOnlyVar.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(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(triforceVar.get()), int(rupoorcostVar.get())]
int(triforceVar.get()), int(rupoorcostVar.get()), int(universalkeyVar.get())]
guiargs.rom = romVar.get()
guiargs.jsonout = None
guiargs.sprite = sprite
@ -934,6 +938,14 @@ def guiMain(args=None):
redclockLabel.pack(anchor=W, side=LEFT, padx=(0,14))
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)
triforcepieceLabel = Label(triforcepieceFrame, text='Triforce Piece')
triforcepieceVar = StringVar(value='0')
@ -978,6 +990,7 @@ def guiMain(args=None):
createSpoilerVar.set(int(args.create_spoiler))
suppressRomVar.set(int(args.suppress_rom))
keysanityVar.set(args.keysanity)
retroVar.set(args.retro)
if args.nodungeonitems:
dungeonItemsVar.set(int(not args.nodungeonitems))
beatableOnlyVar.set(int(args.beatableonly))

View File

@ -2,69 +2,70 @@ from collections import namedtuple
import logging
import random
from Items import ItemFactory
from Fill import FillError, fill_restrictive
from BaseClasses import Region, RegionType, Shop, ShopType, Location
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.
#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',
'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
basicgloves = ['Power Glove', 'Titans Mitts']
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)']
normalbaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows', 'Magic Upgrade (1/2)'] + ['Rupees (300)'] * 4 +
['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
normalbaseitems = (['Silver Arrows', 'Magic Upgrade (1/2)', 'Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] +
['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
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)']
normalfourth5extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2 + ['Rupees (5)']
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 +
['Silver Arrows'] * 2 + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12)
easybaseitems = (['Sanctuary Heart Container'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 + ['Lamp'] * 2 + ['Silver Arrows'] * 2 +
['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12)
easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)']
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
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)']
easyfinal25extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 14 + ['Rupee (1)'] + ['Arrows (10)'] * 4 + ['Rupees (5)'] * 2
easytimedotherextra = ['Red Clock'] * 5
hardbaseitems = (['Silver Arrows', 'Single Arrow', 'Single Bomb'] + ['Rupees (300)'] + ['Rupees (100)'] * 3 + ['Rupees (50)'] * 5 + ['Bombs (3)'] * 5 +
['Boss Heart Container'] * 5 + ['Piece of Heart'] * 24)
hardfirst20extra = ['Single Bomb'] * 7 + ['Rupees (5)'] * 8 + ['Rupee (1)'] * 2 + ['Rupees (20)'] * 2 + ['Arrows (10)']
hardsecond10extra = ['Rupees (5)'] * 7 + ['Rupee (1)'] * 3
hardthird10extra = ['Arrows (10)'] * 4 + ['Rupees (20)'] * 3 + ['Single Bomb'] * 3
hardfourth10extra = ['Rupees (5)'] * 3 + ['Single Arrow'] * 5 + ['Single Bomb'] * 2
hardfinal20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 2 + ['Single Arrow'] * 14
hardbaseitems = ['Silver Arrows', 'Single Arrow', 'Bombs (10)'] + ['Rupees (300)'] * 4 + ['Boss Heart Container'] * 6 + ['Piece of Heart'] * 20 + ['Rupees (5)'] * 7 + ['Bombs (3)'] * 4
hardfirst20extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Bombs (3)'] * 5 + ['Rupees (5)'] * 10 + ['Arrows (10)', 'Rupee (1)']
hardsecond10extra = ['Rupees (5)'] * 5 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)']
hardthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Rupees (5)'] * 3
hardfourth10extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 7 + ['Rupees (5)']
hardfinal20extra = ['Rupees (20)'] * 18 + ['Rupees (5)'] * 2
expertbaseitems = (['Single Arrow', 'Rupees (300)', 'Rupees (100)', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 5 +
['Rupees (20)'] * 3 + ['Single Bomb'] * 10 + ['Piece of Heart'] * 24)
expertfirst15extra = ['Single Bomb'] * 7 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 5
expertsecond15extra = ['Single Bomb'] * 6 + ['Single Arrow'] * 4 + ['Rupee (1)'] * 5
expertthird10extra = ['Rupees (5)'] * 3 + ['Single Bomb'] * 3 + ['Rupees (20)'] * 2 + ['Single Arrow'] * 2
expertfourth5extra = ['Rupees (5)'] * 2 + ['Single Arrow'] * 3
expertfinal25extra = ['Single Bomb'] * 4 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 18
expertbaseitems = (['Rupees (300)'] * 4 + ['Single Arrow', 'Silver Arrows', 'Boss Heart Container', 'Rupee (1)', 'Bombs (10)'] + ['Piece of Heart'] * 20 + ['Rupees (5)'] * 2 +
['Bombs (3)'] * 9 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2)
expertfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Rupees (5)'] * 12
expertsecond15extra = ['Rupees (5)'] * 10 + ['Rupees (20)'] * 5
expertthird10extra = ['Rupees (50)'] * 4 + ['Rupees (5)'] * 2 + ['Arrows (10)'] * 3 + ['Rupee (1)']
expertfourth5extra = ['Rupees (5)'] * 5
expertfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
insanebaseitems = (['Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 10 + ['Rupees (300)'] * 5 + ['Rupees (100)'] * 4 +
['Rupee (1)'] * 8 + ['Rupees (20)'] * 4 + ['Single Bomb'] * 8 + ['Single Arrow'] * 6)
insanefirst15extra = ['Single Bomb'] * 5 + ['Single Arrow'] * 4 + ['Rupee (1)'] * 5 + ['Rupees (20)']
insanesecond15extra = ['Single Bomb'] * 5 + ['Single Arrow'] * 5 + ['Rupee (1)'] * 5
insanethird10extra = ['Single Bomb'] * 4 + ['Single Arrow'] * 3 + ['Rupee (1)'] * 3
insanefourth5extra = ['Single Bomb'] + ['Single Arrow'] * 2 + ['Rupee (1)'] * 2
insanefinal25extra = ['Single Bomb'] * 2 + ['Single Arrow'] * 10 + ['Rupee (1)'] * 7 + ['Rupees (20)'] * 6
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
insanefirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Rupees (5)'] * 12
insanesecond15extra = ['Rupees (5)'] * 10 + ['Rupees (20)'] * 5
insanethird10extra = ['Rupees (50)'] * 4 + ['Rupees (5)'] * 2 + ['Arrows (10)'] * 3 + ['Rupee (1)']
insanefourth5extra = ['Rupees (5)'] * 5
insanefinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
Difficulty = namedtuple('Difficulty',
['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield',
'basicshield', 'progressivearmor', 'basicarmor', 'swordless',
'progressivesword', 'basicsword', 'timedohko', 'timedother',
'triforcehunt', 'triforce_pieces_required', 'conditional_extras',
'triforcehunt', 'triforce_pieces_required', 'retro', 'conditional_extras',
'extras', 'progressive_sword_limit', 'progressive_shield_limit',
'progressive_armor_limit', 'progressive_bottle_limit'])
@ -78,7 +79,7 @@ def easy_conditional_extras(timer, _goal, _mode, pool, placed_items):
return easytimedotherextra
return []
def no_conditonal_extras(*_args):
def no_conditional_extras(*_args):
return []
@ -99,7 +100,8 @@ difficulties = {
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30,
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],
progressive_sword_limit = 4,
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
triforcehunt = ['Triforce Piece'] * 30,
triforce_pieces_required = 20,
retro = ['Small Key (Universal)'] * 27,
conditional_extras = easy_conditional_extras,
extras = [easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra],
progressive_sword_limit = 4,
@ -145,41 +148,43 @@ difficulties = {
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30,
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],
progressive_sword_limit = 3,
progressive_shield_limit = 2,
progressive_armor_limit = 1,
progressive_bottle_limit = 2,
progressive_bottle_limit = 4,
),
'expert': Difficulty(
baseitems = expertbaseitems,
bottles = hardbottles,
bottle_count = 4,
same_bottle = True,
progressiveshield = [],
basicshield = [],
same_bottle = False,
progressiveshield = ['Progressive Shield'] * 3,
basicshield = ['Progressive Shield'] * 3, #only the first one will upgrade, making this equivalent to two blue shields
progressivearmor = [],
basicarmor = [],
swordless = ['Rupees (20)'] * 3 + ['Silver Arrows'],
swordless = ['Rupees (20)'] * 4,
progressivesword = ['Progressive Sword'] * 3,
basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'],
timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5,
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30,
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],
progressive_sword_limit = 2,
progressive_shield_limit = 0,
progressive_shield_limit = 1,
progressive_armor_limit = 0,
progressive_bottle_limit = 1,
progressive_bottle_limit = 4,
),
'insane': Difficulty(
baseitems = insanebaseitems,
bottles = hardbottles,
bottle_count = 4,
same_bottle = True,
same_bottle = False,
progressiveshield = [],
basicshield = [],
progressivearmor = [],
@ -191,12 +196,13 @@ difficulties = {
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
triforcehunt = ['Triforce Piece'] * 30,
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],
progressive_sword_limit = 2,
progressive_shield_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.push_item('Agahnim 2', ItemFactory('Beat Agahnim 2'), False)
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
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)
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)
for (location, item) in placed_items:
world.push_item(location, ItemFactory(item), False)
@ -251,9 +265,84 @@ def generate_itempool(world):
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
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
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):
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'),
@ -281,9 +370,26 @@ def fill_prizes(world, attempts=15):
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 = []
placed_items = []
clock_mode = None
@ -382,9 +488,20 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode):
if goal == 'pedestal':
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)
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 = []
placed_items = []
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):
itemtotal = itemtotal + customitemarray[x]
itemtotal = itemtotal + customitemarray[66]
itemtotal = itemtotal + customitemarray[68]
pool.extend(['Bow'] * customitemarray[0])
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'))
pool.extend(['Fighter Sword'] * customitemarray[32])
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:
pool.extend(['Fighter Sword'] * customitemarray[32])
pool.extend(['Progressive Sword'] * customitemarray[36])
pool.extend(['Small Key (Universal)'] * customitemarray[68])
if shuffle == 'insanity_legacy':
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(['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:
pool.extend(['Nothing'] * (total_items_to_place - itemtotal))
@ -534,15 +661,18 @@ def test():
for mode in ['open', 'standard', 'swordless']:
for progressive in ['on', 'off']:
for shuffle in ['full', 'insane']:
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode)
for retro in [True, False]:
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro)
count = len(out[0]) + len(out[1])
correct_count = total_items_to_place
if goal in ['pedestal']:
# pedestal goals generate one extra item
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__':
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 (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'),
'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'),
'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'),
'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'),
'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'),
'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'),
'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, '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, '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, '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, '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'),
'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),
@ -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'),
'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 (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 (+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'),
@ -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'),
'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'),
'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'),
'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 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
import copy
from itertools import zip_longest
import json
import logging
import random
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 EntranceShuffle import link_entrances
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 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,
186, 135, 130, 189, 246, 254, 123, 245, 85, 241, 101, 129, 70, 255, 55, 248,
43, 146, 23, 179, 243, 208, 230, 176, 9, 88, 239, 226, 222, 203, 244, 183,
205, 74, 44, 5, 122, 220, 206, 47, 221, 125, 138, 155, 98, 79, 238, 119,
30, 24, 159, 39, 253, 27, 33, 218, 62, 82, 200, 28, 141, 191, 93, 22,
192, 54, 227, 108, 48, 78, 242, 166, 60, 250, 75, 145, 49, 212, 41, 25,
127, 89, 178, 157, 19, 158, 177, 231, 207, 66, 172, 17, 133, 61, 109, 86,
57, 143, 142, 219, 148, 209, 181, 87, 163, 40, 81, 114, 240, 103, 31, 175,
237, 185, 18, 173, 168, 45, 216, 106, 161, 16, 151, 139, 104, 134, 110, 21,
32, 131, 118, 182, 215, 67, 3, 73, 171, 71, 150, 147, 223, 247, 42, 132,
107, 149, 232, 153, 10, 201, 156, 225, 116, 194, 187, 204, 46, 165, 124, 92,
7, 0, 251, 126, 162, 80, 90, 154, 252, 197, 188, 52, 137, 117, 198, 63,
167, 38, 136, 96, 58, 11, 1, 115, 229, 224, 37, 112, 170, 59, 68, 196,
36, 64, 91, 213, 14, 180, 190, 164, 8, 56, 214, 77, 202, 193, 97, 84,
152, 83, 236, 211, 20, 217, 2, 228, 140, 69, 121, 111, 113, 128, 210, 51,
53, 6, 235, 34, 102, 29, 120, 35, 50, 65, 160, 249, 99, 169, 199, 195]
logic_hash = [215, 244, 99, 97, 253, 98, 31, 150, 207, 70, 50, 78, 59, 73, 221, 191,
21, 34, 200, 116, 77, 234, 89, 27, 228, 96, 16, 249, 56, 148, 3, 176,
17, 227, 24, 20, 238, 67, 37, 219, 62, 223, 60, 123, 246, 92, 164, 177,
211, 15, 245, 23, 75, 33, 190, 124, 144, 100, 87, 57, 86, 108, 80, 181,
6, 28, 2, 71, 182, 155, 222, 229, 90, 91, 32, 126, 25, 226, 133, 41,
132, 122, 10, 30, 53, 239, 112, 49, 104, 76, 209, 247, 139, 13, 173, 113,
159, 69, 145, 161, 11, 102, 149, 143, 129, 178, 45, 217, 196, 232, 208, 119,
94, 19, 35, 65, 170, 103, 55, 109, 5, 43, 118, 194, 180, 12, 206, 241,
8, 105, 210, 231, 179, 83, 137, 18, 212, 236, 225, 66, 63, 142, 138, 131,
192, 160, 1, 198, 153, 128, 106, 165, 39, 248, 167, 22, 74, 163, 140, 157,
214, 84, 154, 127, 195, 172, 136, 168, 68, 134, 152, 95, 111, 235, 26, 42,
135, 186, 250, 7, 72, 58, 4, 9, 193, 101, 52, 44, 187, 183, 171, 184,
197, 130, 47, 189, 81, 203, 51, 110, 146, 175, 213, 88, 79, 93, 64, 107,
121, 237, 0, 46, 120, 141, 199, 158, 174, 114, 205, 201, 151, 185, 242, 29,
162, 117, 85, 54, 14, 202, 216, 169, 230, 252, 188, 251, 36, 233, 147, 82,
115, 61, 255, 38, 220, 218, 40, 224, 48, 125, 204, 156, 240, 254, 166, 243]
def main(args, seed=None):
start = time.clock()
# 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('')
if seed is None:
random.seed(None)
@ -111,7 +112,7 @@ def main(args, seed=None):
else:
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 args.jsonout:
@ -139,7 +140,7 @@ def gt_filler(world):
def copy_world(world):
# 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.swamp_patch_required = world.swamp_patch_required
ret.ganon_at_pyramid = world.ganon_at_pyramid
@ -157,6 +158,13 @@ def copy_world(world):
create_regions(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
for region in world.regions:
copied_region = ret.get_region(region.name)
@ -185,6 +193,24 @@ def copy_world(world):
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):
# create a copy as we will modify it
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}
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'))
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
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()
# 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('')
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.
### No Logic
Items are placed without regard for progression or the seed being possible. Major glitches are likely required.
## Game Goal
### Ganon
@ -259,7 +263,7 @@ generate spoilers for statistical analysis.
## 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
@ -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
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
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)
```
--logic [{noglitches,minorglitches}]
--logic [{noglitches,minorglitches,nologic}]
```
Select the game logic (default: noglitches)
@ -353,7 +367,7 @@ Select the setting for progressive equipment. (default: on)
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)
@ -407,6 +421,12 @@ Disables game music, resulting in the game sound being just the SFX. (default: F
Enable Keysanity (default: False)
```
--retro
```
Enable Retro mode (default: False)
```
--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)
```
--heartcolor [{red,blue,green,yellow}]
```
Select the color of Link\'s heart meter. (default: red)
```
--sprite SPRITE
```

View File

@ -1,5 +1,5 @@
import collections
from BaseClasses import Region, Location, Entrance, RegionType
from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
def create_regions(world):
@ -28,7 +28,7 @@ def create_regions(world):
create_lw_region('Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
create_cave_region('Kings Grave', ['King\'s Tomb']),
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('Chris Houlihan Room', None, ['Chris Houlihan Room Exit']),
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',
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
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_cave_region('Bat Cave (right)', ['Magic Bat'], ['Bat Cave Door']),
create_cave_region('Bat Cave (left)', None, ['Bat Cave Exit']),
@ -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_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('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',
'Paradox Cave Lower - Left',
'Paradox Cave Lower - Right',
@ -135,6 +135,7 @@ def create_regions(world):
'Paradox Cave Upper - Right'],
['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('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('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)']),
@ -164,10 +165,10 @@ def create_regions(world):
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',
'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']),
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_cave_region('Fortune Teller (Dark)'),
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'])
]
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()
def create_lw_region(name, locations=None, exits=None):
@ -347,6 +356,38 @@ def mark_light_world_regions(world):
seen.add(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'),
'Bottle Merchant': (0x2EB18, False, 'with a merchant'),
@ -567,6 +608,10 @@ location_table = {'Mushroom': (0x180013, False, 'in the woods'),
'Ganon': (None, False, 'from me'),
'Agahnim 1': (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'),
'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'),

125
Rom.py
View File

@ -6,16 +6,17 @@ import os
import struct
import random
from BaseClasses import ShopType
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 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
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'dc5840f0d1ef7b51009c5625a054b3dd'
RANDOMIZERBASEHASH = '354bad0d01b6284c89f05c9b414d48c1'
class JsonRom(object):
@ -259,15 +260,6 @@ class Sprite(object):
# split into palettes of 15 colors
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):
# patch items
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
rom.write_byte(0xDBB73 + exit.addresses, exit.target)
write_custom_shops(rom, world)
# patch medallion requirements
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
rom.write_byte(0x36DD0, 0xD8) # One Heart
# potion heal amount
rom.write_byte(0x180084, 0x28) # Five Hearts
rom.write_byte(0x180084, 0x38) # Seven Hearts
# potion magic restore amount
rom.write_byte(0x180085, 0x40) # Half Magic
#Cape magic cost
@ -423,6 +416,10 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
overflow_replacement = GREEN_TWENTY_RUPEES
# Rupoor negative value
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
rom.write_bytes(0xF73D2, [0xFC, 0xFF])
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)
elif world.difficulty == 'expert':
# Powdered Fairies Prize
rom.write_byte(0x36DD0, 0x79) # Bees
rom.write_byte(0x36DD0, 0xD8) # One Heart
# potion heal amount
rom.write_byte(0x180084, 0x08) # One Heart
# potion magic restore amount
rom.write_byte(0x180085, 0x20) # Quarter Magic
#Cape magic cost
rom.write_bytes(0x3ADA7, [0x02, 0x02, 0x02])
rom.write_bytes(0x3ADA7, [0x01, 0x01, 0x01])
# Byrna Invulnerability: off
rom.write_byte(0x18004F, 0x00)
#Disable catching fairies
rom.write_byte(0x34FD6, 0x80)
overflow_replacement = GREEN_TWENTY_RUPEES
# 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
rom.write_bytes(0xF73D2, [0xFC, 0xFF])
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
rom.write_byte(0x180085, 0x00) # No healing
#Cape magic cost
rom.write_bytes(0x3ADA7, [0x02, 0x02, 0x02])
rom.write_bytes(0x3ADA7, [0x01, 0x01, 0x01])
# Byrna Invulnerability: off
rom.write_byte(0x18004F, 0x00)
#Disable catching fairies
rom.write_byte(0x34FD6, 0x80)
overflow_replacement = GREEN_TWENTY_RUPEES
# 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
rom.write_bytes(0xF73D2, [0xFC, 0xFF])
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)
#Enable catching fairies
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
if world.goal == 'triforcehunt':
overflow_replacement = TRIFORCE_PIECE
elif world.timer in ['timed', 'timed-countdown', 'timed-ohko']:
if world.timer in ['timed', 'timed-countdown', 'timed-ohko']:
overflow_replacement = GREEN_CLOCK
else:
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
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,
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]
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)
# 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(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(0x180175, 0x00) # Arrow mode: normal
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(0x18017E, 0x01) # Fairy fountains only trade in bottles
rom.write_byte(0x180034, 0x0A) # starting max bombs
rom.write_byte(0x180035, 30) # starting max arrows
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(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
rom.write_byte(0x180020, 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(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
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(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
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
# 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
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
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):
# enable instant item menu
@ -955,7 +1020,7 @@ def write_sprite(rom, sprite):
def write_string_to_rom(rom, target, string):
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):

173
Rules.py
View File

@ -3,6 +3,15 @@ import logging
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)
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('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('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_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('Sick Kid'), lambda state: state.has_bottle())
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('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('Agahnim 1'), lambda state: state.has_sword() and state.has('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_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_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('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'))
@ -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_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 Back 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_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 - Armos Knights'), lambda state: state.has('Bow') 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 - 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.can_shoot_arrows() and state.has('Big Key (Eastern Palace)'))
for location in ['Eastern Palace - Armos Knights', 'Eastern Palace - Big Chest']:
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 - 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_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
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow')))
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
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow')))
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_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.can_shoot_arrows()))
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.can_shoot_arrows()))
for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Chest']:
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']:
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_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())
@ -243,11 +255,13 @@ def global_rules(world):
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']:
forbid_item(world.get_location(location), 'Big Key (Tower of Hera)')
for location in ['Tower of Hera - Big Key Chest']:
forbid_item(world.get_location(location), 'Small Key (Tower of Hera)')
# for location in ['Tower of Hera - Big Key Chest']:
# 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 Small Key Door'), lambda state: state.has('Small Key (Swamp Palace)'))
set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.has('Open Floodgate'))
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_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)')
@ -258,48 +272,48 @@ def global_rules(world):
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('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_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_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_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_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']:
forbid_item(world.get_location(location), 'Big Key (Thieves Town)')
for location in ['Thieves\' Town - Attic', 'Thieves Town - Blind']:
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 (Right) North Door'), lambda state: state.has('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 (Left) Door to Exit'), lambda state: state.has('Small Key (Skull Woods)', 2))
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_key('Small Key (Skull Woods)'))
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_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_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']:
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_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 (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 (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_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'))
for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']:
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 - 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)'))
# 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
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...
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
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
(item_name(state, 'Misery Mire - Big Key Chest') in ['Big Key (Misery Mire)'])) else state.has('Small Key (Misery Mire)', 3))
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_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 - 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']:
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_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 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) (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 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 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))))
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 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 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 (North)'), lambda state: state.has('Small Key (Palace of Darkness)', 4))
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_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_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_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_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_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_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_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_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 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_key('Small Key (Palace of Darkness)', 5))
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
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 (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_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_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_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.
# 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 ....
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
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:
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...
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
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 Key Room - Left'), lambda state: state.has('Bow') 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 Room - Right'), lambda state: state.has('Bow') 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_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.can_shoot_arrows() 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.can_shoot_arrows())
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_entrance('Ganons Tower Moldorm Door'), lambda state: state.has('Small Key (Ganons Tower)', 4))
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_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_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'))
@ -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')
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
@ -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 - 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 - Zelda\'s 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_key('Small Key (Escape)'))
def swordless_rules(world):
@ -459,15 +473,15 @@ def swordless_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('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('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('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_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'))
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):
for loc in ['Sanctuary','Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
'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
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 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:
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
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
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:
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
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.
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_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_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_key('Small Key (Turtle Rock)', 3))
# set big key restrictions
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)',
'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):
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
# 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
# b) being able to access all entrances from there to `region`
seen = set([region])
@ -836,6 +845,10 @@ def set_bunny_rules(world):
for exit in region.exits:
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
for location in world.get_locations():
if location.parent_region.is_dark_world:

546
Text.py
View File

@ -1,3 +1,4 @@
# -*- coding: UTF-8 -*-
text_addresses = {'Pedestal': (0x180300, 256),
'Triforce': (0x180400, 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.",
"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",
"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 = [
'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",
"A blind man\nwalks into\na bar.\nAnd a table.\nAnd a chair.\n",
"What do ducks\nlike to eat?\n\nQuackers!\n",
@ -433,78 +437,236 @@ class SceneLargeCreditLine(SceneCreditLine):
buf += LargeCreditBottomMapper.convert(self.text)
return buf
class MultiByteTextMapper(object):
@classmethod
def convert(cls, text, maxbytes=256):
outbuf = MultiByteCoreTextMapper.convert(text)
def string_to_alttp_text(s, maxbytes=256):
lines = s.upper().split('\n')
# check for max length
if len(outbuf) > maxbytes - 2:
outbuf = outbuf[:maxbytes - 2]
# 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.
outbuf.append(0x7F)
outbuf.append(0x7F)
return outbuf
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 = 14
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 len(word) > 14:
if linespace < 14:
if cls.wordlen(word) > wrap:
if linespace < wrap:
word = ' ' + word
word_first = word[:linespace]
words.insert(0, word[linespace:])
(word_first, word_rest) = cls.splitword(word, linespace)
words.insert(0, word_rest)
lines.insert(0, ' '.join(words))
write_word(outbuf, word_first)
outbuf.extend(RawMBTextMapper.convert(word_first))
break
if len(word) <= (linespace if linespace == 14 else linespace - 1):
if linespace < 14:
if cls.wordlen(word) <= (linespace if linespace == wrap else linespace - 1):
if linespace < wrap:
word = ' ' + word
linespace -= len(word)
write_word(outbuf, 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 lineindex % 3 == 0 and lines:
if pause and lineindex % 3 == 0 and has_more_lines:
outbuf.append(0x7E)
if lineindex >= 3 and lines:
if lineindex >= 3 and has_more_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
@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
def write_word(buf, word):
for char in word:
buf.extend([0x00, char_to_alttp_char(char)])
@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
l += c_len
offset = new_offset
return (word[0:offset], word[offset:])
@classmethod
def charlen(cls, word, offset):
c = word[offset]
if c in ['>', '¼', '½', '']:
return (2, offset+1)
if c in ['@']:
return (4, offset+1)
if c in ['', '', '', '']:
return (2, offset+1)
return (1, offset+1)
char_map = {' ': 0xFF,
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:
raise ValueError("Unexpected byte found in uncompressed string")
return outbuf
class CharTextMapper(object):
number_offset = None
alpha_offset = 0
char_map = {}
@classmethod
def map_char(cls, char):
if cls.number_offset is not None:
if 0x30 <= ord(char) <= 0x39:
return ord(char) + cls.number_offset
if 0x61 <= ord(char) <= 0x7A:
return ord(char) + cls.alpha_offset
return cls.char_map.get(char, cls.char_map[' '])
@classmethod
def convert(cls, text):
buf = bytearray()
for char in text.lower():
buf.append(cls.map_char(char))
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,
@ -665,74 +827,338 @@ char_map = {' ': 0xFF,
'': 0x9E,
'': 0x9F}
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
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
alpha_offset = 0
char_map = {}
@classmethod
def map_char(cls, char):
if cls.number_offset is not None:
if 0x30 <= ord(char) <= 0x39:
return ord(char) + cls.number_offset
if 0x61 <= ord(char) <= 0x7A:
return ord(char) + cls.alpha_offset
return cls.char_map.get(char, cls.char_map[' '])
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():
buf.append(cls.map_char(char))
res = cls.map_char(char)
if isinstance(res, int):
buf.extend([0x00, res])
else:
buf.extend(res)
return buf
class GoldCreditMapper(TextMapper):
class GoldCreditMapper(CharTextMapper):
char_map = {' ': 0x9F,
',': 0x34,
'.': 0x37,
"'": 0x35,
'-': 0x36,
"'": 0x35}
'.': 0x37,}
alpha_offset = -0x47
class GreenCreditMapper(TextMapper):
class GreenCreditMapper(CharTextMapper):
char_map = {' ': 0x9F,
'.': 0x52}
'·': 0x52}
alpha_offset = -0x29
class RedCreditMapper(TextMapper):
char_map = {' ': 0x9F} #fixme
class RedCreditMapper(CharTextMapper):
char_map = {' ': 0x9F}
alpha_offset = -0x61
class LargeCreditTopMapper(TextMapper):
class LargeCreditTopMapper(CharTextMapper):
char_map = {' ': 0x9F,
"'": 0x77,
'!': 0x78,
'.': 0xA0,
'#': 0xA1,
'/': 0xA2,
':': 0xA3}
':': 0xA3,
',': 0xA4,
'?': 0xA5,
'=': 0xA6,
'"': 0xA7,
'-': 0xA8,
'·': 0xA9,
'': 0xA9,
'': 0xAA,
'': 0xAB,}
alpha_offset = -0x04
number_offset = 0x23
class LargeCreditBottomMapper(TextMapper):
class LargeCreditBottomMapper(CharTextMapper):
char_map = {' ': 0x9F,
"'": 0x9D,
'!': 0x9E,
'.': 0xC0,
'#': 0xC1,
'/': 0xC2,
':': 0xC3}
':': 0xC3,
',': 0xC4,
'?': 0xC5,
'=': 0xC6,
'"': 0xC7,
'-': 0xC8,
'·': 0xC9,
'': 0xC9,
'': 0xCA,
'': 0xCB,}
alpha_offset = 0x22
number_offset = 0x49

View File

@ -2,6 +2,14 @@ import os
import subprocess
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():
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.