commit
33e076209e
157
BaseClasses.py
157
BaseClasses.py
|
@ -3,11 +3,11 @@ from enum import Enum, unique
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
from Utils import int16_as_bytes
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
|
|
||||||
def __init__(self, shuffle, logic, mode, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, check_beatable_only, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, custom, customitemarray):
|
def __init__(self, shuffle, logic, mode, difficulty, timer, progressive, goal, algorithm, place_dungeon_items, check_beatable_only, shuffle_ganon, quickswap, fastmenu, disable_music, keysanity, retro, custom, customitemarray):
|
||||||
self.shuffle = shuffle
|
self.shuffle = shuffle
|
||||||
self.logic = logic
|
self.logic = logic
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
|
@ -18,6 +18,7 @@ class World(object):
|
||||||
self.algorithm = algorithm
|
self.algorithm = algorithm
|
||||||
self.dungeons = []
|
self.dungeons = []
|
||||||
self.regions = []
|
self.regions = []
|
||||||
|
self.shops = []
|
||||||
self.itempool = []
|
self.itempool = []
|
||||||
self.seed = None
|
self.seed = None
|
||||||
self.state = CollectionState(self)
|
self.state = CollectionState(self)
|
||||||
|
@ -44,7 +45,7 @@ class World(object):
|
||||||
self.aga_randomness = True
|
self.aga_randomness = True
|
||||||
self.lock_aga_door_in_escape = False
|
self.lock_aga_door_in_escape = False
|
||||||
self.fix_trock_doors = self.shuffle != 'vanilla'
|
self.fix_trock_doors = self.shuffle != 'vanilla'
|
||||||
self.save_and_quite_from_boss = False
|
self.save_and_quit_from_boss = False
|
||||||
self.check_beatable_only = check_beatable_only
|
self.check_beatable_only = check_beatable_only
|
||||||
self.fix_skullwoods_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']
|
self.fix_skullwoods_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']
|
||||||
self.fix_palaceofdarkness_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']
|
self.fix_palaceofdarkness_exit = self.shuffle not in ['vanilla', 'simple', 'restricted', 'dungeonssimple']
|
||||||
|
@ -56,11 +57,14 @@ class World(object):
|
||||||
self.fastmenu = fastmenu
|
self.fastmenu = fastmenu
|
||||||
self.disable_music = disable_music
|
self.disable_music = disable_music
|
||||||
self.keysanity = keysanity
|
self.keysanity = keysanity
|
||||||
|
self.retro = retro
|
||||||
self.custom = custom
|
self.custom = custom
|
||||||
self.customitemarray = customitemarray
|
self.customitemarray = customitemarray
|
||||||
self.can_take_damage = True
|
self.can_take_damage = True
|
||||||
self.difficulty_requirements = None
|
self.difficulty_requirements = None
|
||||||
self.fix_fake_world = True
|
self.fix_fake_world = True
|
||||||
|
self.dynamic_regions = []
|
||||||
|
self.dynamic_locations = []
|
||||||
self.spoiler = Spoiler(self)
|
self.spoiler = Spoiler(self)
|
||||||
self.lamps_needed_for_dark_rooms = 1
|
self.lamps_needed_for_dark_rooms = 1
|
||||||
|
|
||||||
|
@ -183,6 +187,9 @@ class World(object):
|
||||||
self._cached_locations.extend(region.locations)
|
self._cached_locations.extend(region.locations)
|
||||||
return self._cached_locations
|
return self._cached_locations
|
||||||
|
|
||||||
|
def clear_location_cache(self):
|
||||||
|
self._cached_locations = None
|
||||||
|
|
||||||
def get_unfilled_locations(self):
|
def get_unfilled_locations(self):
|
||||||
return [location for location in self.get_locations() if location.item is None]
|
return [location for location in self.get_locations() if location.item is None]
|
||||||
|
|
||||||
|
@ -278,6 +285,7 @@ class World(object):
|
||||||
markbool(self.check_beatable_only)
|
markbool(self.check_beatable_only)
|
||||||
markbool(self.shuffle_ganon)
|
markbool(self.shuffle_ganon)
|
||||||
markbool(self.keysanity)
|
markbool(self.keysanity)
|
||||||
|
markbool(self.retro)
|
||||||
assert id_value_max <= 0xFFFFFFFF
|
assert id_value_max <= 0xFFFFFFFF
|
||||||
return id_value
|
return id_value
|
||||||
|
|
||||||
|
@ -375,6 +383,19 @@ class CollectionState(object):
|
||||||
return item in self.prog_items
|
return item in self.prog_items
|
||||||
return self.item_count(item) >= count
|
return self.item_count(item) >= count
|
||||||
|
|
||||||
|
def has_key(self, item, count=1):
|
||||||
|
if self.world.retro:
|
||||||
|
return self.can_buy_unlimited('Small Key (Universal)')
|
||||||
|
if count == 1:
|
||||||
|
return item in self.prog_items
|
||||||
|
return self.item_count(item) >= count
|
||||||
|
|
||||||
|
def can_buy_unlimited(self, item):
|
||||||
|
for shop in self.world.shops:
|
||||||
|
if shop.has_unlimited(item) and shop.region.can_reach(self):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def item_count(self, item):
|
def item_count(self, item):
|
||||||
return len([pritem for pritem in self.prog_items if pritem == item])
|
return len([pritem for pritem in self.prog_items if pritem == item])
|
||||||
|
|
||||||
|
@ -415,18 +436,25 @@ class CollectionState(object):
|
||||||
basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count())
|
basemagic = basemagic + int(basemagic * 0.25 * self.bottle_count())
|
||||||
elif self.world.difficulty == 'insane' and not fullrefill:
|
elif self.world.difficulty == 'insane' and not fullrefill:
|
||||||
basemagic = basemagic
|
basemagic = basemagic
|
||||||
else:
|
elif self.can_buy_unlimited('Green Potion') or self.can_buy_unlimited('Red Potion'):
|
||||||
basemagic = basemagic + basemagic * self.bottle_count()
|
basemagic = basemagic + basemagic * self.bottle_count()
|
||||||
return basemagic >= smallmagic # FIXME bottle should really also have a requirement that we can reach some shop that sells green or blue potions
|
return basemagic >= smallmagic
|
||||||
|
|
||||||
def can_kill_most_things(self, enemies=5):
|
def can_kill_most_things(self, enemies=5):
|
||||||
return (self.has_blunt_weapon()
|
return (self.has_blunt_weapon()
|
||||||
or self.has('Cane of Somaria')
|
or self.has('Cane of Somaria')
|
||||||
or (self.has('Cane of Byrna') and (enemies < 6 or self.can_extend_Magic()))
|
or (self.has('Cane of Byrna') and (enemies < 6 or self.can_extend_Magic()))
|
||||||
or self.has('Bow')
|
or self.can_shoot_arrows()
|
||||||
or self.has('Fire Rod')
|
or self.has('Fire Rod')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def can_shoot_arrows(self):
|
||||||
|
if self.world.retro:
|
||||||
|
#TODO: need to decide how we want to handle wooden arrows longer-term (a can-buy-a check, or via dynamic shop location)
|
||||||
|
#FIXME: Should do something about hard+ ganon only silvers. For the moment, i believe they effective grant wooden, so we are safe
|
||||||
|
return self.has('Bow') and (self.has('Silver Arrows') or self.can_buy_unlimited('Single Arrow'))
|
||||||
|
return self.has('Bow')
|
||||||
|
|
||||||
def has_sword(self):
|
def has_sword(self):
|
||||||
return self.has('Fighter Sword') or self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword')
|
return self.has('Fighter Sword') or self.has('Master Sword') or self.has('Tempered Sword') or self.has('Golden Sword')
|
||||||
|
|
||||||
|
@ -565,7 +593,6 @@ class RegionType(Enum):
|
||||||
return self in (RegionType.Cave, RegionType.Dungeon)
|
return self in (RegionType.Cave, RegionType.Dungeon)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Region(object):
|
class Region(object):
|
||||||
|
|
||||||
def __init__(self, name, type):
|
def __init__(self, name, type):
|
||||||
|
@ -575,6 +602,7 @@ class Region(object):
|
||||||
self.exits = []
|
self.exits = []
|
||||||
self.locations = []
|
self.locations = []
|
||||||
self.dungeon = None
|
self.dungeon = None
|
||||||
|
self.shop = None
|
||||||
self.world = None
|
self.world = None
|
||||||
self.is_light_world = False # will be set aftermaking connections.
|
self.is_light_world = False # will be set aftermaking connections.
|
||||||
self.is_dark_world = False
|
self.is_dark_world = False
|
||||||
|
@ -742,24 +770,121 @@ class Item(object):
|
||||||
class Crystal(Item):
|
class Crystal(Item):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@unique
|
||||||
|
class ShopType(Enum):
|
||||||
|
Shop = 0
|
||||||
|
TakeAny = 1
|
||||||
|
|
||||||
|
class Shop(object):
|
||||||
|
def __init__(self, region, room_id, type, shopkeeper_config, replaceable):
|
||||||
|
self.region = region
|
||||||
|
self.room_id = room_id
|
||||||
|
self.type = type
|
||||||
|
self.inventory = [None, None, None]
|
||||||
|
self.shopkeeper_config = shopkeeper_config
|
||||||
|
self.replaceable = replaceable
|
||||||
|
self.active = False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def item_count(self):
|
||||||
|
return (3 if self.inventory[2] else
|
||||||
|
2 if self.inventory[1] else
|
||||||
|
1 if self.inventory[0] else
|
||||||
|
0)
|
||||||
|
|
||||||
|
def get_bytes(self):
|
||||||
|
# [id][roomID-low][roomID-high][doorID][zero][shop_config][shopkeeper_config][sram_index]
|
||||||
|
entrances = self.region.entrances
|
||||||
|
config = self.item_count
|
||||||
|
if len(entrances) == 1 and entrances[0].addresses:
|
||||||
|
door_id = entrances[0].addresses+1
|
||||||
|
else:
|
||||||
|
door_id = 0
|
||||||
|
config |= 0x40 # ignore door id
|
||||||
|
if self.type == ShopType.TakeAny:
|
||||||
|
config |= 0x80
|
||||||
|
return [0x00]+int16_as_bytes(self.room_id)+[door_id, 0x00, config, self.shopkeeper_config, 0x00]
|
||||||
|
|
||||||
|
def has_unlimited(self, item):
|
||||||
|
for inv in self.inventory:
|
||||||
|
if inv is None:
|
||||||
|
continue
|
||||||
|
if inv['max'] != 0 and inv['replacement'] is not None and inv['replacement'] == item:
|
||||||
|
return True
|
||||||
|
elif inv['item'] is not None and inv['item'] == item:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def clear_inventory(self):
|
||||||
|
self.inventory = [None, None, None]
|
||||||
|
|
||||||
|
def add_inventory(self, slot, item, price, max=0, replacement=None, replacement_price=0, create_location=False):
|
||||||
|
self.inventory[slot] = {
|
||||||
|
'item': item,
|
||||||
|
'price': price,
|
||||||
|
'max': max,
|
||||||
|
'replacement': replacement,
|
||||||
|
'replacement_price': replacement_price,
|
||||||
|
'create_location': create_location
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Spoiler(object):
|
class Spoiler(object):
|
||||||
|
|
||||||
def __init__(self, world):
|
def __init__(self, world):
|
||||||
self.world = world
|
self.world = world
|
||||||
self.entrances = []
|
self.entrances = OrderedDict()
|
||||||
self.medallions = {}
|
self.medallions = {}
|
||||||
self.playthrough = {}
|
self.playthrough = {}
|
||||||
self.locations = {}
|
self.locations = {}
|
||||||
self.paths = {}
|
self.paths = {}
|
||||||
self.metadata = {}
|
self.metadata = {}
|
||||||
|
self.shops = []
|
||||||
|
|
||||||
def set_entrance(self, entrance, exit, direction):
|
def set_entrance(self, entrance, exit, direction):
|
||||||
self.entrances.append(OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)]))
|
self.entrances[(entrance, direction)] = OrderedDict([('entrance', entrance), ('exit', exit), ('direction', direction)])
|
||||||
|
|
||||||
def parse_data(self):
|
def parse_data(self):
|
||||||
self.medallions = OrderedDict([('Misery Mire', self.world.required_medallions[0]), ('Turtle Rock', self.world.required_medallions[1])])
|
self.medallions = OrderedDict([('Misery Mire', self.world.required_medallions[0]), ('Turtle Rock', self.world.required_medallions[1])])
|
||||||
self.locations = {'other locations': OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in self.world.get_locations()])}
|
|
||||||
|
self.locations = OrderedDict()
|
||||||
|
listed_locations = set()
|
||||||
|
|
||||||
|
lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld]
|
||||||
|
self.locations['Light World'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations])
|
||||||
|
listed_locations.update(lw_locations)
|
||||||
|
|
||||||
|
dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld]
|
||||||
|
self.locations['Dark World'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations])
|
||||||
|
listed_locations.update(dw_locations)
|
||||||
|
|
||||||
|
cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave]
|
||||||
|
self.locations['Caves'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations])
|
||||||
|
listed_locations.update(cave_locations)
|
||||||
|
|
||||||
|
for dungeon in self.world.dungeons:
|
||||||
|
dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon]
|
||||||
|
self.locations[dungeon.name] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations])
|
||||||
|
listed_locations.update(dungeon_locations)
|
||||||
|
|
||||||
|
other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations]
|
||||||
|
if other_locations:
|
||||||
|
self.locations['Other Locations'] = OrderedDict([(str(location), str(location.item) if location.item is not None else 'Nothing') for location in other_locations])
|
||||||
|
listed_locations.update(other_locations)
|
||||||
|
|
||||||
|
for shop in self.world.shops:
|
||||||
|
if not shop.active:
|
||||||
|
continue
|
||||||
|
shopdata = {'location': shop.region.name,
|
||||||
|
'type': 'Take Any' if shop.type == ShopType.TakeAny else 'Shop'
|
||||||
|
}
|
||||||
|
for index, item in enumerate(shop.inventory):
|
||||||
|
if item is None:
|
||||||
|
continue
|
||||||
|
shopdata['item_{}'.format(index)] = "{} — {}".format(item['item'], item['price']) if item['price'] else item['item']
|
||||||
|
self.shops.append(shopdata)
|
||||||
|
|
||||||
|
|
||||||
from Main import __version__ as ERVersion
|
from Main import __version__ as ERVersion
|
||||||
self.metadata = {'version': ERVersion,
|
self.metadata = {'version': ERVersion,
|
||||||
'seed': self.world.seed,
|
'seed': self.world.seed,
|
||||||
|
@ -781,9 +906,11 @@ class Spoiler(object):
|
||||||
def to_json(self):
|
def to_json(self):
|
||||||
self.parse_data()
|
self.parse_data()
|
||||||
out = OrderedDict()
|
out = OrderedDict()
|
||||||
out['entrances'] = self.entrances
|
out['Entrances'] = list(self.entrances.values())
|
||||||
out.update(self.locations)
|
out.update(self.locations)
|
||||||
out['medallions'] = self.medallions
|
out['Special'] = self.medallions
|
||||||
|
if self.shops:
|
||||||
|
out['Shops'] = self.shops
|
||||||
out['playthrough'] = self.playthrough
|
out['playthrough'] = self.playthrough
|
||||||
out['paths'] = self.paths
|
out['paths'] = self.paths
|
||||||
out['meta'] = self.metadata
|
out['meta'] = self.metadata
|
||||||
|
@ -805,12 +932,14 @@ class Spoiler(object):
|
||||||
outfile.write('Keysanity enabled: %s' % ('Yes' if self.metadata['keysanity'] else 'No'))
|
outfile.write('Keysanity enabled: %s' % ('Yes' if self.metadata['keysanity'] else 'No'))
|
||||||
if self.entrances:
|
if self.entrances:
|
||||||
outfile.write('\n\nEntrances:\n\n')
|
outfile.write('\n\nEntrances:\n\n')
|
||||||
outfile.write('\n'.join(['%s %s %s' % (entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.entrances]))
|
outfile.write('\n'.join(['%s %s %s' % (entry['entrance'], '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', entry['exit']) for entry in self.entrances.values()]))
|
||||||
outfile.write('\n\nMedallions')
|
outfile.write('\n\nMedallions')
|
||||||
outfile.write('\n\nMisery Mire Medallion: %s' % self.medallions['Misery Mire'])
|
outfile.write('\n\nMisery Mire Medallion: %s' % self.medallions['Misery Mire'])
|
||||||
outfile.write('\nTurtle Rock Medallion: %s' % self.medallions['Turtle Rock'])
|
outfile.write('\nTurtle Rock Medallion: %s' % self.medallions['Turtle Rock'])
|
||||||
outfile.write('\n\nLocations:\n\n')
|
outfile.write('\n\nLocations:\n\n')
|
||||||
outfile.write('\n'.join(['%s: %s' % (location, item) for (location, item) in self.locations['other locations'].items()]))
|
outfile.write('\n'.join(['%s: %s' % (location, item) for grouping in self.locations.values() for (location, item) in grouping.items()]))
|
||||||
|
outfile.write('\n\nShops:\n\n')
|
||||||
|
outfile.write('\n'.join("{} [{}]\n {}".format(shop['location'], shop['type'], "\n ".join(item for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
|
||||||
outfile.write('\n\nPlaythrough:\n\n')
|
outfile.write('\n\nPlaythrough:\n\n')
|
||||||
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
outfile.write('\n'.join(['%s: {\n%s\n}' % (sphere_nr, '\n'.join([' %s: %s' % (location, item) for (location, item) in sphere.items()])) for (sphere_nr, sphere) in self.playthrough.items()]))
|
||||||
outfile.write('\n\nPaths:\n\n')
|
outfile.write('\n\nPaths:\n\n')
|
||||||
|
|
10
Dungeons.py
10
Dungeons.py
|
@ -7,7 +7,7 @@ from Items import ItemFactory
|
||||||
|
|
||||||
def create_dungeons(world):
|
def create_dungeons(world):
|
||||||
def make_dungeon(name, dungeon_regions, big_key, small_keys, dungeon_items):
|
def make_dungeon(name, dungeon_regions, big_key, small_keys, dungeon_items):
|
||||||
dungeon = Dungeon(name, dungeon_regions, big_key, small_keys, dungeon_items)
|
dungeon = Dungeon(name, dungeon_regions, big_key, [] if world.retro else small_keys, dungeon_items)
|
||||||
for region in dungeon.regions:
|
for region in dungeon.regions:
|
||||||
world.get_region(region).dungeon = dungeon
|
world.get_region(region).dungeon = dungeon
|
||||||
return dungeon
|
return dungeon
|
||||||
|
@ -26,7 +26,7 @@ def create_dungeons(world):
|
||||||
TR = make_dungeon('Turtle Rock', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], ItemFactory('Big Key (Turtle Rock)'), ItemFactory(['Small Key (Turtle Rock)'] * 4), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)']))
|
TR = make_dungeon('Turtle Rock', ['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)', 'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'], ItemFactory('Big Key (Turtle Rock)'), ItemFactory(['Small Key (Turtle Rock)'] * 4), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)']))
|
||||||
GT = make_dungeon('Ganons Tower', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)'), ItemFactory(['Small Key (Ganons Tower)'] * 4), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)']))
|
GT = make_dungeon('Ganons Tower', ['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)'), ItemFactory(['Small Key (Ganons Tower)'] * 4), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)']))
|
||||||
|
|
||||||
world.dungeons = [TR, ES, EP, DP, ToH, AT, PoD, TT, SW, IP, MM, GT, SP]
|
world.dungeons = [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]
|
||||||
|
|
||||||
|
|
||||||
def fill_dungeons(world):
|
def fill_dungeons(world):
|
||||||
|
@ -34,6 +34,9 @@ def fill_dungeons(world):
|
||||||
|
|
||||||
all_state_base = world.get_all_state()
|
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.push_item(world.get_location('Skull Woods - Pinball Room'), ItemFactory('Small Key (Skull Woods)'), False)
|
||||||
world.get_location('Skull Woods - Pinball Room').event = True
|
world.get_location('Skull Woods - Pinball Room').event = True
|
||||||
|
|
||||||
|
@ -110,6 +113,9 @@ def fill_dungeons_restrictive(world, shuffled_locations):
|
||||||
all_state_base = world.get_all_state()
|
all_state_base = world.get_all_state()
|
||||||
|
|
||||||
skull_woods_big_chest = world.get_location('Skull Woods - Pinball Room')
|
skull_woods_big_chest = world.get_location('Skull Woods - Pinball Room')
|
||||||
|
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)
|
world.push_item(skull_woods_big_chest, ItemFactory('Small Key (Skull Woods)'), False)
|
||||||
skull_woods_big_chest.event = True
|
skull_woods_big_chest.event = True
|
||||||
shuffled_locations.remove(skull_woods_big_chest)
|
shuffled_locations.remove(skull_woods_big_chest)
|
||||||
|
|
|
@ -20,12 +20,14 @@ class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
|
||||||
def start():
|
def start():
|
||||||
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||||
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
|
parser.add_argument('--create_spoiler', help='Output a Spoiler File', action='store_true')
|
||||||
parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches'],
|
parser.add_argument('--logic', default='noglitches', const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'nologic'],
|
||||||
help='''\
|
help='''\
|
||||||
Select Enforcement of Item Requirements. (default: %(default)s)
|
Select Enforcement of Item Requirements. (default: %(default)s)
|
||||||
No Glitches:
|
No Glitches:
|
||||||
Minor Glitches: May require Fake Flippers, Bunny Revival
|
Minor Glitches: May require Fake Flippers, Bunny Revival
|
||||||
and Dark Room Navigation.
|
and Dark Room Navigation.
|
||||||
|
No Logic: Distribute items without regard for
|
||||||
|
item requirements.
|
||||||
''')
|
''')
|
||||||
parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'swordless'],
|
parser.add_argument('--mode', default='open', const='open', nargs='?', choices=['standard', 'open', 'swordless'],
|
||||||
help='''\
|
help='''\
|
||||||
|
@ -165,6 +167,10 @@ def start():
|
||||||
Keys (and other dungeon items) are no longer restricted to
|
Keys (and other dungeon items) are no longer restricted to
|
||||||
their dungeons, but can be anywhere
|
their dungeons, but can be anywhere
|
||||||
''', action='store_true')
|
''', action='store_true')
|
||||||
|
parser.add_argument('--retro', help='''\
|
||||||
|
Keys are universal, shooting arrows costs rupees,
|
||||||
|
and a few other little things make this more like Zelda-1.
|
||||||
|
''', action='store_true')
|
||||||
parser.add_argument('--custom', default=False, help='Not supported.')
|
parser.add_argument('--custom', default=False, help='Not supported.')
|
||||||
parser.add_argument('--customitemarray', default=False, help='Not supported.')
|
parser.add_argument('--customitemarray', default=False, help='Not supported.')
|
||||||
parser.add_argument('--nodungeonitems', help='''\
|
parser.add_argument('--nodungeonitems', help='''\
|
||||||
|
|
|
@ -1026,7 +1026,7 @@ def link_entrances(world):
|
||||||
if world.get_entrance('Dam').connected_region.name != 'Dam' or world.get_entrance('Swamp Palace').connected_region.name != 'Swamp Palace (Entrance)':
|
if world.get_entrance('Dam').connected_region.name != 'Dam' or world.get_entrance('Swamp Palace').connected_region.name != 'Swamp Palace (Entrance)':
|
||||||
world.swamp_patch_required = True
|
world.swamp_patch_required = True
|
||||||
|
|
||||||
# check for
|
# check for potion shop location
|
||||||
if world.get_entrance('Potion Shop').connected_region.name != 'Potion Shop':
|
if world.get_entrance('Potion Shop').connected_region.name != 'Potion Shop':
|
||||||
world.powder_patch_required = True
|
world.powder_patch_required = True
|
||||||
|
|
||||||
|
@ -1744,6 +1744,7 @@ mandatory_connections = [('Lake Hylia Central Island Pier', 'Lake Hylia Central
|
||||||
('Paradox Cave Push Block', 'Paradox Cave Front'),
|
('Paradox Cave Push Block', 'Paradox Cave Front'),
|
||||||
('Paradox Cave Bomb Jump', 'Paradox Cave'),
|
('Paradox Cave Bomb Jump', 'Paradox Cave'),
|
||||||
('Paradox Cave Drop', 'Paradox Cave Chest Area'),
|
('Paradox Cave Drop', 'Paradox Cave Chest Area'),
|
||||||
|
('Light World Death Mountain Shop', 'Light World Death Mountain Shop'),
|
||||||
('Fairy Ascension Rocks', 'Fairy Ascension Plateau'),
|
('Fairy Ascension Rocks', 'Fairy Ascension Plateau'),
|
||||||
('Fairy Ascension Mirror Spot', 'Fairy Ascension Plateau'),
|
('Fairy Ascension Mirror Spot', 'Fairy Ascension Plateau'),
|
||||||
('Fairy Ascension Drop', 'East Death Mountain (Bottom)'),
|
('Fairy Ascension Drop', 'East Death Mountain (Bottom)'),
|
||||||
|
|
17
Gui.py
17
Gui.py
|
@ -62,6 +62,8 @@ def guiMain(args=None):
|
||||||
quickSwapCheckbutton = Checkbutton(checkBoxFrame, text="Enabled L/R Item quickswapping", variable=quickSwapVar)
|
quickSwapCheckbutton = Checkbutton(checkBoxFrame, text="Enabled L/R Item quickswapping", variable=quickSwapVar)
|
||||||
keysanityVar = IntVar()
|
keysanityVar = IntVar()
|
||||||
keysanityCheckbutton = Checkbutton(checkBoxFrame, text="Keysanity (keys anywhere)", variable=keysanityVar)
|
keysanityCheckbutton = Checkbutton(checkBoxFrame, text="Keysanity (keys anywhere)", variable=keysanityVar)
|
||||||
|
retroVar = IntVar()
|
||||||
|
retroCheckbutton = Checkbutton(checkBoxFrame, text="Retro mode (universal keys)", variable=retroVar)
|
||||||
dungeonItemsVar = IntVar()
|
dungeonItemsVar = IntVar()
|
||||||
dungeonItemsCheckbutton = Checkbutton(checkBoxFrame, text="Place Dungeon Items (Compasses/Maps)", onvalue=0, offvalue=1, variable=dungeonItemsVar)
|
dungeonItemsCheckbutton = Checkbutton(checkBoxFrame, text="Place Dungeon Items (Compasses/Maps)", onvalue=0, offvalue=1, variable=dungeonItemsVar)
|
||||||
beatableOnlyVar = IntVar()
|
beatableOnlyVar = IntVar()
|
||||||
|
@ -78,6 +80,7 @@ def guiMain(args=None):
|
||||||
suppressRomCheckbutton.pack(expand=True, anchor=W)
|
suppressRomCheckbutton.pack(expand=True, anchor=W)
|
||||||
quickSwapCheckbutton.pack(expand=True, anchor=W)
|
quickSwapCheckbutton.pack(expand=True, anchor=W)
|
||||||
keysanityCheckbutton.pack(expand=True, anchor=W)
|
keysanityCheckbutton.pack(expand=True, anchor=W)
|
||||||
|
retroCheckbutton.pack(expand=True, anchor=W)
|
||||||
dungeonItemsCheckbutton.pack(expand=True, anchor=W)
|
dungeonItemsCheckbutton.pack(expand=True, anchor=W)
|
||||||
beatableOnlyCheckbutton.pack(expand=True, anchor=W)
|
beatableOnlyCheckbutton.pack(expand=True, anchor=W)
|
||||||
disableMusicCheckbutton.pack(expand=True, anchor=W)
|
disableMusicCheckbutton.pack(expand=True, anchor=W)
|
||||||
|
@ -146,7 +149,7 @@ def guiMain(args=None):
|
||||||
logicFrame = Frame(drowDownFrame)
|
logicFrame = Frame(drowDownFrame)
|
||||||
logicVar = StringVar()
|
logicVar = StringVar()
|
||||||
logicVar.set('noglitches')
|
logicVar.set('noglitches')
|
||||||
logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches')
|
logicOptionMenu = OptionMenu(logicFrame, logicVar, 'noglitches', 'minorglitches', 'nologic')
|
||||||
logicOptionMenu.pack(side=RIGHT)
|
logicOptionMenu.pack(side=RIGHT)
|
||||||
logicLabel = Label(logicFrame, text='Game logic')
|
logicLabel = Label(logicFrame, text='Game logic')
|
||||||
logicLabel.pack(side=LEFT)
|
logicLabel.pack(side=LEFT)
|
||||||
|
@ -262,6 +265,7 @@ def guiMain(args=None):
|
||||||
guiargs.create_spoiler = bool(createSpoilerVar.get())
|
guiargs.create_spoiler = bool(createSpoilerVar.get())
|
||||||
guiargs.suppress_rom = bool(suppressRomVar.get())
|
guiargs.suppress_rom = bool(suppressRomVar.get())
|
||||||
guiargs.keysanity = bool(keysanityVar.get())
|
guiargs.keysanity = bool(keysanityVar.get())
|
||||||
|
guiargs.retro = bool(retroVar.get())
|
||||||
guiargs.nodungeonitems = bool(dungeonItemsVar.get())
|
guiargs.nodungeonitems = bool(dungeonItemsVar.get())
|
||||||
guiargs.beatableonly = bool(beatableOnlyVar.get())
|
guiargs.beatableonly = bool(beatableOnlyVar.get())
|
||||||
guiargs.quickswap = bool(quickSwapVar.get())
|
guiargs.quickswap = bool(quickSwapVar.get())
|
||||||
|
@ -276,7 +280,7 @@ def guiMain(args=None):
|
||||||
int(redmailVar.get()), int(progmailVar.get()), int(halfmagicVar.get()), int(quartermagicVar.get()), int(bcap5Var.get()), int(bcap10Var.get()), int(acap5Var.get()), int(acap10Var.get()),
|
int(redmailVar.get()), int(progmailVar.get()), int(halfmagicVar.get()), int(quartermagicVar.get()), int(bcap5Var.get()), int(bcap10Var.get()), int(acap5Var.get()), int(acap10Var.get()),
|
||||||
int(arrow1Var.get()), int(arrow10Var.get()), int(bomb1Var.get()), int(bomb3Var.get()), int(rupee1Var.get()), int(rupee5Var.get()), int(rupee20Var.get()), int(rupee50Var.get()), int(rupee100Var.get()),
|
int(arrow1Var.get()), int(arrow10Var.get()), int(bomb1Var.get()), int(bomb3Var.get()), int(rupee1Var.get()), int(rupee5Var.get()), int(rupee20Var.get()), int(rupee50Var.get()), int(rupee100Var.get()),
|
||||||
int(rupee300Var.get()), int(rupoorVar.get()), int(blueclockVar.get()), int(greenclockVar.get()), int(redclockVar.get()), int(triforcepieceVar.get()), int(triforcecountVar.get()),
|
int(rupee300Var.get()), int(rupoorVar.get()), int(blueclockVar.get()), int(greenclockVar.get()), int(redclockVar.get()), int(triforcepieceVar.get()), int(triforcecountVar.get()),
|
||||||
int(triforceVar.get()), int(rupoorcostVar.get())]
|
int(triforceVar.get()), int(rupoorcostVar.get()), int(universalkeyVar.get())]
|
||||||
guiargs.rom = romVar.get()
|
guiargs.rom = romVar.get()
|
||||||
guiargs.jsonout = None
|
guiargs.jsonout = None
|
||||||
guiargs.sprite = sprite
|
guiargs.sprite = sprite
|
||||||
|
@ -934,6 +938,14 @@ def guiMain(args=None):
|
||||||
redclockLabel.pack(anchor=W, side=LEFT, padx=(0,14))
|
redclockLabel.pack(anchor=W, side=LEFT, padx=(0,14))
|
||||||
redclockEntry.pack(anchor=E)
|
redclockEntry.pack(anchor=E)
|
||||||
|
|
||||||
|
universalkeyFrame = Frame(itemList5)
|
||||||
|
universalkeyLabel = Label(universalkeyFrame, text='Universal Key')
|
||||||
|
universalkeyVar = StringVar(value='0')
|
||||||
|
universalkeyEntry = Entry(universalkeyFrame, textvariable=universalkeyVar, width=3, validate='all', vcmd=vcmd)
|
||||||
|
universalkeyFrame.pack()
|
||||||
|
universalkeyLabel.pack(anchor=W, side=LEFT, padx=(0,57))
|
||||||
|
universalkeyEntry.pack(anchor=E)
|
||||||
|
|
||||||
triforcepieceFrame = Frame(itemList5)
|
triforcepieceFrame = Frame(itemList5)
|
||||||
triforcepieceLabel = Label(triforcepieceFrame, text='Triforce Piece')
|
triforcepieceLabel = Label(triforcepieceFrame, text='Triforce Piece')
|
||||||
triforcepieceVar = StringVar(value='0')
|
triforcepieceVar = StringVar(value='0')
|
||||||
|
@ -978,6 +990,7 @@ def guiMain(args=None):
|
||||||
createSpoilerVar.set(int(args.create_spoiler))
|
createSpoilerVar.set(int(args.create_spoiler))
|
||||||
suppressRomVar.set(int(args.suppress_rom))
|
suppressRomVar.set(int(args.suppress_rom))
|
||||||
keysanityVar.set(args.keysanity)
|
keysanityVar.set(args.keysanity)
|
||||||
|
retroVar.set(args.retro)
|
||||||
if args.nodungeonitems:
|
if args.nodungeonitems:
|
||||||
dungeonItemsVar.set(int(not args.nodungeonitems))
|
dungeonItemsVar.set(int(not args.nodungeonitems))
|
||||||
beatableOnlyVar.set(int(args.beatableonly))
|
beatableOnlyVar.set(int(args.beatableonly))
|
||||||
|
|
232
ItemList.py
232
ItemList.py
|
@ -2,69 +2,70 @@ from collections import namedtuple
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from Items import ItemFactory
|
from BaseClasses import Region, RegionType, Shop, ShopType, Location
|
||||||
from Fill import FillError, fill_restrictive
|
|
||||||
from Dungeons import get_dungeon_item_pool
|
from Dungeons import get_dungeon_item_pool
|
||||||
|
from EntranceShuffle import connect_entrance
|
||||||
|
from Fill import FillError, fill_restrictive
|
||||||
|
from Items import ItemFactory
|
||||||
|
|
||||||
|
|
||||||
#This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
#This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
||||||
#Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
|
#Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
|
||||||
|
|
||||||
alwaysitems = ['Bombos', 'Book of Mudora', 'Bow', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp',
|
alwaysitems = ['Bombos', 'Book of Mudora', 'Bow', 'Cane of Somaria', 'Ether', 'Fire Rod', 'Flippers', 'Ocarina', 'Hammer', 'Hookshot', 'Ice Rod', 'Lamp',
|
||||||
'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Bug Catching Net', 'Cane of Byrna']
|
'Cape', 'Magic Powder', 'Mushroom', 'Pegasus Boots', 'Quake', 'Shovel', 'Bug Catching Net', 'Cane of Byrna', 'Blue Boomerang', 'Red Boomerang']
|
||||||
progressivegloves = ['Progressive Glove'] * 2
|
progressivegloves = ['Progressive Glove'] * 2
|
||||||
basicgloves = ['Power Glove', 'Titans Mitts']
|
basicgloves = ['Power Glove', 'Titans Mitts']
|
||||||
|
|
||||||
normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
normalbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Fairy)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
||||||
hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
hardbottles = ['Bottle', 'Bottle (Red Potion)', 'Bottle (Green Potion)', 'Bottle (Blue Potion)', 'Bottle (Bee)', 'Bottle (Good Bee)']
|
||||||
|
|
||||||
normalbaseitems = (['Blue Boomerang', 'Red Boomerang', 'Silver Arrows', 'Magic Upgrade (1/2)'] + ['Rupees (300)'] * 4 +
|
normalbaseitems = (['Silver Arrows', 'Magic Upgrade (1/2)', 'Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] +
|
||||||
['Single Arrow', 'Sanctuary Heart Container', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
|
['Rupees (300)'] * 4 + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 24)
|
||||||
normalfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6
|
normalfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6
|
||||||
normalsecond15extra = ['Bombs (3)'] * 10 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)']
|
normalsecond15extra = ['Bombs (3)'] * 9 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)'] + ['Bombs (10)']
|
||||||
normalthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Arrows (10)', 'Rupee (1)', 'Rupees (5)']
|
normalthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Arrows (10)', 'Rupee (1)', 'Rupees (5)']
|
||||||
normalfourth5extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2 + ['Rupees (5)']
|
normalfourth5extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2 + ['Rupees (5)']
|
||||||
normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
|
normalfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
|
||||||
|
|
||||||
|
|
||||||
easybaseitems = (['Blue Boomerang', 'Red Boomerang', 'Sanctuary Heart Container'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 + ['Lamp'] * 2 +
|
easybaseitems = (['Sanctuary Heart Container'] + ['Rupees (300)'] * 4 + ['Magic Upgrade (1/2)'] * 2 + ['Lamp'] * 2 + ['Silver Arrows'] * 2 +
|
||||||
['Silver Arrows'] * 2 + ['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12)
|
['Boss Heart Container'] * 10 + ['Piece of Heart'] * 12)
|
||||||
easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)']
|
easyextra = ['Piece of Heart'] * 12 + ['Rupees (300)']
|
||||||
easylimitedextra = ['Boss Heart Container'] * 3 # collapsing down the 12 pieces of heart
|
easylimitedextra = ['Boss Heart Container'] * 3 # collapsing down the 12 pieces of heart
|
||||||
easyfirst15extra = ['Rupees (100)', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6
|
easyfirst15extra = ['Rupees (100)', 'Arrow Upgrade (+10)', 'Bomb Upgrade (+10)'] + ['Arrow Upgrade (+5)'] * 6 + ['Bomb Upgrade (+5)'] * 6
|
||||||
easysecond10extra = ['Bombs (3)'] * 8 + ['Rupee (1)', 'Rupees (50)']
|
easysecond10extra = ['Bombs (3)'] * 7 + ['Rupee (1)', 'Rupees (50)', 'Bombs (10)']
|
||||||
easythird5extra = ['Rupees (50)'] * 2 + ['Bombs (3)'] * 2 + ['Arrows (10)']
|
easythird5extra = ['Rupees (50)'] * 2 + ['Bombs (3)'] * 2 + ['Arrows (10)']
|
||||||
easyfinal25extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 14 + ['Rupee (1)'] + ['Arrows (10)'] * 4 + ['Rupees (5)'] * 2
|
easyfinal25extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 14 + ['Rupee (1)'] + ['Arrows (10)'] * 4 + ['Rupees (5)'] * 2
|
||||||
easytimedotherextra = ['Red Clock'] * 5
|
easytimedotherextra = ['Red Clock'] * 5
|
||||||
|
|
||||||
hardbaseitems = (['Silver Arrows', 'Single Arrow', 'Single Bomb'] + ['Rupees (300)'] + ['Rupees (100)'] * 3 + ['Rupees (50)'] * 5 + ['Bombs (3)'] * 5 +
|
hardbaseitems = ['Silver Arrows', 'Single Arrow', 'Bombs (10)'] + ['Rupees (300)'] * 4 + ['Boss Heart Container'] * 6 + ['Piece of Heart'] * 20 + ['Rupees (5)'] * 7 + ['Bombs (3)'] * 4
|
||||||
['Boss Heart Container'] * 5 + ['Piece of Heart'] * 24)
|
hardfirst20extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Bombs (3)'] * 5 + ['Rupees (5)'] * 10 + ['Arrows (10)', 'Rupee (1)']
|
||||||
hardfirst20extra = ['Single Bomb'] * 7 + ['Rupees (5)'] * 8 + ['Rupee (1)'] * 2 + ['Rupees (20)'] * 2 + ['Arrows (10)']
|
hardsecond10extra = ['Rupees (5)'] * 5 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupee (1)']
|
||||||
hardsecond10extra = ['Rupees (5)'] * 7 + ['Rupee (1)'] * 3
|
hardthird10extra = ['Rupees (50)'] * 4 + ['Rupees (20)'] * 3 + ['Rupees (5)'] * 3
|
||||||
hardthird10extra = ['Arrows (10)'] * 4 + ['Rupees (20)'] * 3 + ['Single Bomb'] * 3
|
hardfourth10extra = ['Arrows (10)'] * 2 + ['Rupees (20)'] * 7 + ['Rupees (5)']
|
||||||
hardfourth10extra = ['Rupees (5)'] * 3 + ['Single Arrow'] * 5 + ['Single Bomb'] * 2
|
hardfinal20extra = ['Rupees (20)'] * 18 + ['Rupees (5)'] * 2
|
||||||
hardfinal20extra = ['Single Bomb'] * 4 + ['Rupees (5)'] * 2 + ['Single Arrow'] * 14
|
|
||||||
|
|
||||||
expertbaseitems = (['Single Arrow', 'Rupees (300)', 'Rupees (100)', 'Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 5 +
|
expertbaseitems = (['Rupees (300)'] * 4 + ['Single Arrow', 'Silver Arrows', 'Boss Heart Container', 'Rupee (1)', 'Bombs (10)'] + ['Piece of Heart'] * 20 + ['Rupees (5)'] * 2 +
|
||||||
['Rupees (20)'] * 3 + ['Single Bomb'] * 10 + ['Piece of Heart'] * 24)
|
['Bombs (3)'] * 9 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupees (20)'] * 2)
|
||||||
expertfirst15extra = ['Single Bomb'] * 7 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 5
|
expertfirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Rupees (5)'] * 12
|
||||||
expertsecond15extra = ['Single Bomb'] * 6 + ['Single Arrow'] * 4 + ['Rupee (1)'] * 5
|
expertsecond15extra = ['Rupees (5)'] * 10 + ['Rupees (20)'] * 5
|
||||||
expertthird10extra = ['Rupees (5)'] * 3 + ['Single Bomb'] * 3 + ['Rupees (20)'] * 2 + ['Single Arrow'] * 2
|
expertthird10extra = ['Rupees (50)'] * 4 + ['Rupees (5)'] * 2 + ['Arrows (10)'] * 3 + ['Rupee (1)']
|
||||||
expertfourth5extra = ['Rupees (5)'] * 2 + ['Single Arrow'] * 3
|
expertfourth5extra = ['Rupees (5)'] * 5
|
||||||
expertfinal25extra = ['Single Bomb'] * 4 + ['Rupees (20)'] * 3 + ['Single Arrow'] * 18
|
expertfinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
|
||||||
|
|
||||||
insanebaseitems = (['Bombs (3)', 'Arrows (10)'] + ['Rupees (50)'] * 4 + ['Rupees (5)'] * 10 + ['Rupees (300)'] * 5 + ['Rupees (100)'] * 4 +
|
insanebaseitems = ['Rupees (300)'] * 4 + ['Single Arrow', 'Bombs (10)', 'Rupee (1)'] + ['Rupees (5)'] * 24 + ['Bombs (3)'] * 9 + ['Rupees (50)'] * 2 + ['Arrows (10)'] * 2 + ['Rupees (20)'] * 5
|
||||||
['Rupee (1)'] * 8 + ['Rupees (20)'] * 4 + ['Single Bomb'] * 8 + ['Single Arrow'] * 6)
|
insanefirst15extra = ['Rupees (100)', 'Rupees (300)', 'Rupees (50)'] + ['Rupees (5)'] * 12
|
||||||
insanefirst15extra = ['Single Bomb'] * 5 + ['Single Arrow'] * 4 + ['Rupee (1)'] * 5 + ['Rupees (20)']
|
insanesecond15extra = ['Rupees (5)'] * 10 + ['Rupees (20)'] * 5
|
||||||
insanesecond15extra = ['Single Bomb'] * 5 + ['Single Arrow'] * 5 + ['Rupee (1)'] * 5
|
insanethird10extra = ['Rupees (50)'] * 4 + ['Rupees (5)'] * 2 + ['Arrows (10)'] * 3 + ['Rupee (1)']
|
||||||
insanethird10extra = ['Single Bomb'] * 4 + ['Single Arrow'] * 3 + ['Rupee (1)'] * 3
|
insanefourth5extra = ['Rupees (5)'] * 5
|
||||||
insanefourth5extra = ['Single Bomb'] + ['Single Arrow'] * 2 + ['Rupee (1)'] * 2
|
insanefinal25extra = ['Rupees (20)'] * 23 + ['Rupees (5)'] * 2
|
||||||
insanefinal25extra = ['Single Bomb'] * 2 + ['Single Arrow'] * 10 + ['Rupee (1)'] * 7 + ['Rupees (20)'] * 6
|
|
||||||
|
|
||||||
Difficulty = namedtuple('Difficulty',
|
Difficulty = namedtuple('Difficulty',
|
||||||
['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield',
|
['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield',
|
||||||
'basicshield', 'progressivearmor', 'basicarmor', 'swordless',
|
'basicshield', 'progressivearmor', 'basicarmor', 'swordless',
|
||||||
'progressivesword', 'basicsword', 'timedohko', 'timedother',
|
'progressivesword', 'basicsword', 'timedohko', 'timedother',
|
||||||
'triforcehunt', 'triforce_pieces_required', 'conditional_extras',
|
'triforcehunt', 'triforce_pieces_required', 'retro', 'conditional_extras',
|
||||||
'extras', 'progressive_sword_limit', 'progressive_shield_limit',
|
'extras', 'progressive_sword_limit', 'progressive_shield_limit',
|
||||||
'progressive_armor_limit', 'progressive_bottle_limit'])
|
'progressive_armor_limit', 'progressive_bottle_limit'])
|
||||||
|
|
||||||
|
@ -78,7 +79,7 @@ def easy_conditional_extras(timer, _goal, _mode, pool, placed_items):
|
||||||
return easytimedotherextra
|
return easytimedotherextra
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def no_conditonal_extras(*_args):
|
def no_conditional_extras(*_args):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,7 +100,8 @@ difficulties = {
|
||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
triforcehunt = ['Triforce Piece'] * 30,
|
triforcehunt = ['Triforce Piece'] * 30,
|
||||||
triforce_pieces_required = 20,
|
triforce_pieces_required = 20,
|
||||||
conditional_extras = no_conditonal_extras,
|
retro = ['Small Key (Universal)'] * 17 + ['Rupees (20)'] * 10,
|
||||||
|
conditional_extras = no_conditional_extras,
|
||||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||||
progressive_sword_limit = 4,
|
progressive_sword_limit = 4,
|
||||||
progressive_shield_limit = 3,
|
progressive_shield_limit = 3,
|
||||||
|
@ -122,6 +124,7 @@ difficulties = {
|
||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5, # +5 more Red Clocks if there is room
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 5, # +5 more Red Clocks if there is room
|
||||||
triforcehunt = ['Triforce Piece'] * 30,
|
triforcehunt = ['Triforce Piece'] * 30,
|
||||||
triforce_pieces_required = 20,
|
triforce_pieces_required = 20,
|
||||||
|
retro = ['Small Key (Universal)'] * 27,
|
||||||
conditional_extras = easy_conditional_extras,
|
conditional_extras = easy_conditional_extras,
|
||||||
extras = [easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra],
|
extras = [easyextra, easyfirst15extra, easysecond10extra, easythird5extra, easyfinal25extra],
|
||||||
progressive_sword_limit = 4,
|
progressive_sword_limit = 4,
|
||||||
|
@ -145,41 +148,43 @@ difficulties = {
|
||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
triforcehunt = ['Triforce Piece'] * 30,
|
triforcehunt = ['Triforce Piece'] * 30,
|
||||||
triforce_pieces_required = 20,
|
triforce_pieces_required = 20,
|
||||||
conditional_extras = no_conditonal_extras,
|
retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
|
||||||
|
conditional_extras = no_conditional_extras,
|
||||||
extras = [hardfirst20extra, hardsecond10extra, hardthird10extra, hardfourth10extra, hardfinal20extra],
|
extras = [hardfirst20extra, hardsecond10extra, hardthird10extra, hardfourth10extra, hardfinal20extra],
|
||||||
progressive_sword_limit = 3,
|
progressive_sword_limit = 3,
|
||||||
progressive_shield_limit = 2,
|
progressive_shield_limit = 2,
|
||||||
progressive_armor_limit = 1,
|
progressive_armor_limit = 1,
|
||||||
progressive_bottle_limit = 2,
|
progressive_bottle_limit = 4,
|
||||||
),
|
),
|
||||||
'expert': Difficulty(
|
'expert': Difficulty(
|
||||||
baseitems = expertbaseitems,
|
baseitems = expertbaseitems,
|
||||||
bottles = hardbottles,
|
bottles = hardbottles,
|
||||||
bottle_count = 4,
|
bottle_count = 4,
|
||||||
same_bottle = True,
|
same_bottle = False,
|
||||||
progressiveshield = [],
|
progressiveshield = ['Progressive Shield'] * 3,
|
||||||
basicshield = [],
|
basicshield = ['Progressive Shield'] * 3, #only the first one will upgrade, making this equivalent to two blue shields
|
||||||
progressivearmor = [],
|
progressivearmor = [],
|
||||||
basicarmor = [],
|
basicarmor = [],
|
||||||
swordless = ['Rupees (20)'] * 3 + ['Silver Arrows'],
|
swordless = ['Rupees (20)'] * 4,
|
||||||
progressivesword = ['Progressive Sword'] * 3,
|
progressivesword = ['Progressive Sword'] * 3,
|
||||||
basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'],
|
basicsword = ['Fighter Sword', 'Master Sword', 'Master Sword'],
|
||||||
timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5,
|
timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5,
|
||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
triforcehunt = ['Triforce Piece'] * 30,
|
triforcehunt = ['Triforce Piece'] * 30,
|
||||||
triforce_pieces_required = 20,
|
triforce_pieces_required = 20,
|
||||||
conditional_extras = no_conditonal_extras,
|
retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
|
||||||
|
conditional_extras = no_conditional_extras,
|
||||||
extras = [expertfirst15extra, expertsecond15extra, expertthird10extra, expertfourth5extra, expertfinal25extra],
|
extras = [expertfirst15extra, expertsecond15extra, expertthird10extra, expertfourth5extra, expertfinal25extra],
|
||||||
progressive_sword_limit = 2,
|
progressive_sword_limit = 2,
|
||||||
progressive_shield_limit = 0,
|
progressive_shield_limit = 1,
|
||||||
progressive_armor_limit = 0,
|
progressive_armor_limit = 0,
|
||||||
progressive_bottle_limit = 1,
|
progressive_bottle_limit = 4,
|
||||||
),
|
),
|
||||||
'insane': Difficulty(
|
'insane': Difficulty(
|
||||||
baseitems = insanebaseitems,
|
baseitems = insanebaseitems,
|
||||||
bottles = hardbottles,
|
bottles = hardbottles,
|
||||||
bottle_count = 4,
|
bottle_count = 4,
|
||||||
same_bottle = True,
|
same_bottle = False,
|
||||||
progressiveshield = [],
|
progressiveshield = [],
|
||||||
basicshield = [],
|
basicshield = [],
|
||||||
progressivearmor = [],
|
progressivearmor = [],
|
||||||
|
@ -191,12 +196,13 @@ difficulties = {
|
||||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||||
triforcehunt = ['Triforce Piece'] * 30,
|
triforcehunt = ['Triforce Piece'] * 30,
|
||||||
triforce_pieces_required = 20,
|
triforce_pieces_required = 20,
|
||||||
conditional_extras = no_conditonal_extras,
|
retro = ['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 15,
|
||||||
|
conditional_extras = no_conditional_extras,
|
||||||
extras = [insanefirst15extra, insanesecond15extra, insanethird10extra, insanefourth5extra, insanefinal25extra],
|
extras = [insanefirst15extra, insanesecond15extra, insanethird10extra, insanefourth5extra, insanefinal25extra],
|
||||||
progressive_sword_limit = 2,
|
progressive_sword_limit = 2,
|
||||||
progressive_shield_limit = 0,
|
progressive_shield_limit = 0,
|
||||||
progressive_armor_limit = 0,
|
progressive_armor_limit = 0,
|
||||||
progressive_bottle_limit = 1,
|
progressive_bottle_limit = 4,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,13 +220,21 @@ def generate_itempool(world):
|
||||||
world.get_location('Agahnim 1').event = True
|
world.get_location('Agahnim 1').event = True
|
||||||
world.push_item('Agahnim 2', ItemFactory('Beat Agahnim 2'), False)
|
world.push_item('Agahnim 2', ItemFactory('Beat Agahnim 2'), False)
|
||||||
world.get_location('Agahnim 2').event = True
|
world.get_location('Agahnim 2').event = True
|
||||||
|
world.push_item('Dark Blacksmith Ruins', ItemFactory('Pick Up Purple Chest'), False)
|
||||||
|
world.get_location('Dark Blacksmith Ruins').event = True
|
||||||
|
world.push_item('Frog', ItemFactory('Get Frog'), False)
|
||||||
|
world.get_location('Frog').event = True
|
||||||
|
world.push_item('Missing Smith', ItemFactory('Return Smith'), False)
|
||||||
|
world.get_location('Missing Smith').event = True
|
||||||
|
world.push_item('Floodgate', ItemFactory('Open Floodgate'), False)
|
||||||
|
world.get_location('Floodgate').event = True
|
||||||
|
|
||||||
# set up item pool
|
# set up item pool
|
||||||
if world.custom:
|
if world.custom:
|
||||||
(pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.customitemarray)
|
(pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.retro, world.customitemarray)
|
||||||
world.rupoor_cost = min(world.customitemarray[67], 9999)
|
world.rupoor_cost = min(world.customitemarray[67], 9999)
|
||||||
else:
|
else:
|
||||||
(pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode)
|
(pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle, world.difficulty, world.timer, world.goal, world.mode, world.retro)
|
||||||
world.itempool = ItemFactory(pool)
|
world.itempool = ItemFactory(pool)
|
||||||
for (location, item) in placed_items:
|
for (location, item) in placed_items:
|
||||||
world.push_item(location, ItemFactory(item), False)
|
world.push_item(location, ItemFactory(item), False)
|
||||||
|
@ -251,9 +265,84 @@ def generate_itempool(world):
|
||||||
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
tr_medallion = ['Ether', 'Quake', 'Bombos'][random.randint(0, 2)]
|
||||||
world.required_medallions = (mm_medallion, tr_medallion)
|
world.required_medallions = (mm_medallion, tr_medallion)
|
||||||
|
|
||||||
|
set_up_shops(world)
|
||||||
|
|
||||||
|
if world.retro:
|
||||||
|
set_up_take_anys(world)
|
||||||
|
|
||||||
|
create_dynamic_shop_locations(world)
|
||||||
|
|
||||||
# distribute crystals
|
# distribute crystals
|
||||||
fill_prizes(world)
|
fill_prizes(world)
|
||||||
|
|
||||||
|
take_any_locations = [
|
||||||
|
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
|
||||||
|
'Fortune Teller (Light)', 'Lake Hylia Fortune Teller', 'Lumberjack House', 'Bonk Fairy (Light)',
|
||||||
|
'Bonk Fairy (Dark)', 'Lake Hylia Healer Fairy', 'Swamp Healer Fairy', 'Desert Healer Fairy',
|
||||||
|
'Dark Lake Hylia Healer Fairy', 'Dark Lake Hylia Ledge Healer Fairy', 'Dark Desert Healer Fairy',
|
||||||
|
'Dark Death Mountain Healer Fairy', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave',
|
||||||
|
'Kakariko Gamble Game', 'Capacity Upgrade', '50 Rupee Cave', 'Lost Woods Gamble', 'Hookshot Fairy',
|
||||||
|
'Palace of Darkness Hint', 'East Dark World Hint', 'Archery Game', 'Dark Lake Hylia Ledge Hint',
|
||||||
|
'Dark Lake Hylia Ledge Spike Cave', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint', 'Dark Desert Hint']
|
||||||
|
|
||||||
|
def set_up_take_anys(world):
|
||||||
|
regions = random.sample(take_any_locations, 5)
|
||||||
|
|
||||||
|
old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave)
|
||||||
|
world.regions.append(old_man_take_any)
|
||||||
|
world.dynamic_regions.append(old_man_take_any)
|
||||||
|
|
||||||
|
reg = regions.pop()
|
||||||
|
entrance = world.get_region(reg).entrances[0]
|
||||||
|
connect_entrance(world, entrance, old_man_take_any)
|
||||||
|
entrance.target = 0x58
|
||||||
|
old_man_take_any.shop = Shop(old_man_take_any, 0x0112, ShopType.TakeAny, 0xE2, True)
|
||||||
|
world.shops.append(old_man_take_any.shop)
|
||||||
|
old_man_take_any.shop.active = True
|
||||||
|
|
||||||
|
swords = [item for item in world.itempool if item.type == 'Sword']
|
||||||
|
if swords:
|
||||||
|
sword = random.choice(swords)
|
||||||
|
world.itempool.remove(sword)
|
||||||
|
world.itempool.append(ItemFactory('Rupees (20)'))
|
||||||
|
old_man_take_any.shop.add_inventory(0, sword.name, 0, 0, create_location=True)
|
||||||
|
else:
|
||||||
|
old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0)
|
||||||
|
|
||||||
|
for num in range(4):
|
||||||
|
take_any = Region("Take-Any #{}".format(num+1), RegionType.Cave)
|
||||||
|
world.regions.append(take_any)
|
||||||
|
world.dynamic_regions.append(take_any)
|
||||||
|
|
||||||
|
target, room_id = random.choice([(0x58, 0x0112), (0x60, 0x010F), (0x46, 0x011F)])
|
||||||
|
reg = regions.pop()
|
||||||
|
entrance = world.get_region(reg).entrances[0]
|
||||||
|
connect_entrance(world, entrance, take_any)
|
||||||
|
entrance.target = target
|
||||||
|
take_any.shop = Shop(take_any, room_id, ShopType.TakeAny, 0xE3, True)
|
||||||
|
world.shops.append(take_any.shop)
|
||||||
|
take_any.shop.active = True
|
||||||
|
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
|
||||||
|
take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)
|
||||||
|
|
||||||
|
world.intialize_regions()
|
||||||
|
|
||||||
|
def create_dynamic_shop_locations(world):
|
||||||
|
for shop in world.shops:
|
||||||
|
for i, item in enumerate(shop.inventory):
|
||||||
|
if item is None:
|
||||||
|
continue
|
||||||
|
if item['create_location']:
|
||||||
|
loc = Location("{} Item {}".format(shop.region.name, i+1), parent=shop.region)
|
||||||
|
shop.region.locations.append(loc)
|
||||||
|
world.dynamic_locations.append(loc)
|
||||||
|
|
||||||
|
world.clear_location_cache()
|
||||||
|
|
||||||
|
world.push_item(loc, ItemFactory(item['item']), False)
|
||||||
|
loc.event = True
|
||||||
|
|
||||||
|
|
||||||
def fill_prizes(world, attempts=15):
|
def fill_prizes(world, attempts=15):
|
||||||
crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'])
|
crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'])
|
||||||
crystal_locations = [world.get_location('Turtle Rock - Prize'), world.get_location('Eastern Palace - Prize'), world.get_location('Desert Palace - Prize'), world.get_location('Tower of Hera - Prize'), world.get_location('Palace of Darkness - Prize'),
|
crystal_locations = [world.get_location('Turtle Rock - Prize'), world.get_location('Eastern Palace - Prize'), world.get_location('Desert Palace - Prize'), world.get_location('Tower of Hera - Prize'), world.get_location('Palace of Darkness - Prize'),
|
||||||
|
@ -281,9 +370,26 @@ def fill_prizes(world, attempts=15):
|
||||||
raise FillError('Unable to place dungeon prizes')
|
raise FillError('Unable to place dungeon prizes')
|
||||||
|
|
||||||
|
|
||||||
|
def set_up_shops(world):
|
||||||
|
# Changes to basic Shops
|
||||||
|
# TODO: move hard+ mode changes for sheilds here, utilizing the new shops
|
||||||
|
|
||||||
|
if world.retro:
|
||||||
|
rss = world.get_region('Red Shield Shop').shop
|
||||||
|
rss.active = True
|
||||||
|
rss.add_inventory(2, 'Single Arrow', 80)
|
||||||
|
|
||||||
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode):
|
# Randomized changes to Shops
|
||||||
|
if world.retro:
|
||||||
|
for shop in random.sample([s for s in world.shops if s.replaceable], 5):
|
||||||
|
shop.active = True
|
||||||
|
shop.add_inventory(0, 'Single Arrow', 80)
|
||||||
|
shop.add_inventory(1, 'Small Key (Universal)', 100)
|
||||||
|
shop.add_inventory(2, 'Bombs (10)', 50)
|
||||||
|
|
||||||
|
#special shop types
|
||||||
|
|
||||||
|
def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro):
|
||||||
pool = []
|
pool = []
|
||||||
placed_items = []
|
placed_items = []
|
||||||
clock_mode = None
|
clock_mode = None
|
||||||
|
@ -382,9 +488,20 @@ def get_pool_core(progressive, shuffle, difficulty, timer, goal, mode):
|
||||||
|
|
||||||
if goal == 'pedestal':
|
if goal == 'pedestal':
|
||||||
placed_items.append(('Master Sword Pedestal', 'Triforce'))
|
placed_items.append(('Master Sword Pedestal', 'Triforce'))
|
||||||
|
if retro:
|
||||||
|
pool = [item.replace('Single Arrow','Rupees (5)') for item in pool]
|
||||||
|
pool = [item.replace('Arrows (10)','Rupees (5)') for item in pool]
|
||||||
|
pool = [item.replace('Arrow Upgrade (+5)','Rupees (5)') for item in pool]
|
||||||
|
pool = [item.replace('Arrow Upgrade (+10)','Rupees (5)') for item in pool]
|
||||||
|
pool.extend(diff.retro)
|
||||||
|
if mode == 'standard':
|
||||||
|
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||||
|
placed_items.append((key_location, 'Small Key (Universal)'))
|
||||||
|
else:
|
||||||
|
pool.extend(['Small Key (Universal)'])
|
||||||
return (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
return (pool, placed_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms)
|
||||||
|
|
||||||
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, customitemarray):
|
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, retro, customitemarray):
|
||||||
pool = []
|
pool = []
|
||||||
placed_items = []
|
placed_items = []
|
||||||
clock_mode = None
|
clock_mode = None
|
||||||
|
@ -401,6 +518,7 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, c
|
||||||
for x in range(0, 65):
|
for x in range(0, 65):
|
||||||
itemtotal = itemtotal + customitemarray[x]
|
itemtotal = itemtotal + customitemarray[x]
|
||||||
itemtotal = itemtotal + customitemarray[66]
|
itemtotal = itemtotal + customitemarray[66]
|
||||||
|
itemtotal = itemtotal + customitemarray[68]
|
||||||
|
|
||||||
pool.extend(['Bow'] * customitemarray[0])
|
pool.extend(['Bow'] * customitemarray[0])
|
||||||
pool.extend(['Silver Arrows']* customitemarray[1])
|
pool.extend(['Silver Arrows']* customitemarray[1])
|
||||||
|
@ -508,9 +626,16 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, c
|
||||||
placed_items.append(('Link\'s Uncle', 'Progressive Sword'))
|
placed_items.append(('Link\'s Uncle', 'Progressive Sword'))
|
||||||
pool.extend(['Fighter Sword'] * customitemarray[32])
|
pool.extend(['Fighter Sword'] * customitemarray[32])
|
||||||
pool.extend(['Progressive Sword'] * max((customitemarray[36] - 1), 0))
|
pool.extend(['Progressive Sword'] * max((customitemarray[36] - 1), 0))
|
||||||
|
if retro:
|
||||||
|
key_location = random.choice(['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||||
|
placed_items.append((key_location, 'Small Key (Universal)'))
|
||||||
|
pool.extend(['Small Key (Universal)'] * max((customitemarray[68] - 1), 0))
|
||||||
|
else:
|
||||||
|
pool.extend(['Small Key (Universal)'] * customitemarray[68])
|
||||||
else:
|
else:
|
||||||
pool.extend(['Fighter Sword'] * customitemarray[32])
|
pool.extend(['Fighter Sword'] * customitemarray[32])
|
||||||
pool.extend(['Progressive Sword'] * customitemarray[36])
|
pool.extend(['Progressive Sword'] * customitemarray[36])
|
||||||
|
pool.extend(['Small Key (Universal)'] * customitemarray[68])
|
||||||
|
|
||||||
if shuffle == 'insanity_legacy':
|
if shuffle == 'insanity_legacy':
|
||||||
placed_items.append(('Link\'s House', 'Magic Mirror'))
|
placed_items.append(('Link\'s House', 'Magic Mirror'))
|
||||||
|
@ -521,6 +646,8 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, c
|
||||||
pool.extend(['Magic Mirror'] * customitemarray[22])
|
pool.extend(['Magic Mirror'] * customitemarray[22])
|
||||||
pool.extend(['Moon Pearl'] * customitemarray[28])
|
pool.extend(['Moon Pearl'] * customitemarray[28])
|
||||||
|
|
||||||
|
if retro:
|
||||||
|
itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in Retro Mode
|
||||||
if itemtotal < total_items_to_place:
|
if itemtotal < total_items_to_place:
|
||||||
pool.extend(['Nothing'] * (total_items_to_place - itemtotal))
|
pool.extend(['Nothing'] * (total_items_to_place - itemtotal))
|
||||||
|
|
||||||
|
@ -534,15 +661,18 @@ def test():
|
||||||
for mode in ['open', 'standard', 'swordless']:
|
for mode in ['open', 'standard', 'swordless']:
|
||||||
for progressive in ['on', 'off']:
|
for progressive in ['on', 'off']:
|
||||||
for shuffle in ['full', 'insane']:
|
for shuffle in ['full', 'insane']:
|
||||||
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode)
|
for retro in [True, False]:
|
||||||
|
out = get_pool_core(progressive, shuffle, difficulty, timer, goal, mode, retro)
|
||||||
count = len(out[0]) + len(out[1])
|
count = len(out[0]) + len(out[1])
|
||||||
|
|
||||||
correct_count = total_items_to_place
|
correct_count = total_items_to_place
|
||||||
if goal in ['pedestal']:
|
if goal in ['pedestal']:
|
||||||
# pedestal goals generate one extra item
|
# pedestal goals generate one extra item
|
||||||
correct_count += 1
|
correct_count += 1
|
||||||
|
if retro:
|
||||||
|
correct_count += 28
|
||||||
|
|
||||||
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode))
|
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, retro))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
test()
|
test()
|
||||||
|
|
24
Items.py
24
Items.py
|
@ -52,11 +52,11 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
||||||
'Bottle (Fairy)': (True, False, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again'),
|
'Bottle (Fairy)': (True, False, None, 0x3D, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again'),
|
||||||
'Bottle (Bee)': (True, False, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again'),
|
'Bottle (Bee)': (True, False, None, 0x3C, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again'),
|
||||||
'Bottle (Good Bee)': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again'),
|
'Bottle (Good Bee)': (True, False, None, 0x48, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again'),
|
||||||
'Master Sword': (True, False, None, 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again'),
|
'Master Sword': (True, False, 'Sword', 0x50, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again'),
|
||||||
'Tempered Sword': (True, False, None, 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again'),
|
'Tempered Sword': (True, False, 'Sword', 0x02, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again'),
|
||||||
'Fighter Sword': (True, False, None, 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again'),
|
'Fighter Sword': (True, False, 'Sword', 0x49, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again'),
|
||||||
'Golden Sword': (True, False, None, 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again'),
|
'Golden Sword': (True, False, 'Sword', 0x03, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again'),
|
||||||
'Progressive Sword': (True, False, None, 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again'),
|
'Progressive Sword': (True, False, 'Sword', 0x5E, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again'),
|
||||||
'Progressive Glove': (True, False, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again'),
|
'Progressive Glove': (True, False, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again'),
|
||||||
'Silver Arrows': (True, False, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again'),
|
'Silver Arrows': (True, False, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again'),
|
||||||
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], None, None, None, None, None, None),
|
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], None, None, None, None, None, None),
|
||||||
|
@ -78,6 +78,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
||||||
'Arrow Upgrade (+5)': (False, False, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again'),
|
'Arrow Upgrade (+5)': (False, False, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again'),
|
||||||
'Single Bomb': (False, False, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again'),
|
'Single Bomb': (False, False, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again'),
|
||||||
'Bombs (3)': (False, False, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again'),
|
'Bombs (3)': (False, False, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again'),
|
||||||
|
'Bombs (10)': (False, False, None, 0x31, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again'),
|
||||||
'Bomb Upgrade (+10)': (False, False, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again'),
|
'Bomb Upgrade (+10)': (False, False, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again'),
|
||||||
'Bomb Upgrade (+5)': (False, False, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again'),
|
'Bomb Upgrade (+5)': (False, False, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again'),
|
||||||
'Blue Mail': (False, True, None, 0x22, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again'),
|
'Blue Mail': (False, True, None, 0x22, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again'),
|
||||||
|
@ -157,6 +158,17 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
|
||||||
'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 'A big key to the evil tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
'Big Key (Ganons Tower)': (False, False, 'BigKey', 0x92, 'A big key to the evil tower', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again'),
|
||||||
'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 'Now you can find Agahnim!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
'Compass (Ganons Tower)': (False, True, 'Compass', 0x82, 'Now you can find Agahnim!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again'),
|
||||||
'Map (Ganons Tower)': (False, True, 'Map', 0x72, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
'Map (Ganons Tower)': (False, True, 'Map', 0x72, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again'),
|
||||||
|
'Small Key (Universal)': (False, True, None, 0xAF, 'A small key for any door', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again'),
|
||||||
'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again'),
|
'Nothing': (False, False, None, 0x5A, 'Some Hot Air', 'and the Nothing', 'the zen kid', 'outright theft', 'shroom theft', 'empty boy is bored again'),
|
||||||
|
'Red Potion': (False, False, None, 0x2E, None, None, None, None, None, None),
|
||||||
|
'Green Potion': (False, False, None, 0x2F, None, None, None, None, None, None),
|
||||||
|
'Blue Potion': (False, False, None, 0x30, None, None, None, None, None, None),
|
||||||
|
'Bee': (False, False, None, 0x0E, None, None, None, None, None, None),
|
||||||
|
'Small Heart': (False, False, None, 0x42, None, None, None, None, None, None),
|
||||||
'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None),
|
'Beat Agahnim 1': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||||
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None)}
|
'Beat Agahnim 2': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||||
|
'Get Frog': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||||
|
'Return Smith': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||||
|
'Pick Up Purple Chest': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||||
|
'Open Floodgate': (True, False, 'Event', None, None, None, None, None, None, None),
|
||||||
|
}
|
||||||
|
|
70
Main.py
70
Main.py
|
@ -1,11 +1,12 @@
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
import copy
|
||||||
from itertools import zip_longest
|
from itertools import zip_longest
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from BaseClasses import World, CollectionState, Item
|
from BaseClasses import World, CollectionState, Item, Region, Location, Entrance, Shop
|
||||||
from Regions import create_regions, mark_light_world_regions
|
from Regions import create_regions, mark_light_world_regions
|
||||||
from EntranceShuffle import link_entrances
|
from EntranceShuffle import link_entrances
|
||||||
from Rom import patch_rom, Sprite, LocalRom, JsonRom
|
from Rom import patch_rom, Sprite, LocalRom, JsonRom
|
||||||
|
@ -15,31 +16,31 @@ from Fill import distribute_items_cutoff, distribute_items_staleness, distribute
|
||||||
from ItemList import generate_itempool, difficulties
|
from ItemList import generate_itempool, difficulties
|
||||||
from Utils import output_path
|
from Utils import output_path
|
||||||
|
|
||||||
__version__ = '0.6.0'
|
__version__ = '0.6.1'
|
||||||
|
|
||||||
logic_hash = [26, 76, 4, 144, 72, 105, 234, 233, 12, 184, 95, 94, 100, 13, 15, 174,
|
logic_hash = [215, 244, 99, 97, 253, 98, 31, 150, 207, 70, 50, 78, 59, 73, 221, 191,
|
||||||
186, 135, 130, 189, 246, 254, 123, 245, 85, 241, 101, 129, 70, 255, 55, 248,
|
21, 34, 200, 116, 77, 234, 89, 27, 228, 96, 16, 249, 56, 148, 3, 176,
|
||||||
43, 146, 23, 179, 243, 208, 230, 176, 9, 88, 239, 226, 222, 203, 244, 183,
|
17, 227, 24, 20, 238, 67, 37, 219, 62, 223, 60, 123, 246, 92, 164, 177,
|
||||||
205, 74, 44, 5, 122, 220, 206, 47, 221, 125, 138, 155, 98, 79, 238, 119,
|
211, 15, 245, 23, 75, 33, 190, 124, 144, 100, 87, 57, 86, 108, 80, 181,
|
||||||
30, 24, 159, 39, 253, 27, 33, 218, 62, 82, 200, 28, 141, 191, 93, 22,
|
6, 28, 2, 71, 182, 155, 222, 229, 90, 91, 32, 126, 25, 226, 133, 41,
|
||||||
192, 54, 227, 108, 48, 78, 242, 166, 60, 250, 75, 145, 49, 212, 41, 25,
|
132, 122, 10, 30, 53, 239, 112, 49, 104, 76, 209, 247, 139, 13, 173, 113,
|
||||||
127, 89, 178, 157, 19, 158, 177, 231, 207, 66, 172, 17, 133, 61, 109, 86,
|
159, 69, 145, 161, 11, 102, 149, 143, 129, 178, 45, 217, 196, 232, 208, 119,
|
||||||
57, 143, 142, 219, 148, 209, 181, 87, 163, 40, 81, 114, 240, 103, 31, 175,
|
94, 19, 35, 65, 170, 103, 55, 109, 5, 43, 118, 194, 180, 12, 206, 241,
|
||||||
237, 185, 18, 173, 168, 45, 216, 106, 161, 16, 151, 139, 104, 134, 110, 21,
|
8, 105, 210, 231, 179, 83, 137, 18, 212, 236, 225, 66, 63, 142, 138, 131,
|
||||||
32, 131, 118, 182, 215, 67, 3, 73, 171, 71, 150, 147, 223, 247, 42, 132,
|
192, 160, 1, 198, 153, 128, 106, 165, 39, 248, 167, 22, 74, 163, 140, 157,
|
||||||
107, 149, 232, 153, 10, 201, 156, 225, 116, 194, 187, 204, 46, 165, 124, 92,
|
214, 84, 154, 127, 195, 172, 136, 168, 68, 134, 152, 95, 111, 235, 26, 42,
|
||||||
7, 0, 251, 126, 162, 80, 90, 154, 252, 197, 188, 52, 137, 117, 198, 63,
|
135, 186, 250, 7, 72, 58, 4, 9, 193, 101, 52, 44, 187, 183, 171, 184,
|
||||||
167, 38, 136, 96, 58, 11, 1, 115, 229, 224, 37, 112, 170, 59, 68, 196,
|
197, 130, 47, 189, 81, 203, 51, 110, 146, 175, 213, 88, 79, 93, 64, 107,
|
||||||
36, 64, 91, 213, 14, 180, 190, 164, 8, 56, 214, 77, 202, 193, 97, 84,
|
121, 237, 0, 46, 120, 141, 199, 158, 174, 114, 205, 201, 151, 185, 242, 29,
|
||||||
152, 83, 236, 211, 20, 217, 2, 228, 140, 69, 121, 111, 113, 128, 210, 51,
|
162, 117, 85, 54, 14, 202, 216, 169, 230, 252, 188, 251, 36, 233, 147, 82,
|
||||||
53, 6, 235, 34, 102, 29, 120, 35, 50, 65, 160, 249, 99, 169, 199, 195]
|
115, 61, 255, 38, 220, 218, 40, 224, 48, 125, 204, 156, 240, 254, 166, 243]
|
||||||
|
|
||||||
|
|
||||||
def main(args, seed=None):
|
def main(args, seed=None):
|
||||||
start = time.clock()
|
start = time.clock()
|
||||||
|
|
||||||
# initialize the world
|
# initialize the world
|
||||||
world = World(args.shuffle, args.logic, args.mode, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.beatableonly, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.custom, args.customitemarray)
|
world = World(args.shuffle, args.logic, args.mode, args.difficulty, args.timer, args.progressive, args.goal, args.algorithm, not args.nodungeonitems, args.beatableonly, args.shuffleganon, args.quickswap, args.fastmenu, args.disablemusic, args.keysanity, args.retro, args.custom, args.customitemarray)
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
if seed is None:
|
if seed is None:
|
||||||
random.seed(None)
|
random.seed(None)
|
||||||
|
@ -111,7 +112,7 @@ def main(args, seed=None):
|
||||||
else:
|
else:
|
||||||
sprite = None
|
sprite = None
|
||||||
|
|
||||||
outfilebase = 'ER_%s_%s-%s-%s%s_%s-%s%s%s%s_%s' % (world.logic, world.difficulty, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-shuffleganon" if world.shuffle_ganon else "", world.seed)
|
outfilebase = 'ER_%s_%s-%s-%s%s_%s-%s%s%s%s_%s' % (world.logic, world.difficulty, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", world.seed)
|
||||||
|
|
||||||
if not args.suppress_rom:
|
if not args.suppress_rom:
|
||||||
if args.jsonout:
|
if args.jsonout:
|
||||||
|
@ -139,7 +140,7 @@ def gt_filler(world):
|
||||||
|
|
||||||
def copy_world(world):
|
def copy_world(world):
|
||||||
# ToDo: Not good yet
|
# ToDo: Not good yet
|
||||||
ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.custom, world.customitemarray)
|
ret = World(world.shuffle, world.logic, world.mode, world.difficulty, world.timer, world.progressive, world.goal, world.algorithm, world.place_dungeon_items, world.check_beatable_only, world.shuffle_ganon, world.quickswap, world.fastmenu, world.disable_music, world.keysanity, world.retro, world.custom, world.customitemarray)
|
||||||
ret.required_medallions = list(world.required_medallions)
|
ret.required_medallions = list(world.required_medallions)
|
||||||
ret.swamp_patch_required = world.swamp_patch_required
|
ret.swamp_patch_required = world.swamp_patch_required
|
||||||
ret.ganon_at_pyramid = world.ganon_at_pyramid
|
ret.ganon_at_pyramid = world.ganon_at_pyramid
|
||||||
|
@ -157,6 +158,13 @@ def copy_world(world):
|
||||||
create_regions(ret)
|
create_regions(ret)
|
||||||
create_dungeons(ret)
|
create_dungeons(ret)
|
||||||
|
|
||||||
|
copy_dynamic_regions_and_locations(world, ret)
|
||||||
|
|
||||||
|
for shop in world.shops:
|
||||||
|
copied_shop = ret.get_region(shop.region.name).shop
|
||||||
|
copied_shop.active = shop.active
|
||||||
|
copied_shop.inventory = copy.copy(shop.inventory)
|
||||||
|
|
||||||
# connect copied world
|
# connect copied world
|
||||||
for region in world.regions:
|
for region in world.regions:
|
||||||
copied_region = ret.get_region(region.name)
|
copied_region = ret.get_region(region.name)
|
||||||
|
@ -185,6 +193,24 @@ def copy_world(world):
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def copy_dynamic_regions_and_locations(world, ret):
|
||||||
|
for region in world.dynamic_regions:
|
||||||
|
new_reg = Region(region.name, region.type)
|
||||||
|
ret.regions.append(new_reg)
|
||||||
|
ret.dynamic_regions.append(new_reg)
|
||||||
|
|
||||||
|
# Note: ideally exits should be copied here, but the current use case (Take anys) do not require this
|
||||||
|
|
||||||
|
if region.shop:
|
||||||
|
new_reg.shop = Shop(new_reg, region.shop.room_id, region.shop.type, region.shop.shopkeeper_config, region.shop.replaceable)
|
||||||
|
ret.shops.append(new_reg.shop)
|
||||||
|
|
||||||
|
for location in world.dynamic_locations:
|
||||||
|
new_loc = Location(location.name, location.address, location.crystal, location.hint_text, location.parent_region)
|
||||||
|
new_reg = ret.get_region(location.parent_region.name)
|
||||||
|
new_reg.locations.append(new_loc)
|
||||||
|
|
||||||
|
|
||||||
def create_playthrough(world):
|
def create_playthrough(world):
|
||||||
# create a copy as we will modify it
|
# create a copy as we will modify it
|
||||||
old_world = world
|
old_world = world
|
||||||
|
@ -294,8 +320,6 @@ def create_playthrough(world):
|
||||||
old_world.spoiler.paths = {location.name : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere}
|
old_world.spoiler.paths = {location.name : get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere}
|
||||||
if any(exit == 'Pyramid Fairy' for path in old_world.spoiler.paths.values() for (_, exit) in path):
|
if any(exit == 'Pyramid Fairy' for path in old_world.spoiler.paths.values() for (_, exit) in path):
|
||||||
old_world.spoiler.paths['Big Bomb Shop'] = get_path(state, world.get_region('Big Bomb Shop'))
|
old_world.spoiler.paths['Big Bomb Shop'] = get_path(state, world.get_region('Big Bomb Shop'))
|
||||||
if any(exit == 'Swamp Palace Moat' for path in old_world.spoiler.paths.values() for (_, exit) in path) or 'Sunken Treasure' in old_world.required_locations:
|
|
||||||
old_world.spoiler.paths['Dam'] = get_path(state, world.get_region('Dam'))
|
|
||||||
|
|
||||||
# we can finally output our playthrough
|
# we can finally output our playthrough
|
||||||
old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)])
|
old_world.spoiler.playthrough = OrderedDict([(str(i + 1), {str(location): str(location.item) for location in sphere}) for i, sphere in enumerate(collection_spheres)])
|
||||||
|
|
|
@ -33,7 +33,7 @@ def main(args):
|
||||||
start_time = time.clock()
|
start_time = time.clock()
|
||||||
|
|
||||||
# initialize the world
|
# initialize the world
|
||||||
world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False, False, None)
|
world = World('vanilla', 'noglitches', 'standard', 'normal', 'none', 'on', 'ganon', 'freshness', False, False, False, args.quickswap, args.fastmenu, args.disablemusic, False, False, False, None)
|
||||||
logger = logging.getLogger('')
|
logger = logging.getLogger('')
|
||||||
|
|
||||||
hasher = hashlib.md5()
|
hasher = hashlib.md5()
|
||||||
|
|
32
README.md
32
README.md
|
@ -54,6 +54,10 @@ The game can be completed without knowing how to perform glitches of any kind.
|
||||||
|
|
||||||
May require Fake Flippers, Bunny Revival.
|
May require Fake Flippers, Bunny Revival.
|
||||||
|
|
||||||
|
### No Logic
|
||||||
|
|
||||||
|
Items are placed without regard for progression or the seed being possible. Major glitches are likely required.
|
||||||
|
|
||||||
## Game Goal
|
## Game Goal
|
||||||
|
|
||||||
### Ganon
|
### Ganon
|
||||||
|
@ -259,7 +263,7 @@ generate spoilers for statistical analysis.
|
||||||
|
|
||||||
## Enable L/R button quickswapping
|
## Enable L/R button quickswapping
|
||||||
|
|
||||||
Use to enable quick item swap with L/R buttons
|
Use to enable quick item swap with L/R buttons. Press L and R together to switch the state of items like the Mushroom/Powder pair.
|
||||||
|
|
||||||
## Keysanity
|
## Keysanity
|
||||||
|
|
||||||
|
@ -269,6 +273,16 @@ is traditionally a guaranteed Small Key still is. These items will be distribute
|
||||||
the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell
|
the rest of the itempool will respect the algorithm setting. Music for dungeons is randomized so it cannot be used as a tell
|
||||||
for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize.
|
for which dungeons contain pendants and crystals; finding a Map for a dungeon will allow the overworld map to display its prize.
|
||||||
|
|
||||||
|
## Retro
|
||||||
|
|
||||||
|
This setting turns all Small Keys into universal Small Keys that can be used in any dungeon and are distributed across the world.
|
||||||
|
The Bow now consumed rupees to shoot; the cost is 10 rupees per Wood Arrow and 50 per Silver Arrow. Shooting Wood Arrows requires
|
||||||
|
the purchase of an arrow item from shops, and to account for this and the dynamic use of keys, both Wood Arrows and Small Keys will
|
||||||
|
be added to several shops around the world. Four "take any" caves are added that allow the player to choose between an extra Heart
|
||||||
|
Container and a Bottle being filled with Blue Potion, and one of the four swords from the item pool is placed into a special cave as
|
||||||
|
well. The five caves that are removed for these will be randomly selected single entrance caves that did not contain any items or any shops.
|
||||||
|
In further concert with the Bow changes, all arrows under pots, in chests, and elsewhere in the seed will be replaced with rupees.
|
||||||
|
|
||||||
## Place Dungeon Items
|
## Place Dungeon Items
|
||||||
|
|
||||||
If not set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world.
|
If not set, Compasses and Maps are removed from the dungeon item pools and replaced by empty chests that may end up anywhere in the world.
|
||||||
|
@ -311,7 +325,7 @@ Show the help message and exit.
|
||||||
Output a Spoiler File (default: False)
|
Output a Spoiler File (default: False)
|
||||||
|
|
||||||
```
|
```
|
||||||
--logic [{noglitches,minorglitches}]
|
--logic [{noglitches,minorglitches,nologic}]
|
||||||
```
|
```
|
||||||
|
|
||||||
Select the game logic (default: noglitches)
|
Select the game logic (default: noglitches)
|
||||||
|
@ -353,7 +367,7 @@ Select the setting for progressive equipment. (default: on)
|
||||||
Select item distribution algorithm. (default: balanced)
|
Select item distribution algorithm. (default: balanced)
|
||||||
|
|
||||||
```
|
```
|
||||||
--shuffle [{default,simple,restricted,full,madness,insanity,dungeonsfull,dungeonssimple}]
|
--shuffle [{default,simple,restricted,full,crossed,insanity,restricted_legacy,full_legacy,madness_legacy,insanity_legacy,dungeonsfull,dungeonssimple}]
|
||||||
```
|
```
|
||||||
|
|
||||||
Select entrance shuffle algorithm. (default: full)
|
Select entrance shuffle algorithm. (default: full)
|
||||||
|
@ -407,6 +421,12 @@ Disables game music, resulting in the game sound being just the SFX. (default: F
|
||||||
|
|
||||||
Enable Keysanity (default: False)
|
Enable Keysanity (default: False)
|
||||||
|
|
||||||
|
```
|
||||||
|
--retro
|
||||||
|
```
|
||||||
|
|
||||||
|
Enable Retro mode (default: False)
|
||||||
|
|
||||||
```
|
```
|
||||||
--nodungeonitems
|
--nodungeonitems
|
||||||
```
|
```
|
||||||
|
@ -420,6 +440,12 @@ This may lead to different amount of itempool items being placed in a dungeon th
|
||||||
|
|
||||||
Select frequency of beeps when on low health. (default: normal)
|
Select frequency of beeps when on low health. (default: normal)
|
||||||
|
|
||||||
|
```
|
||||||
|
--heartcolor [{red,blue,green,yellow}]
|
||||||
|
```
|
||||||
|
|
||||||
|
Select the color of Link\'s heart meter. (default: red)
|
||||||
|
|
||||||
```
|
```
|
||||||
--sprite SPRITE
|
--sprite SPRITE
|
||||||
```
|
```
|
||||||
|
|
57
Regions.py
57
Regions.py
|
@ -1,5 +1,5 @@
|
||||||
import collections
|
import collections
|
||||||
from BaseClasses import Region, Location, Entrance, RegionType
|
from BaseClasses import Region, Location, Entrance, RegionType, Shop, ShopType
|
||||||
|
|
||||||
|
|
||||||
def create_regions(world):
|
def create_regions(world):
|
||||||
|
@ -28,7 +28,7 @@ def create_regions(world):
|
||||||
create_lw_region('Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
|
create_lw_region('Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
|
||||||
create_cave_region('Kings Grave', ['King\'s Tomb']),
|
create_cave_region('Kings Grave', ['King\'s Tomb']),
|
||||||
create_cave_region('North Fairy Cave', None, ['North Fairy Cave Exit']),
|
create_cave_region('North Fairy Cave', None, ['North Fairy Cave Exit']),
|
||||||
create_cave_region('Dam', ['Floodgate Chest']),
|
create_cave_region('Dam', ['Floodgate', 'Floodgate Chest']),
|
||||||
create_cave_region('Links House', ['Link\'s House'], ['Links House Exit']),
|
create_cave_region('Links House', ['Link\'s House'], ['Links House Exit']),
|
||||||
create_cave_region('Chris Houlihan Room', None, ['Chris Houlihan Room Exit']),
|
create_cave_region('Chris Houlihan Room', None, ['Chris Houlihan Room Exit']),
|
||||||
create_cave_region('Tavern', ['Kakariko Tavern']),
|
create_cave_region('Tavern', ['Kakariko Tavern']),
|
||||||
|
@ -57,7 +57,7 @@ def create_regions(world):
|
||||||
create_cave_region('Kakariko Well (top)', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
create_cave_region('Kakariko Well (top)', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
||||||
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
|
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
|
||||||
create_cave_region('Kakariko Well (bottom)', None, ['Kakariko Well Exit']),
|
create_cave_region('Kakariko Well (bottom)', None, ['Kakariko Well Exit']),
|
||||||
create_cave_region('Blacksmiths Hut', ['Blacksmith']),
|
create_cave_region('Blacksmiths Hut', ['Blacksmith', 'Missing Smith']),
|
||||||
create_lw_region('Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
create_lw_region('Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
||||||
create_cave_region('Bat Cave (right)', ['Magic Bat'], ['Bat Cave Door']),
|
create_cave_region('Bat Cave (right)', ['Magic Bat'], ['Bat Cave Door']),
|
||||||
create_cave_region('Bat Cave (left)', None, ['Bat Cave Exit']),
|
create_cave_region('Bat Cave (left)', None, ['Bat Cave Exit']),
|
||||||
|
@ -125,7 +125,7 @@ def create_regions(world):
|
||||||
create_cave_region('Spectacle Rock Cave (Peak)', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
|
create_cave_region('Spectacle Rock Cave (Peak)', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
|
||||||
create_lw_region('East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']),
|
create_lw_region('East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']),
|
||||||
create_cave_region('Hookshot Fairy'),
|
create_cave_region('Hookshot Fairy'),
|
||||||
create_cave_region('Paradox Cave Front', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)']),
|
create_cave_region('Paradox Cave Front', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']),
|
||||||
create_cave_region('Paradox Cave Chest Area', ['Paradox Cave Lower - Far Left',
|
create_cave_region('Paradox Cave Chest Area', ['Paradox Cave Lower - Far Left',
|
||||||
'Paradox Cave Lower - Left',
|
'Paradox Cave Lower - Left',
|
||||||
'Paradox Cave Lower - Right',
|
'Paradox Cave Lower - Right',
|
||||||
|
@ -135,6 +135,7 @@ def create_regions(world):
|
||||||
'Paradox Cave Upper - Right'],
|
'Paradox Cave Upper - Right'],
|
||||||
['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']),
|
['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']),
|
||||||
create_cave_region('Paradox Cave', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']),
|
create_cave_region('Paradox Cave', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']),
|
||||||
|
create_cave_region('Light World Death Mountain Shop'),
|
||||||
create_lw_region('East Death Mountain (Top)', None, ['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access', 'East Death Mountain Drop', 'Turtle Rock Teleporter', 'Fairy Ascension Ledge']),
|
create_lw_region('East Death Mountain (Top)', None, ['Paradox Cave (Top)', 'Death Mountain (Top)', 'Spiral Cave Ledge Access', 'East Death Mountain Drop', 'Turtle Rock Teleporter', 'Fairy Ascension Ledge']),
|
||||||
create_lw_region('Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop']),
|
create_lw_region('Spiral Cave Ledge', None, ['Spiral Cave', 'Spiral Cave Ledge Drop']),
|
||||||
create_cave_region('Spiral Cave (Top)', ['Spiral Cave'], ['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']),
|
create_cave_region('Spiral Cave (Top)', ['Spiral Cave'], ['Spiral Cave (top to bottom)', 'Spiral Cave Exit (Top)']),
|
||||||
|
@ -164,10 +165,10 @@ def create_regions(world):
|
||||||
create_cave_region('Dark Lake Hylia Ledge Spike Cave'),
|
create_cave_region('Dark Lake Hylia Ledge Spike Cave'),
|
||||||
create_cave_region('Hype Cave', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left',
|
create_cave_region('Hype Cave', ['Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left',
|
||||||
'Hype Cave - Bottom', 'Hype Cave - Generous Guy']),
|
'Hype Cave - Bottom', 'Hype Cave - Generous Guy']),
|
||||||
create_dw_region('West Dark World', None, ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot', 'Bumper Cave Entrance Rock',
|
create_dw_region('West Dark World', ['Frog'], ['Village of Outcasts Drop', 'East Dark World River Pier', 'Brewery', 'C-Shaped House', 'Chest Game', 'Thieves Town', 'Graveyard Ledge Mirror Spot', 'Kings Grave Mirror Spot', 'Bumper Cave Entrance Rock',
|
||||||
'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Dark World Lumberjack Shop']),
|
'Skull Woods Forest', 'Village of Outcasts Pegs', 'Village of Outcasts Eastern Rocks', 'Red Shield Shop', 'Dark Sanctuary Hint', 'Fortune Teller (Dark)', 'Dark World Lumberjack Shop']),
|
||||||
create_dw_region('Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop']),
|
create_dw_region('Dark Grassy Lawn', None, ['Grassy Lawn Pegs', 'Dark World Shop']),
|
||||||
create_dw_region('Hammer Peg Area', None, ['Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Peg Area Rocks']),
|
create_dw_region('Hammer Peg Area', ['Dark Blacksmith Ruins'], ['Bat Cave Drop Ledge Mirror Spot', 'Dark World Hammer Peg Cave', 'Peg Area Rocks']),
|
||||||
create_dw_region('Bumper Cave Entrance', None, ['Bumper Cave (Bottom)', 'Bumper Cave Entrance Mirror Spot', 'Bumper Cave Entrance Drop']),
|
create_dw_region('Bumper Cave Entrance', None, ['Bumper Cave (Bottom)', 'Bumper Cave Entrance Mirror Spot', 'Bumper Cave Entrance Drop']),
|
||||||
create_cave_region('Fortune Teller (Dark)'),
|
create_cave_region('Fortune Teller (Dark)'),
|
||||||
create_cave_region('Village of Outcasts Shop'),
|
create_cave_region('Village of Outcasts Shop'),
|
||||||
|
@ -290,6 +291,14 @@ def create_regions(world):
|
||||||
create_dw_region('Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop'])
|
create_dw_region('Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop'])
|
||||||
]
|
]
|
||||||
|
|
||||||
|
for region_name, (room_id, shopkeeper, replaceable) in shop_table.items():
|
||||||
|
region = world.get_region(region_name)
|
||||||
|
shop = Shop(region, room_id, ShopType.Shop, shopkeeper, replaceable)
|
||||||
|
region.shop = shop
|
||||||
|
world.shops.append(shop)
|
||||||
|
for index, (item, price) in enumerate(default_shop_contents[region_name]):
|
||||||
|
shop.add_inventory(index, item, price)
|
||||||
|
|
||||||
world.intialize_regions()
|
world.intialize_regions()
|
||||||
|
|
||||||
def create_lw_region(name, locations=None, exits=None):
|
def create_lw_region(name, locations=None, exits=None):
|
||||||
|
@ -347,6 +356,38 @@ def mark_light_world_regions(world):
|
||||||
seen.add(exit.connected_region)
|
seen.add(exit.connected_region)
|
||||||
queue.append(exit.connected_region)
|
queue.append(exit.connected_region)
|
||||||
|
|
||||||
|
# (room_id, shopkeeper, replaceable)
|
||||||
|
shop_table = {
|
||||||
|
'Cave Shop (Dark Death Mountain)': (0x0112, 0x51, True),
|
||||||
|
'Red Shield Shop': (0x0110, 0x51, True),
|
||||||
|
'Dark Lake Hylia Shop': (0x010F, 0x51, True),
|
||||||
|
'Dark World Lumberjack Shop': (0x010F, 0x51, True),
|
||||||
|
'Village of Outcasts Shop': (0x010F, 0x51, True),
|
||||||
|
'Dark World Potion Shop': (0x010F, 0x51, True),
|
||||||
|
'Light World Death Mountain Shop': (0x00FF, 0x51, True),
|
||||||
|
'Kakariko Shop': (0x011F, 0x51, True),
|
||||||
|
'Cave Shop (Lake Hylia)': (0x0112, 0x51, True),
|
||||||
|
'Potion Shop': (0x0109, 0xFF, False),
|
||||||
|
# Bomb Shop not currently modeled as a shop, due to special nature of items
|
||||||
|
}
|
||||||
|
# region, [item]
|
||||||
|
# slot, item, price, max=0, replacement=None, replacement_price=0
|
||||||
|
# item = (item, price)
|
||||||
|
|
||||||
|
_basic_shop_defaults = [('Red Potion', 150), ('Small Heart', 10), ('Bombs (10)', 50)]
|
||||||
|
_dark_world_shop_defaults = [('Red Potion', 150), ('Blue Shield', 50), ('Bombs (10)', 50)]
|
||||||
|
default_shop_contents = {
|
||||||
|
'Cave Shop (Dark Death Mountain)': _basic_shop_defaults,
|
||||||
|
'Red Shield Shop': [('Red Shield', 500), ('Bee', 10), ('Arrows (10)', 30)],
|
||||||
|
'Dark Lake Hylia Shop': _dark_world_shop_defaults,
|
||||||
|
'Dark World Lumberjack Shop': _dark_world_shop_defaults,
|
||||||
|
'Village of Outcasts Shop': _dark_world_shop_defaults,
|
||||||
|
'Dark World Potion Shop': _dark_world_shop_defaults,
|
||||||
|
'Light World Death Mountain Shop': _basic_shop_defaults,
|
||||||
|
'Kakariko Shop': _basic_shop_defaults,
|
||||||
|
'Cave Shop (Lake Hylia)': _basic_shop_defaults,
|
||||||
|
'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
|
||||||
|
}
|
||||||
|
|
||||||
location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
||||||
'Bottle Merchant': (0x2EB18, False, 'with a merchant'),
|
'Bottle Merchant': (0x2EB18, False, 'with a merchant'),
|
||||||
|
@ -567,6 +608,10 @@ location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
||||||
'Ganon': (None, False, 'from me'),
|
'Ganon': (None, False, 'from me'),
|
||||||
'Agahnim 1': (None, False, 'from my wizardry form'),
|
'Agahnim 1': (None, False, 'from my wizardry form'),
|
||||||
'Agahnim 2': (None, False, 'from my wizardry form'),
|
'Agahnim 2': (None, False, 'from my wizardry form'),
|
||||||
|
'Floodgate': (None, False, None),
|
||||||
|
'Frog': (None, False, None),
|
||||||
|
'Missing Smith': (None, False, None),
|
||||||
|
'Dark Blacksmith Ruins': (None, False, None),
|
||||||
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'),
|
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'),
|
||||||
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'),
|
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'),
|
||||||
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'),
|
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'),
|
||||||
|
|
125
Rom.py
125
Rom.py
|
@ -6,16 +6,17 @@ import os
|
||||||
import struct
|
import struct
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
from BaseClasses import ShopType
|
||||||
from Dungeons import dungeon_music_addresses
|
from Dungeons import dungeon_music_addresses
|
||||||
from Text import string_to_alttp_text, text_addresses, Credits
|
from Text import MultiByteTextMapper, text_addresses, Credits
|
||||||
from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts
|
from Text import Uncle_texts, Ganon1_texts, PyramidFairy_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, Blind_texts, BombShop2_texts
|
||||||
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
|
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
|
||||||
from Utils import local_path
|
from Utils import local_path, int16_as_bytes, int32_as_bytes
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = 'dc5840f0d1ef7b51009c5625a054b3dd'
|
RANDOMIZERBASEHASH = '354bad0d01b6284c89f05c9b414d48c1'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
|
@ -259,15 +260,6 @@ class Sprite(object):
|
||||||
# split into palettes of 15 colors
|
# split into palettes of 15 colors
|
||||||
return array_chunk(palette_as_colors, 15)
|
return array_chunk(palette_as_colors, 15)
|
||||||
|
|
||||||
|
|
||||||
def int16_as_bytes(value):
|
|
||||||
value = value & 0xFFFF
|
|
||||||
return [value & 0xFF, (value >> 8) & 0xFF]
|
|
||||||
|
|
||||||
def int32_as_bytes(value):
|
|
||||||
value = value & 0xFFFFFFFF
|
|
||||||
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]
|
|
||||||
|
|
||||||
def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
# patch items
|
# patch items
|
||||||
for location in world.get_locations():
|
for location in world.get_locations():
|
||||||
|
@ -355,6 +347,7 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
# patch door table
|
# patch door table
|
||||||
rom.write_byte(0xDBB73 + exit.addresses, exit.target)
|
rom.write_byte(0xDBB73 + exit.addresses, exit.target)
|
||||||
|
|
||||||
|
write_custom_shops(rom, world)
|
||||||
|
|
||||||
# patch medallion requirements
|
# patch medallion requirements
|
||||||
if world.required_medallions[0] == 'Bombos':
|
if world.required_medallions[0] == 'Bombos':
|
||||||
|
@ -411,7 +404,7 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
# Powdered Fairies Prize
|
# Powdered Fairies Prize
|
||||||
rom.write_byte(0x36DD0, 0xD8) # One Heart
|
rom.write_byte(0x36DD0, 0xD8) # One Heart
|
||||||
# potion heal amount
|
# potion heal amount
|
||||||
rom.write_byte(0x180084, 0x28) # Five Hearts
|
rom.write_byte(0x180084, 0x38) # Seven Hearts
|
||||||
# potion magic restore amount
|
# potion magic restore amount
|
||||||
rom.write_byte(0x180085, 0x40) # Half Magic
|
rom.write_byte(0x180085, 0x40) # Half Magic
|
||||||
#Cape magic cost
|
#Cape magic cost
|
||||||
|
@ -423,6 +416,10 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
overflow_replacement = GREEN_TWENTY_RUPEES
|
overflow_replacement = GREEN_TWENTY_RUPEES
|
||||||
# Rupoor negative value
|
# Rupoor negative value
|
||||||
rom.write_int16_to_rom(0x180036, world.rupoor_cost)
|
rom.write_int16_to_rom(0x180036, world.rupoor_cost)
|
||||||
|
# Set stun items
|
||||||
|
rom.write_byte(0x180180, 0x02) # Hookshot only
|
||||||
|
# Make silver arrows only usable against Ganon
|
||||||
|
rom.write_byte(0x180181, 0x01)
|
||||||
#Make Blue Shield more expensive
|
#Make Blue Shield more expensive
|
||||||
rom.write_bytes(0xF73D2, [0xFC, 0xFF])
|
rom.write_bytes(0xF73D2, [0xFC, 0xFF])
|
||||||
rom.write_bytes(0xF73DA, [0x04, 0x00])
|
rom.write_bytes(0xF73DA, [0x04, 0x00])
|
||||||
|
@ -443,20 +440,24 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
rom.write_byte(0xF723F, 0xE7)
|
rom.write_byte(0xF723F, 0xE7)
|
||||||
elif world.difficulty == 'expert':
|
elif world.difficulty == 'expert':
|
||||||
# Powdered Fairies Prize
|
# Powdered Fairies Prize
|
||||||
rom.write_byte(0x36DD0, 0x79) # Bees
|
rom.write_byte(0x36DD0, 0xD8) # One Heart
|
||||||
# potion heal amount
|
# potion heal amount
|
||||||
rom.write_byte(0x180084, 0x08) # One Heart
|
rom.write_byte(0x180084, 0x08) # One Heart
|
||||||
# potion magic restore amount
|
# potion magic restore amount
|
||||||
rom.write_byte(0x180085, 0x20) # Quarter Magic
|
rom.write_byte(0x180085, 0x20) # Quarter Magic
|
||||||
#Cape magic cost
|
#Cape magic cost
|
||||||
rom.write_bytes(0x3ADA7, [0x02, 0x02, 0x02])
|
rom.write_bytes(0x3ADA7, [0x01, 0x01, 0x01])
|
||||||
# Byrna Invulnerability: off
|
# Byrna Invulnerability: off
|
||||||
rom.write_byte(0x18004F, 0x00)
|
rom.write_byte(0x18004F, 0x00)
|
||||||
#Disable catching fairies
|
#Disable catching fairies
|
||||||
rom.write_byte(0x34FD6, 0x80)
|
rom.write_byte(0x34FD6, 0x80)
|
||||||
overflow_replacement = GREEN_TWENTY_RUPEES
|
overflow_replacement = GREEN_TWENTY_RUPEES
|
||||||
# Rupoor negative value
|
# Rupoor negative value
|
||||||
rom.write_int16_to_rom(0x180036, 20)
|
rom.write_int16_to_rom(0x180036, world.rupoor_cost)
|
||||||
|
# Set stun items
|
||||||
|
rom.write_byte(0x180180, 0x00) # Nothing
|
||||||
|
# Make silver arrows only usable against Ganon
|
||||||
|
rom.write_byte(0x180181, 0x01)
|
||||||
#Make Blue Shield more expensive
|
#Make Blue Shield more expensive
|
||||||
rom.write_bytes(0xF73D2, [0xFC, 0xFF])
|
rom.write_bytes(0xF73D2, [0xFC, 0xFF])
|
||||||
rom.write_bytes(0xF73DA, [0x04, 0x00])
|
rom.write_bytes(0xF73DA, [0x04, 0x00])
|
||||||
|
@ -483,14 +484,18 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
# potion magic restore amount
|
# potion magic restore amount
|
||||||
rom.write_byte(0x180085, 0x00) # No healing
|
rom.write_byte(0x180085, 0x00) # No healing
|
||||||
#Cape magic cost
|
#Cape magic cost
|
||||||
rom.write_bytes(0x3ADA7, [0x02, 0x02, 0x02])
|
rom.write_bytes(0x3ADA7, [0x01, 0x01, 0x01])
|
||||||
# Byrna Invulnerability: off
|
# Byrna Invulnerability: off
|
||||||
rom.write_byte(0x18004F, 0x00)
|
rom.write_byte(0x18004F, 0x00)
|
||||||
#Disable catching fairies
|
#Disable catching fairies
|
||||||
rom.write_byte(0x34FD6, 0x80)
|
rom.write_byte(0x34FD6, 0x80)
|
||||||
overflow_replacement = GREEN_TWENTY_RUPEES
|
overflow_replacement = GREEN_TWENTY_RUPEES
|
||||||
# Rupoor negative value
|
# Rupoor negative value
|
||||||
rom.write_int16_to_rom(0x180036, 9999)
|
rom.write_int16_to_rom(0x180036, world.rupoor_cost)
|
||||||
|
# Set stun items
|
||||||
|
rom.write_byte(0x180180, 0x00) # Nothing
|
||||||
|
# Make silver arrows only usable against Ganon
|
||||||
|
rom.write_byte(0x180181, 0x01)
|
||||||
#Make Blue Shield more expensive
|
#Make Blue Shield more expensive
|
||||||
rom.write_bytes(0xF73D2, [0xFC, 0xFF])
|
rom.write_bytes(0xF73D2, [0xFC, 0xFF])
|
||||||
rom.write_bytes(0xF73DA, [0x04, 0x00])
|
rom.write_bytes(0xF73DA, [0x04, 0x00])
|
||||||
|
@ -522,14 +527,25 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
rom.write_byte(0x18004F, 0x01)
|
rom.write_byte(0x18004F, 0x01)
|
||||||
#Enable catching fairies
|
#Enable catching fairies
|
||||||
rom.write_byte(0x34FD6, 0xF0)
|
rom.write_byte(0x34FD6, 0xF0)
|
||||||
|
# Rupoor negative value
|
||||||
|
rom.write_int16_to_rom(0x180036, world.rupoor_cost)
|
||||||
|
# Set stun items
|
||||||
|
rom.write_byte(0x180180, 0x03) # All standard items
|
||||||
|
# Make silver arrows freely usable
|
||||||
|
rom.write_byte(0x180181, 0x00)
|
||||||
#Set overflow items for progressive equipment
|
#Set overflow items for progressive equipment
|
||||||
if world.goal == 'triforcehunt':
|
if world.timer in ['timed', 'timed-countdown', 'timed-ohko']:
|
||||||
overflow_replacement = TRIFORCE_PIECE
|
|
||||||
elif world.timer in ['timed', 'timed-countdown', 'timed-ohko']:
|
|
||||||
overflow_replacement = GREEN_CLOCK
|
overflow_replacement = GREEN_CLOCK
|
||||||
else:
|
else:
|
||||||
overflow_replacement = GREEN_TWENTY_RUPEES
|
overflow_replacement = GREEN_TWENTY_RUPEES
|
||||||
|
|
||||||
|
if world.difficulty in ['easy']:
|
||||||
|
rom.write_byte(0x180182, 0x03) # auto equip silvers on pickup and at ganon
|
||||||
|
elif world.retro and world.difficulty in ['hard','expert', 'insane']: #FIXME: this is temporary for v29 baserom
|
||||||
|
rom.write_byte(0x180182, 0x03) # auto equip silvers on pickup and at ganon
|
||||||
|
else:
|
||||||
|
rom.write_byte(0x180182, 0x01) # auto equip silvers on pickup
|
||||||
|
|
||||||
#Byrna residual magic cost
|
#Byrna residual magic cost
|
||||||
rom.write_bytes(0x45C42, [0x04, 0x02, 0x01])
|
rom.write_bytes(0x45C42, [0x04, 0x02, 0x01])
|
||||||
|
|
||||||
|
@ -549,6 +565,19 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF,
|
prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0, 0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF,
|
||||||
0xDC, 0xDC, 0xDC, 0xDD, 0xDC, 0xDC, 0xDE, 0xDC, 0xE1, 0xD8, 0xE1, 0xE2, 0xE1, 0xD8, 0xE1, 0xE2, 0xDF, 0xD9, 0xD8, 0xE1, 0xDF, 0xDC, 0xD9, 0xD8,
|
0xDC, 0xDC, 0xDC, 0xDD, 0xDC, 0xDC, 0xDE, 0xDC, 0xE1, 0xD8, 0xE1, 0xE2, 0xE1, 0xD8, 0xE1, 0xE2, 0xDF, 0xD9, 0xD8, 0xE1, 0xDF, 0xDC, 0xD9, 0xD8,
|
||||||
0xD8, 0xE3, 0xE0, 0xDB, 0xDE, 0xD8, 0xDB, 0xE2, 0xD9, 0xDA, 0xDB, 0xD9, 0xDB, 0xD9, 0xDB]
|
0xD8, 0xE3, 0xE0, 0xDB, 0xDE, 0xD8, 0xDB, 0xE2, 0xD9, 0xDA, 0xDB, 0xD9, 0xDB, 0xD9, 0xDB]
|
||||||
|
dig_prizes = [0xB2, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8, 0xD8,
|
||||||
|
0xD9, 0xD9, 0xD9, 0xD9, 0xD9, 0xDA, 0xDA, 0xDA, 0xDA, 0xDA,
|
||||||
|
0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDC, 0xDC, 0xDC, 0xDC, 0xDC,
|
||||||
|
0xDD, 0xDD, 0xDD, 0xDD, 0xDD, 0xDE, 0xDE, 0xDE, 0xDE, 0xDE,
|
||||||
|
0xDF, 0xDF, 0xDF, 0xDF, 0xDF, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0,
|
||||||
|
0xE1, 0xE1, 0xE1, 0xE1, 0xE1, 0xE2, 0xE2, 0xE2, 0xE2, 0xE2,
|
||||||
|
0xE3, 0xE3, 0xE3, 0xE3, 0xE3]
|
||||||
|
if world.retro:
|
||||||
|
prize_replacements = {0xE1: 0xDA, #5 Arrows -> Blue Rupee
|
||||||
|
0xE2: 0xDB} #10 Arrows -> Red Rupee
|
||||||
|
prizes = [prize_replacements.get(prize,prize) for prize in prizes]
|
||||||
|
dig_prizes = [prize_replacements.get(prize,prize) for prize in dig_prizes]
|
||||||
|
rom.write_bytes(0x180100, dig_prizes)
|
||||||
random.shuffle(prizes)
|
random.shuffle(prizes)
|
||||||
|
|
||||||
# write tree pull prizes
|
# write tree pull prizes
|
||||||
|
@ -726,9 +755,7 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror
|
rom.write_byte(0x1800A0, 0x01) # return to light world on s+q without mirror
|
||||||
rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp
|
rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp
|
||||||
rom.write_byte(0x180174, 0x01 if world.fix_fake_world else 0x00)
|
rom.write_byte(0x180174, 0x01 if world.fix_fake_world else 0x00)
|
||||||
rom.write_byte(0x180175, 0x00) # Arrow mode: normal
|
rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles
|
||||||
rom.write_int16_to_rom(0x180176, 0) # Wood Arrow Cost (rupee arrow mode)
|
|
||||||
rom.write_int16_to_rom(0x180178, 0) # Silver Arrow Cost (rupee arrow mode)
|
|
||||||
rom.write_byte(0x180034, 0x0A) # starting max bombs
|
rom.write_byte(0x180034, 0x0A) # starting max bombs
|
||||||
rom.write_byte(0x180035, 30) # starting max arrows
|
rom.write_byte(0x180035, 30) # starting max arrows
|
||||||
for x in range(0x183000, 0x18304F):
|
for x in range(0x183000, 0x18304F):
|
||||||
|
@ -769,11 +796,20 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
rom.write_byte(0x18003C, 0x00)
|
rom.write_byte(0x18003C, 0x00)
|
||||||
|
|
||||||
rom.write_byte(0x180045, 0xFF if world.keysanity else 0x00) # free roaming items in menu
|
rom.write_byte(0x180045, 0xFF if world.keysanity else 0x00) # free roaming items in menu
|
||||||
|
rom.write_byte(0x180172, 0x01 if world.retro else 0x00) # universal keys
|
||||||
|
rom.write_byte(0x180175, 0x01 if world.retro else 0x00) # rupee bow
|
||||||
|
rom.write_byte(0x180176, 0x0A if world.retro else 0x00) # wood arrow cost
|
||||||
|
rom.write_byte(0x180178, 0x32 if world.retro else 0x00) # silver arrow cost
|
||||||
|
rom.write_byte(0x301FC, 0xDA if world.retro else 0xE1) # rupees replace arrows under pots
|
||||||
|
rom.write_byte(0x30052, 0xDB if world.retro else 0xE2) # replace arrows in fish prize from bottle merchant
|
||||||
|
rom.write_bytes(0xECB4E, [0xA9, 0x00, 0xEA, 0xEA] if world.retro else [0xAF, 0x77, 0xF3, 0x7E]) # Thief steals rupees instead of arrows
|
||||||
|
rom.write_bytes(0xF0D96, [0xA9, 0x00, 0xEA, 0xEA] if world.retro else [0xAF, 0x77, 0xF3, 0x7E]) # Pikit steals rupees instead of arrows
|
||||||
|
rom.write_bytes(0xEDA5, [0x35, 0x41] if world.retro else [0x43, 0x44]) # Chest game gives rupees instead of arrows
|
||||||
digging_game_rng = random.randint(1, 30) # set rng for digging game
|
digging_game_rng = random.randint(1, 30) # set rng for digging game
|
||||||
rom.write_byte(0x180020, digging_game_rng)
|
rom.write_byte(0x180020, digging_game_rng)
|
||||||
rom.write_byte(0xEFD95, digging_game_rng)
|
rom.write_byte(0xEFD95, digging_game_rng)
|
||||||
rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
|
rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
|
||||||
rom.write_byte(0x180042, 0x01 if world.save_and_quite_from_boss else 0x00) # Allow Save and Quite after boss kill
|
rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill
|
||||||
|
|
||||||
# remove shield from uncle
|
# remove shield from uncle
|
||||||
rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
rom.write_bytes(0x6D253, [0x00, 0x00, 0xf6, 0xff, 0x00, 0x0E])
|
||||||
|
@ -784,15 +820,12 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
rom.write_bytes(0x6D2FB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
|
rom.write_bytes(0x6D2FB, [0x00, 0x00, 0xf7, 0xff, 0x02, 0x0E])
|
||||||
rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
|
rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
|
||||||
|
|
||||||
# Shop table
|
|
||||||
rom.write_bytes(0x184800, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
|
|
||||||
|
|
||||||
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved
|
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved
|
||||||
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required else 0x00)
|
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required else 0x00)
|
||||||
|
|
||||||
# powder patch: remove the need to leave the scrren after powder, since it causes problems for potion shop at race game
|
# powder patch: remove the need to leave the scrren after powder, since it causes problems for potion shop at race game
|
||||||
# temporarally we are just nopping out this check we will conver this to a rom fix soon.
|
# temporarally we are just nopping out this check we will conver this to a rom fix soon.
|
||||||
rom.write_bytes(0x02F539,[0xEA,0xEA,0xEA,0xEA,0xEA] if world.powder_patch_required else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F])
|
rom.write_bytes(0x02F539, [0xEA, 0xEA, 0xEA, 0xEA, 0xEA] if world.powder_patch_required else [0xAD, 0xBF, 0x0A, 0xF0, 0x4F])
|
||||||
|
|
||||||
# allow smith into multi-entrance caves in appropriate shuffles
|
# allow smith into multi-entrance caves in appropriate shuffles
|
||||||
if world.shuffle in ['restricted', 'full', 'crossed', 'insanity']:
|
if world.shuffle in ['restricted', 'full', 'crossed', 'insanity']:
|
||||||
|
@ -827,6 +860,38 @@ def patch_rom(world, rom, hashtable, beep='normal', color='red', sprite=None):
|
||||||
|
|
||||||
return rom
|
return rom
|
||||||
|
|
||||||
|
def write_custom_shops(rom, world):
|
||||||
|
shops = [shop for shop in world.shops if shop.replaceable and shop.active]
|
||||||
|
|
||||||
|
shop_data = bytearray()
|
||||||
|
items_data = bytearray()
|
||||||
|
sram_offset = 0
|
||||||
|
|
||||||
|
for shop_id, shop in enumerate(shops):
|
||||||
|
if shop_id == len(shops) - 1:
|
||||||
|
shop_id = 0xFF
|
||||||
|
bytes = shop.get_bytes()
|
||||||
|
bytes[0] = shop_id
|
||||||
|
bytes[-1] = sram_offset
|
||||||
|
if shop.type == ShopType.TakeAny:
|
||||||
|
sram_offset += 1
|
||||||
|
else:
|
||||||
|
sram_offset += shop.item_count
|
||||||
|
shop_data.extend(bytes)
|
||||||
|
# [id][item][price-low][price-high][max][repl_id][repl_price-low][repl_price-high]
|
||||||
|
for item in shop.inventory:
|
||||||
|
if item is None:
|
||||||
|
break
|
||||||
|
item_data = [shop_id, ItemFactory(item['item']).code] + int16_as_bytes(item['price']) + [item['max'], ItemFactory(item['replacement']).code if item['replacement'] else 0xFF] + int16_as_bytes(item['replacement_price'])
|
||||||
|
items_data.extend(item_data)
|
||||||
|
|
||||||
|
rom.write_bytes(0x184800, shop_data)
|
||||||
|
|
||||||
|
items_data.extend([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
|
||||||
|
rom.write_bytes(0x184900, items_data)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite):
|
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite):
|
||||||
|
|
||||||
# enable instant item menu
|
# enable instant item menu
|
||||||
|
@ -955,7 +1020,7 @@ def write_sprite(rom, sprite):
|
||||||
|
|
||||||
def write_string_to_rom(rom, target, string):
|
def write_string_to_rom(rom, target, string):
|
||||||
address, maxbytes = text_addresses[target]
|
address, maxbytes = text_addresses[target]
|
||||||
rom.write_bytes(address, string_to_alttp_text(string, maxbytes))
|
rom.write_bytes(address, MultiByteTextMapper.convert(string, maxbytes))
|
||||||
|
|
||||||
|
|
||||||
def write_strings(rom, world):
|
def write_strings(rom, world):
|
||||||
|
|
173
Rules.py
173
Rules.py
|
@ -3,6 +3,15 @@ import logging
|
||||||
|
|
||||||
|
|
||||||
def set_rules(world):
|
def set_rules(world):
|
||||||
|
|
||||||
|
if world.logic == 'nologic':
|
||||||
|
logging.getLogger('').info('WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
|
||||||
|
world.get_region('Links House').can_reach = lambda state: True
|
||||||
|
world.get_region('Sanctuary').can_reach = lambda state: True
|
||||||
|
old_rule = world.get_region('Old Man House').can_reach
|
||||||
|
world.get_region('Old Man House').can_reach = lambda state: state.can_reach('Old Man', 'Location') or old_rule(state)
|
||||||
|
return
|
||||||
|
|
||||||
global_rules(world)
|
global_rules(world)
|
||||||
|
|
||||||
if world.mode == 'open':
|
if world.mode == 'open':
|
||||||
|
@ -110,11 +119,14 @@ def global_rules(world):
|
||||||
set_rule(world.get_entrance('South Hyrule Teleporter'), lambda state: state.has('Hammer') and state.can_lift_rocks() and state.has_Pearl()) # bunny cannot use hammer
|
set_rule(world.get_entrance('South Hyrule Teleporter'), lambda state: state.has('Hammer') and state.can_lift_rocks() and state.has_Pearl()) # bunny cannot use hammer
|
||||||
set_rule(world.get_entrance('Kakariko Teleporter'), lambda state: ((state.has('Hammer') and state.can_lift_rocks()) or state.can_lift_heavy_rocks()) and state.has_Pearl()) # bunny cannot lift bushes
|
set_rule(world.get_entrance('Kakariko Teleporter'), lambda state: ((state.has('Hammer') and state.can_lift_rocks()) or state.can_lift_heavy_rocks()) and state.has_Pearl()) # bunny cannot lift bushes
|
||||||
set_rule(world.get_location('Flute Spot'), lambda state: state.has('Shovel'))
|
set_rule(world.get_location('Flute Spot'), lambda state: state.has('Shovel'))
|
||||||
set_rule(world.get_location('Purple Chest'), lambda state: state.can_reach('Blacksmith', 'Location')) # Can S&Q with chest
|
set_rule(world.get_location('Dark Blacksmith Ruins'), lambda state: state.has('Return Smith'))
|
||||||
|
set_rule(world.get_location('Purple Chest'), lambda state: state.has('Pick Up Purple Chest')) # Can S&Q with chest
|
||||||
|
|
||||||
set_rule(world.get_location('Zora\'s Ledge'), lambda state: state.has('Flippers'))
|
set_rule(world.get_location('Zora\'s Ledge'), lambda state: state.has('Flippers'))
|
||||||
set_rule(world.get_entrance('Waterfall of Wishing'), lambda state: state.has('Flippers')) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo
|
set_rule(world.get_entrance('Waterfall of Wishing'), lambda state: state.has('Flippers')) # can be fake flippered into, but is in weird state inside that might prevent you from doing things. Can be improved in future Todo
|
||||||
set_rule(world.get_location('Blacksmith'), lambda state: state.can_lift_heavy_rocks() and state.can_reach('West Dark World')) # Can S&Q with smith
|
set_rule(world.get_location('Frog'), lambda state: state.can_lift_heavy_rocks()) # will get automatic moon pearl requirement
|
||||||
|
set_rule(world.get_location('Missing Smith'), lambda state: state.has('Get Frog')) # Can S&Q with smith
|
||||||
|
set_rule(world.get_location('Blacksmith'), lambda state: state.has('Return Smith'))
|
||||||
set_rule(world.get_location('Magic Bat'), lambda state: state.has('Magic Powder'))
|
set_rule(world.get_location('Magic Bat'), lambda state: state.has('Magic Powder'))
|
||||||
set_rule(world.get_location('Sick Kid'), lambda state: state.has_bottle())
|
set_rule(world.get_location('Sick Kid'), lambda state: state.has_bottle())
|
||||||
set_rule(world.get_location('Library'), lambda state: state.has_Boots())
|
set_rule(world.get_location('Library'), lambda state: state.has_Boots())
|
||||||
|
@ -125,8 +137,8 @@ def global_rules(world):
|
||||||
set_rule(world.get_location('Master Sword Pedestal'), lambda state: state.has('Red Pendant') and state.has('Blue Pendant') and state.has('Green Pendant'))
|
set_rule(world.get_location('Master Sword Pedestal'), lambda state: state.has('Red Pendant') and state.has('Blue Pendant') and state.has('Green Pendant'))
|
||||||
set_rule(world.get_location('Sahasrahla'), lambda state: state.has('Green Pendant'))
|
set_rule(world.get_location('Sahasrahla'), lambda state: state.has('Green Pendant'))
|
||||||
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has_beam_sword() or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
|
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has_beam_sword() or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
|
||||||
set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has_sword() and state.has('Small Key (Agahnims Tower)', 2))
|
set_rule(world.get_entrance('Agahnim 1'), lambda state: state.has_sword() and state.has_key('Small Key (Agahnims Tower)', 2))
|
||||||
set_rule(world.get_location('Castle Tower - Dark Maze'), lambda state: state.has('Small Key (Agahnims Tower)'))
|
set_rule(world.get_location('Castle Tower - Dark Maze'), lambda state: state.has_key('Small Key (Agahnims Tower)'))
|
||||||
set_rule(world.get_entrance('Top of Pyramid'), lambda state: state.has('Beat Agahnim 1'))
|
set_rule(world.get_entrance('Top of Pyramid'), lambda state: state.has('Beat Agahnim 1'))
|
||||||
set_rule(world.get_entrance('Old Man Cave Exit (West)'), lambda state: False) # drop cannot be climbed up
|
set_rule(world.get_entrance('Old Man Cave Exit (West)'), lambda state: False) # drop cannot be climbed up
|
||||||
set_rule(world.get_entrance('Broken Bridge (West)'), lambda state: state.has('Hookshot'))
|
set_rule(world.get_entrance('Broken Bridge (West)'), lambda state: state.has('Hookshot'))
|
||||||
|
@ -212,29 +224,29 @@ def global_rules(world):
|
||||||
set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_sword() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword required to cast magic (!)
|
set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_sword() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword required to cast magic (!)
|
||||||
set_rule(world.get_location('Mimic Cave'), lambda state: state.has('Hammer'))
|
set_rule(world.get_location('Mimic Cave'), lambda state: state.has('Hammer'))
|
||||||
|
|
||||||
set_rule(world.get_entrance('Sewers Door'), lambda state: state.has('Small Key (Escape)'))
|
set_rule(world.get_entrance('Sewers Door'), lambda state: state.has_key('Small Key (Escape)'))
|
||||||
set_rule(world.get_entrance('Sewers Back Door'), lambda state: state.has('Small Key (Escape)'))
|
set_rule(world.get_entrance('Sewers Back Door'), lambda state: state.has_key('Small Key (Escape)'))
|
||||||
|
|
||||||
set_rule(world.get_location('Eastern Palace - Big Chest'), lambda state: state.has('Big Key (Eastern Palace)'))
|
set_rule(world.get_location('Eastern Palace - Big Chest'), lambda state: state.has('Big Key (Eastern Palace)'))
|
||||||
set_rule(world.get_location('Eastern Palace - Armos Knights'), lambda state: state.has('Bow') and state.has('Big Key (Eastern Palace)'))
|
set_rule(world.get_location('Eastern Palace - Armos Knights'), lambda state: state.can_shoot_arrows() and state.has('Big Key (Eastern Palace)'))
|
||||||
set_rule(world.get_location('Eastern Palace - Prize'), lambda state: state.has('Bow') and state.has('Big Key (Eastern Palace)'))
|
set_rule(world.get_location('Eastern Palace - Prize'), lambda state: state.can_shoot_arrows() and state.has('Big Key (Eastern Palace)'))
|
||||||
for location in ['Eastern Palace - Armos Knights', 'Eastern Palace - Big Chest']:
|
for location in ['Eastern Palace - Armos Knights', 'Eastern Palace - Big Chest']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Eastern Palace)')
|
forbid_item(world.get_location(location), 'Big Key (Eastern Palace)')
|
||||||
|
|
||||||
set_rule(world.get_location('Desert Palace - Big Chest'), lambda state: state.has('Big Key (Desert Palace)'))
|
set_rule(world.get_location('Desert Palace - Big Chest'), lambda state: state.has('Big Key (Desert Palace)'))
|
||||||
set_rule(world.get_location('Desert Palace - Torch'), lambda state: state.has_Boots())
|
set_rule(world.get_location('Desert Palace - Torch'), lambda state: state.has_Boots())
|
||||||
set_rule(world.get_entrance('Desert Palace East Wing'), lambda state: state.has('Small Key (Desert Palace)'))
|
set_rule(world.get_entrance('Desert Palace East Wing'), lambda state: state.has_key('Small Key (Desert Palace)'))
|
||||||
set_rule(world.get_location('Desert Palace - Prize'), lambda state: state.has('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and
|
set_rule(world.get_location('Desert Palace - Prize'), lambda state: state.has_key('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and
|
||||||
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow')))
|
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.can_shoot_arrows()))
|
||||||
set_rule(world.get_location('Desert Palace - Lanmolas'), lambda state: state.has('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and
|
set_rule(world.get_location('Desert Palace - Lanmolas'), lambda state: state.has_key('Small Key (Desert Palace)') and state.has('Big Key (Desert Palace)') and state.has_fire_source() and
|
||||||
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Bow')))
|
(state.has_blunt_weapon() or state.has('Fire Rod') or state.has('Ice Rod') or state.can_shoot_arrows()))
|
||||||
for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Chest']:
|
for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Chest']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Desert Palace)')
|
forbid_item(world.get_location(location), 'Big Key (Desert Palace)')
|
||||||
|
|
||||||
for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Key Chest', 'Desert Palace - Compass Chest']:
|
for location in ['Desert Palace - Lanmolas', 'Desert Palace - Big Key Chest', 'Desert Palace - Compass Chest']:
|
||||||
forbid_item(world.get_location(location), 'Small Key (Desert Palace)')
|
forbid_item(world.get_location(location), 'Small Key (Desert Palace)')
|
||||||
|
|
||||||
set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.has('Small Key (Tower of Hera)') or item_name(state, 'Tower of Hera - Big Key Chest') == 'Small Key (Tower of Hera)')
|
set_rule(world.get_entrance('Tower of Hera Small Key Door'), lambda state: state.has_key('Small Key (Tower of Hera)') or item_name(state, 'Tower of Hera - Big Key Chest') == 'Small Key (Tower of Hera)')
|
||||||
set_rule(world.get_entrance('Tower of Hera Big Key Door'), lambda state: state.has('Big Key (Tower of Hera)'))
|
set_rule(world.get_entrance('Tower of Hera Big Key Door'), lambda state: state.has('Big Key (Tower of Hera)'))
|
||||||
set_rule(world.get_location('Tower of Hera - Big Chest'), lambda state: state.has('Big Key (Tower of Hera)'))
|
set_rule(world.get_location('Tower of Hera - Big Chest'), lambda state: state.has('Big Key (Tower of Hera)'))
|
||||||
set_rule(world.get_location('Tower of Hera - Big Key Chest'), lambda state: state.has_fire_source())
|
set_rule(world.get_location('Tower of Hera - Big Key Chest'), lambda state: state.has_fire_source())
|
||||||
|
@ -243,11 +255,13 @@ def global_rules(world):
|
||||||
set_rule(world.get_location('Tower of Hera - Prize'), lambda state: state.has_blunt_weapon())
|
set_rule(world.get_location('Tower of Hera - Prize'), lambda state: state.has_blunt_weapon())
|
||||||
for location in ['Tower of Hera - Moldorm', 'Tower of Hera - Big Chest', 'Tower of Hera - Compass Chest']:
|
for location in ['Tower of Hera - Moldorm', 'Tower of Hera - Big Chest', 'Tower of Hera - Compass Chest']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Tower of Hera)')
|
forbid_item(world.get_location(location), 'Big Key (Tower of Hera)')
|
||||||
for location in ['Tower of Hera - Big Key Chest']:
|
# for location in ['Tower of Hera - Big Key Chest']:
|
||||||
forbid_item(world.get_location(location), 'Small Key (Tower of Hera)')
|
# forbid_item(world.get_location(location), 'Small Key (Tower of Hera)')
|
||||||
|
|
||||||
set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.can_reach('Dam'))
|
set_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has('Flippers') and state.has('Open Floodgate'))
|
||||||
set_rule(world.get_entrance('Swamp Palace Small Key Door'), lambda state: state.has('Small Key (Swamp Palace)'))
|
add_rule(world.get_location('Sunken Treasure'), lambda state: state.has('Open Floodgate'))
|
||||||
|
|
||||||
|
set_rule(world.get_entrance('Swamp Palace Small Key Door'), lambda state: state.has_key('Small Key (Swamp Palace)'))
|
||||||
set_rule(world.get_entrance('Swamp Palace (Center)'), lambda state: state.has('Hammer'))
|
set_rule(world.get_entrance('Swamp Palace (Center)'), lambda state: state.has('Hammer'))
|
||||||
set_rule(world.get_location('Swamp Palace - Big Chest'), lambda state: state.has('Big Key (Swamp Palace)') or item_name(state, 'Swamp Palace - Big Chest') == 'Big Key (Swamp Palace)')
|
set_rule(world.get_location('Swamp Palace - Big Chest'), lambda state: state.has('Big Key (Swamp Palace)') or item_name(state, 'Swamp Palace - Big Chest') == 'Big Key (Swamp Palace)')
|
||||||
set_always_allow(world.get_location('Swamp Palace - Big Chest'), lambda state, item: item.name == 'Big Key (Swamp Palace)')
|
set_always_allow(world.get_location('Swamp Palace - Big Chest'), lambda state, item: item.name == 'Big Key (Swamp Palace)')
|
||||||
|
@ -258,48 +272,48 @@ def global_rules(world):
|
||||||
forbid_item(world.get_location(location), 'Big Key (Swamp Palace)')
|
forbid_item(world.get_location(location), 'Big Key (Swamp Palace)')
|
||||||
|
|
||||||
set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.has('Big Key (Thieves Town)'))
|
set_rule(world.get_entrance('Thieves Town Big Key Door'), lambda state: state.has('Big Key (Thieves Town)'))
|
||||||
set_rule(world.get_entrance('Blind Fight'), lambda state: state.has('Small Key (Thieves Town)') and (state.has_blunt_weapon() or state.has('Cane of Somaria') or state.has('Cane of Byrna')))
|
set_rule(world.get_entrance('Blind Fight'), lambda state: state.has_key('Small Key (Thieves Town)') and (state.has_blunt_weapon() or state.has('Cane of Somaria') or state.has('Cane of Byrna')))
|
||||||
set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: (state.has('Small Key (Thieves Town)') or item_name(state, 'Thieves\' Town - Big Chest') == 'Small Key (Thieves Town)') and state.has('Hammer'))
|
set_rule(world.get_location('Thieves\' Town - Big Chest'), lambda state: (state.has_key('Small Key (Thieves Town)') or item_name(state, 'Thieves\' Town - Big Chest') == 'Small Key (Thieves Town)') and state.has('Hammer'))
|
||||||
set_always_allow(world.get_location('Thieves\' Town - Big Chest'), lambda state, item: item.name == 'Small Key (Thieves Town)' and state.has('Hammer'))
|
set_always_allow(world.get_location('Thieves\' Town - Big Chest'), lambda state, item: item.name == 'Small Key (Thieves Town)' and state.has('Hammer'))
|
||||||
set_rule(world.get_location('Thieves\' Town - Attic'), lambda state: state.has('Small Key (Thieves Town)'))
|
set_rule(world.get_location('Thieves\' Town - Attic'), lambda state: state.has_key('Small Key (Thieves Town)'))
|
||||||
for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves Town - Blind']:
|
for location in ['Thieves\' Town - Attic', 'Thieves\' Town - Big Chest', 'Thieves\' Town - Blind\'s Cell', 'Thieves Town - Blind']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Thieves Town)')
|
forbid_item(world.get_location(location), 'Big Key (Thieves Town)')
|
||||||
for location in ['Thieves\' Town - Attic', 'Thieves Town - Blind']:
|
for location in ['Thieves\' Town - Attic', 'Thieves Town - Blind']:
|
||||||
forbid_item(world.get_location(location), 'Small Key (Thieves Town)')
|
forbid_item(world.get_location(location), 'Small Key (Thieves Town)')
|
||||||
|
|
||||||
set_rule(world.get_entrance('Skull Woods First Section South Door'), lambda state: state.has('Small Key (Skull Woods)'))
|
set_rule(world.get_entrance('Skull Woods First Section South Door'), lambda state: state.has_key('Small Key (Skull Woods)'))
|
||||||
set_rule(world.get_entrance('Skull Woods First Section (Right) North Door'), lambda state: state.has('Small Key (Skull Woods)'))
|
set_rule(world.get_entrance('Skull Woods First Section (Right) North Door'), lambda state: state.has_key('Small Key (Skull Woods)'))
|
||||||
set_rule(world.get_entrance('Skull Woods First Section West Door'), lambda state: state.has('Small Key (Skull Woods)', 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section
|
set_rule(world.get_entrance('Skull Woods First Section West Door'), lambda state: state.has_key('Small Key (Skull Woods)', 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section
|
||||||
set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit'), lambda state: state.has('Small Key (Skull Woods)', 2))
|
set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit'), lambda state: state.has_key('Small Key (Skull Woods)', 2))
|
||||||
set_rule(world.get_location('Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)') or item_name(state, 'Skull Woods - Big Chest') == 'Big Key (Skull Woods)')
|
set_rule(world.get_location('Skull Woods - Big Chest'), lambda state: state.has('Big Key (Skull Woods)') or item_name(state, 'Skull Woods - Big Chest') == 'Big Key (Skull Woods)')
|
||||||
set_always_allow(world.get_location('Skull Woods - Big Chest'), lambda state, item: item.name == 'Big Key (Skull Woods)')
|
set_always_allow(world.get_location('Skull Woods - Big Chest'), lambda state, item: item.name == 'Big Key (Skull Woods)')
|
||||||
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and state.has_sword()) # sword required for curtain
|
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has_key('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and state.has_sword()) # sword required for curtain
|
||||||
for location in ['Skull Woods - Mothula']:
|
for location in ['Skull Woods - Mothula']:
|
||||||
forbid_item(world.get_location(location), 'Small Key (Skull Woods)')
|
forbid_item(world.get_location(location), 'Small Key (Skull Woods)')
|
||||||
|
|
||||||
set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or (state.has('Bombos') and state.has_sword()))
|
set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or (state.has('Bombos') and state.has_sword()))
|
||||||
set_rule(world.get_location('Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)'))
|
set_rule(world.get_location('Ice Palace - Big Chest'), lambda state: state.has('Big Key (Ice Palace)'))
|
||||||
set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.has('Big Key (Ice Palace)') and (state.has('Small Key (Ice Palace)', 2) or (state.has('Cane of Somaria') and state.has('Small Key (Ice Palace)', 1))))
|
set_rule(world.get_entrance('Ice Palace (Kholdstare)'), lambda state: state.can_lift_rocks() and state.has('Hammer') and state.has('Big Key (Ice Palace)') and (state.has_key('Small Key (Ice Palace)', 2) or (state.has('Cane of Somaria') and state.has_key('Small Key (Ice Palace)', 1))))
|
||||||
set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has('Small Key (Ice Palace)'))) and (state.world.can_take_damage or state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna')))
|
set_rule(world.get_entrance('Ice Palace (East)'), lambda state: (state.has('Hookshot') or (item_in_locations(state, 'Big Key (Ice Palace)', ['Ice Palace - Spike Room', 'Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']) and state.has_key('Small Key (Ice Palace)'))) and (state.world.can_take_damage or state.has('Hookshot') or state.has('Cape') or state.has('Cane of Byrna')))
|
||||||
set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer'))
|
set_rule(world.get_entrance('Ice Palace (East Top)'), lambda state: state.can_lift_rocks() and state.has('Hammer'))
|
||||||
for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']:
|
for location in ['Ice Palace - Big Chest', 'Ice Palace - Kholdstare']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Ice Palace)')
|
forbid_item(world.get_location(location), 'Big Key (Ice Palace)')
|
||||||
|
|
||||||
set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: (state.has_Boots() or state.has('Hookshot')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.has('Bow'))) # need to defeat wizzrobes, bombs don't work ...
|
set_rule(world.get_entrance('Misery Mire Entrance Gap'), lambda state: (state.has_Boots() or state.has('Hookshot')) and (state.has_sword() or state.has('Fire Rod') or state.has('Ice Rod') or state.has('Hammer') or state.has('Cane of Somaria') or state.can_shoot_arrows())) # need to defeat wizzrobes, bombs don't work ...
|
||||||
set_rule(world.get_location('Misery Mire - Big Chest'), lambda state: state.has('Big Key (Misery Mire)'))
|
set_rule(world.get_location('Misery Mire - Big Chest'), lambda state: state.has('Big Key (Misery Mire)'))
|
||||||
set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: (state.world.can_take_damage and state.has_hearts(4)) or state.has('Cane of Byrna') or state.has('Cape'))
|
set_rule(world.get_location('Misery Mire - Spike Chest'), lambda state: (state.world.can_take_damage and state.has_hearts(4)) or state.has('Cane of Byrna') or state.has('Cape'))
|
||||||
set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)'))
|
set_rule(world.get_entrance('Misery Mire Big Key Door'), lambda state: state.has('Big Key (Misery Mire)'))
|
||||||
# you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ...
|
# you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ...
|
||||||
# big key gives backdoor access to that from the teleporter in the north west
|
# big key gives backdoor access to that from the teleporter in the north west
|
||||||
set_rule(world.get_location('Misery Mire - Map Chest'), lambda state: state.has('Small Key (Misery Mire)', 1) or state.has('Big Key (Misery Mire)'))
|
set_rule(world.get_location('Misery Mire - Map Chest'), lambda state: state.has_key('Small Key (Misery Mire)', 1) or state.has('Big Key (Misery Mire)'))
|
||||||
# in addition, you can open the door to the map room before getting access to a color switch, so this is locked behing 2 small keys or the big key...
|
# in addition, you can open the door to the map room before getting access to a color switch, so this is locked behing 2 small keys or the big key...
|
||||||
set_rule(world.get_location('Misery Mire - Main Lobby'), lambda state: state.has('Small Key (Misery Mire)', 2) or state.has('Big Key (Misery Mire)'))
|
set_rule(world.get_location('Misery Mire - Main Lobby'), lambda state: state.has_key('Small Key (Misery Mire)', 2) or state.has_key('Big Key (Misery Mire)'))
|
||||||
# we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet
|
# we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet
|
||||||
set_rule(world.get_entrance('Misery Mire (West)'), lambda state: state.has('Small Key (Misery Mire)', 2) if ((item_name(state, 'Misery Mire - Compass Chest') in ['Big Key (Misery Mire)']) or
|
set_rule(world.get_entrance('Misery Mire (West)'), lambda state: state.has_key('Small Key (Misery Mire)', 2) if ((item_name(state, 'Misery Mire - Compass Chest') in ['Big Key (Misery Mire)']) or
|
||||||
(item_name(state, 'Misery Mire - Big Key Chest') in ['Big Key (Misery Mire)'])) else state.has('Small Key (Misery Mire)', 3))
|
(item_name(state, 'Misery Mire - Big Key Chest') in ['Big Key (Misery Mire)'])) else state.has_key('Small Key (Misery Mire)', 3))
|
||||||
set_rule(world.get_location('Misery Mire - Compass Chest'), lambda state: state.has_fire_source())
|
set_rule(world.get_location('Misery Mire - Compass Chest'), lambda state: state.has_fire_source())
|
||||||
set_rule(world.get_location('Misery Mire - Big Key Chest'), lambda state: state.has_fire_source())
|
set_rule(world.get_location('Misery Mire - Big Key Chest'), lambda state: state.has_fire_source())
|
||||||
set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria') and (state.has('Bow') or state.has_blunt_weapon()))
|
set_rule(world.get_entrance('Misery Mire (Vitreous)'), lambda state: state.has('Cane of Somaria') and (state.can_shoot_arrows() or state.has_blunt_weapon()))
|
||||||
for location in ['Misery Mire - Big Chest', 'Misery Mire - Vitreous']:
|
for location in ['Misery Mire - Big Chest', 'Misery Mire - Vitreous']:
|
||||||
forbid_item(world.get_location(location), 'Big Key (Misery Mire)')
|
forbid_item(world.get_location(location), 'Big Key (Misery Mire)')
|
||||||
|
|
||||||
|
@ -311,31 +325,31 @@ def global_rules(world):
|
||||||
set_rule(world.get_location('Turtle Rock - Big Chest'), lambda state: state.has('Big Key (Turtle Rock)') and (state.has('Cane of Somaria') or state.has('Hookshot')))
|
set_rule(world.get_location('Turtle Rock - Big Chest'), lambda state: state.has('Big Key (Turtle Rock)') and (state.has('Cane of Somaria') or state.has('Hookshot')))
|
||||||
set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)'), lambda state: state.has('Cane of Somaria') or state.has('Hookshot'))
|
set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)'), lambda state: state.has('Cane of Somaria') or state.has('Hookshot'))
|
||||||
set_rule(world.get_entrance('Turtle Rock Big Key Door'), lambda state: state.has('Big Key (Turtle Rock)'))
|
set_rule(world.get_entrance('Turtle Rock Big Key Door'), lambda state: state.has('Big Key (Turtle Rock)'))
|
||||||
set_rule(world.get_entrance('Turtle Rock Dark Room Staircase'), lambda state: state.has('Small Key (Turtle Rock)', 3))
|
set_rule(world.get_entrance('Turtle Rock Dark Room Staircase'), lambda state: state.has_key('Small Key (Turtle Rock)', 3))
|
||||||
set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)'), lambda state: state.has('Cane of Somaria'))
|
set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)'), lambda state: state.has('Cane of Somaria'))
|
||||||
set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)'), lambda state: state.has('Cane of Somaria'))
|
set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)'), lambda state: state.has('Cane of Somaria'))
|
||||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
||||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
||||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
||||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right'), lambda state: state.has('Cane of Byrna') or state.has('Cape') or state.has('Mirror Shield'))
|
||||||
set_rule(world.get_entrance('Turtle Rock (Trinexx)'), lambda state: state.has('Small Key (Turtle Rock)', 4) and state.has('Big Key (Turtle Rock)') and state.has('Cane of Somaria') and state.has('Fire Rod') and state.has('Ice Rod') and
|
set_rule(world.get_entrance('Turtle Rock (Trinexx)'), lambda state: state.has_key('Small Key (Turtle Rock)', 4) and state.has('Big Key (Turtle Rock)') and state.has('Cane of Somaria') and state.has('Fire Rod') and state.has('Ice Rod') and
|
||||||
(state.has('Hammer') or state.has_beam_sword() or (state.has_sword() and state.can_extend_magic(32))))
|
(state.has('Hammer') or state.has_beam_sword() or (state.has_sword() and state.can_extend_magic(32))))
|
||||||
|
|
||||||
set_trock_key_rules(world)
|
set_trock_key_rules(world)
|
||||||
|
|
||||||
set_rule(world.get_entrance('Palace of Darkness Bonk Wall'), lambda state: state.has('Bow'))
|
set_rule(world.get_entrance('Palace of Darkness Bonk Wall'), lambda state: state.can_shoot_arrows())
|
||||||
set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop'), lambda state: state.has('Hammer'))
|
set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop'), lambda state: state.has('Hammer'))
|
||||||
set_rule(world.get_entrance('Palace of Darkness Bridge Room'), lambda state: state.has('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area
|
set_rule(world.get_entrance('Palace of Darkness Bridge Room'), lambda state: state.has_key('Small Key (Palace of Darkness)', 1)) # If we can reach any other small key door, we already have back door access to this area
|
||||||
set_rule(world.get_entrance('Palace of Darkness Big Key Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) and state.has('Big Key (Palace of Darkness)') and state.has('Bow') and state.has('Hammer'))
|
set_rule(world.get_entrance('Palace of Darkness Big Key Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) and state.has('Big Key (Palace of Darkness)') and state.can_shoot_arrows() and state.has('Hammer'))
|
||||||
set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has('Small Key (Palace of Darkness)', 4))
|
set_rule(world.get_entrance('Palace of Darkness (North)'), lambda state: state.has_key('Small Key (Palace of Darkness)', 4))
|
||||||
set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)'))
|
set_rule(world.get_location('Palace of Darkness - Big Chest'), lambda state: state.has('Big Key (Palace of Darkness)'))
|
||||||
|
|
||||||
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Big Key Chest') in ['Small Key (Palace of Darkness)'] and state.has('Small Key (Palace of Darkness)', 3)))
|
set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Big Key Chest') in ['Small Key (Palace of Darkness)'] and state.has_key('Small Key (Palace of Darkness)', 3)))
|
||||||
set_always_allow(world.get_location('Palace of Darkness - Big Key Chest'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has('Small Key (Palace of Darkness)', 5))
|
set_always_allow(world.get_location('Palace of Darkness - Big Key Chest'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has_key('Small Key (Palace of Darkness)', 5))
|
||||||
|
|
||||||
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Harmless Hellway') in ['Small Key (Palace of Darkness)'] and state.has('Small Key (Palace of Darkness)', 4)))
|
set_rule(world.get_entrance('Palace of Darkness Spike Statue Room Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6) or (item_name(state, 'Palace of Darkness - Harmless Hellway') in ['Small Key (Palace of Darkness)'] and state.has_key('Small Key (Palace of Darkness)', 4)))
|
||||||
set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has('Small Key (Palace of Darkness)', 5))
|
set_always_allow(world.get_location('Palace of Darkness - Harmless Hellway'), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and state.has_key('Small Key (Palace of Darkness)', 5))
|
||||||
set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has('Small Key (Palace of Darkness)', 6))
|
set_rule(world.get_entrance('Palace of Darkness Maze Door'), lambda state: state.has_key('Small Key (Palace of Darkness)', 6))
|
||||||
|
|
||||||
# these key rules are conservative, you might be able to get away with more lenient rules
|
# these key rules are conservative, you might be able to get away with more lenient rules
|
||||||
randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right']
|
randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right']
|
||||||
|
@ -345,34 +359,34 @@ def global_rules(world):
|
||||||
set_rule(world.get_entrance('Ganons Tower (Tile Room)'), lambda state: state.has('Cane of Somaria'))
|
set_rule(world.get_entrance('Ganons Tower (Tile Room)'), lambda state: state.has('Cane of Somaria'))
|
||||||
set_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hammer'))
|
set_rule(world.get_entrance('Ganons Tower (Hookshot Room)'), lambda state: state.has('Hammer'))
|
||||||
|
|
||||||
set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has('Small Key (Ganons Tower)', 4) or (item_name(state, 'Ganons Tower - Map Chest') in ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)'] and state.has('Small Key (Ganons Tower)', 3)))
|
set_rule(world.get_entrance('Ganons Tower (Map Room)'), lambda state: state.has_key('Small Key (Ganons Tower)', 4) or (item_name(state, 'Ganons Tower - Map Chest') in ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)'] and state.has_key('Small Key (Ganons Tower)', 3)))
|
||||||
set_always_allow(world.get_location('Ganons Tower - Map Chest'), lambda state, item: item.name == 'Small Key (Ganons Tower)' and state.has('Small Key (Ganons Tower)', 3))
|
set_always_allow(world.get_location('Ganons Tower - Map Chest'), lambda state, item: item.name == 'Small Key (Ganons Tower)' and state.has_key('Small Key (Ganons Tower)', 3))
|
||||||
|
|
||||||
# It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere. We reflect this in the chest requirements.
|
# It is possible to need more than 2 keys to get through this entance if you spend keys elsewhere. We reflect this in the chest requirements.
|
||||||
# However we need to leave these at the lower values to derive that with 3 keys it is always possible to reach Bob and Ice Armos.
|
# However we need to leave these at the lower values to derive that with 3 keys it is always possible to reach Bob and Ice Armos.
|
||||||
set_rule(world.get_entrance('Ganons Tower (Double Switch Room)'), lambda state: state.has('Small Key (Ganons Tower)', 2))
|
set_rule(world.get_entrance('Ganons Tower (Double Switch Room)'), lambda state: state.has_key('Small Key (Ganons Tower)', 2))
|
||||||
# It is possible to need more than 3 keys ....
|
# It is possible to need more than 3 keys ....
|
||||||
set_rule(world.get_entrance('Ganons Tower (Firesnake Room)'), lambda state: state.has('Small Key (Ganons Tower)', 3))
|
set_rule(world.get_entrance('Ganons Tower (Firesnake Room)'), lambda state: state.has_key('Small Key (Ganons Tower)', 3))
|
||||||
|
|
||||||
#The actual requirements for these rooms to avoid key-lock
|
#The actual requirements for these rooms to avoid key-lock
|
||||||
set_rule(world.get_location('Ganons Tower - Firesnake Room'), lambda state: state.has('Small Key (Ganons Tower)', 3) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has('Small Key (Ganons Tower)', 2)))
|
set_rule(world.get_location('Ganons Tower - Firesnake Room'), lambda state: state.has_key('Small Key (Ganons Tower)', 3) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has_key('Small Key (Ganons Tower)', 2)))
|
||||||
for location in randomizer_room_chests:
|
for location in randomizer_room_chests:
|
||||||
set_rule(world.get_location(location), lambda state: state.has('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has('Small Key (Ganons Tower)', 3)))
|
set_rule(world.get_location(location), lambda state: state.has_key('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', randomizer_room_chests) and state.has_key('Small Key (Ganons Tower)', 3)))
|
||||||
|
|
||||||
# Once again it is possible to need more than 3 keys...
|
# Once again it is possible to need more than 3 keys...
|
||||||
set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door'), lambda state: state.has('Small Key (Ganons Tower)', 3) and state.has('Fire Rod'))
|
set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door'), lambda state: state.has_key('Small Key (Ganons Tower)', 3) and state.has('Fire Rod'))
|
||||||
# Actual requirements
|
# Actual requirements
|
||||||
for location in compass_room_chests:
|
for location in compass_room_chests:
|
||||||
set_rule(world.get_location(location), lambda state: state.has('Fire Rod') and (state.has('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', compass_room_chests) and state.has('Small Key (Ganons Tower)', 3))))
|
set_rule(world.get_location(location), lambda state: state.has('Fire Rod') and (state.has_key('Small Key (Ganons Tower)', 4) or (item_in_locations(state, 'Big Key (Ganons Tower)', compass_room_chests) and state.has_key('Small Key (Ganons Tower)', 3))))
|
||||||
|
|
||||||
set_rule(world.get_location('Ganons Tower - Big Chest'), lambda state: state.has('Big Key (Ganons Tower)'))
|
set_rule(world.get_location('Ganons Tower - Big Chest'), lambda state: state.has('Big Key (Ganons Tower)'))
|
||||||
set_rule(world.get_location('Ganons Tower - Big Key Room - Left'), lambda state: state.has('Bow') or state.has_blunt_weapon())
|
set_rule(world.get_location('Ganons Tower - Big Key Room - Left'), lambda state: state.can_shoot_arrows() or state.has_blunt_weapon())
|
||||||
set_rule(world.get_location('Ganons Tower - Big Key Chest'), lambda state: state.has('Bow') or state.has_blunt_weapon())
|
set_rule(world.get_location('Ganons Tower - Big Key Chest'), lambda state: state.can_shoot_arrows() or state.has_blunt_weapon())
|
||||||
set_rule(world.get_location('Ganons Tower - Big Key Room - Right'), lambda state: state.has('Bow') or state.has_blunt_weapon())
|
set_rule(world.get_location('Ganons Tower - Big Key Room - Right'), lambda state: state.can_shoot_arrows() or state.has_blunt_weapon())
|
||||||
set_rule(world.get_entrance('Ganons Tower Big Key Door'), lambda state: state.has('Big Key (Ganons Tower)') and state.has('Bow'))
|
set_rule(world.get_entrance('Ganons Tower Big Key Door'), lambda state: state.has('Big Key (Ganons Tower)') and state.can_shoot_arrows())
|
||||||
set_rule(world.get_entrance('Ganons Tower Torch Rooms'), lambda state: state.has_fire_source())
|
set_rule(world.get_entrance('Ganons Tower Torch Rooms'), lambda state: state.has_fire_source())
|
||||||
set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest'), lambda state: state.has('Small Key (Ganons Tower)', 3))
|
set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest'), lambda state: state.has_key('Small Key (Ganons Tower)', 3))
|
||||||
set_rule(world.get_entrance('Ganons Tower Moldorm Door'), lambda state: state.has('Small Key (Ganons Tower)', 4))
|
set_rule(world.get_entrance('Ganons Tower Moldorm Door'), lambda state: state.has_key('Small Key (Ganons Tower)', 4))
|
||||||
set_rule(world.get_entrance('Ganons Tower Moldorm Gap'), lambda state: state.has('Hookshot') and state.has_blunt_weapon())
|
set_rule(world.get_entrance('Ganons Tower Moldorm Gap'), lambda state: state.has('Hookshot') and state.has_blunt_weapon())
|
||||||
set_rule(world.get_location('Agahnim 2'), lambda state: state.has_sword() or state.has('Hammer') or state.has('Bug Catching Net'))
|
set_rule(world.get_location('Agahnim 2'), lambda state: state.has_sword() or state.has('Hammer') or state.has('Bug Catching Net'))
|
||||||
set_rule(world.get_entrance('Pyramid Hole'), lambda state: state.has('Beat Agahnim 2'))
|
set_rule(world.get_entrance('Pyramid Hole'), lambda state: state.has('Beat Agahnim 2'))
|
||||||
|
@ -382,7 +396,7 @@ def global_rules(world):
|
||||||
|
|
||||||
set_rule(world.get_location('Ganon'), lambda state: state.has_beam_sword() and state.has_fire_source() and state.has('Crystal 1') and state.has('Crystal 2')
|
set_rule(world.get_location('Ganon'), lambda state: state.has_beam_sword() and state.has_fire_source() and state.has('Crystal 1') and state.has('Crystal 2')
|
||||||
and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7')
|
and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7')
|
||||||
and (state.has('Tempered Sword') or state.has('Golden Sword') or (state.has('Silver Arrows') and state.has('Bow')) or state.has('Lamp') or state.can_extend_magic(12))) # need to light torch a sufficient amount of times
|
and (state.has('Tempered Sword') or state.has('Golden Sword') or (state.has('Silver Arrows') and state.can_shoot_arrows()) or state.has('Lamp') or state.can_extend_magic(12))) # need to light torch a sufficient amount of times
|
||||||
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has_beam_sword()) # need to damage ganon to get tiles to drop
|
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has_beam_sword()) # need to damage ganon to get tiles to drop
|
||||||
|
|
||||||
|
|
||||||
|
@ -447,8 +461,8 @@ def open_rules(world):
|
||||||
forbid_item(world.get_location('Hyrule Castle - Boomerang Chest'), 'Small Key (Escape)')
|
forbid_item(world.get_location('Hyrule Castle - Boomerang Chest'), 'Small Key (Escape)')
|
||||||
forbid_item(world.get_location('Hyrule Castle - Zelda\'s Chest'), 'Small Key (Escape)')
|
forbid_item(world.get_location('Hyrule Castle - Zelda\'s Chest'), 'Small Key (Escape)')
|
||||||
|
|
||||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest'), lambda state: state.has('Small Key (Escape)'))
|
set_rule(world.get_location('Hyrule Castle - Boomerang Chest'), lambda state: state.has_key('Small Key (Escape)'))
|
||||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest'), lambda state: state.has('Small Key (Escape)'))
|
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest'), lambda state: state.has_key('Small Key (Escape)'))
|
||||||
|
|
||||||
|
|
||||||
def swordless_rules(world):
|
def swordless_rules(world):
|
||||||
|
@ -459,15 +473,15 @@ def swordless_rules(world):
|
||||||
open_rules(world)
|
open_rules(world)
|
||||||
|
|
||||||
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has('Hammer') or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
|
set_rule(world.get_entrance('Agahnims Tower'), lambda state: state.has('Cape') or state.has('Hammer') or state.has('Beat Agahnim 1')) # barrier gets removed after killing agahnim, relevant for entrance shuffle
|
||||||
set_rule(world.get_entrance('Agahnim 1'), lambda state: (state.has('Hammer') or (state.has('Bug Catching Net') and (state.has('Fire Rod') or state.has('Bow') or state.has('Cane of Somaria')))) and state.has('Small Key (Agahnims Tower)', 2))
|
set_rule(world.get_entrance('Agahnim 1'), lambda state: (state.has('Hammer') or (state.has('Bug Catching Net') and (state.has('Fire Rod') or state.can_shoot_arrows() or state.has('Cane of Somaria')))) and state.has_key('Small Key (Agahnims Tower)', 2))
|
||||||
set_rule(world.get_location('Ether Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer'))
|
set_rule(world.get_location('Ether Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer'))
|
||||||
set_rule(world.get_location('Bombos Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer') and state.has_Mirror())
|
set_rule(world.get_location('Bombos Tablet'), lambda state: state.has('Book of Mudora') and state.has('Hammer') and state.has_Mirror())
|
||||||
set_rule(world.get_entrance('Misery Mire'), lambda state: state.has_Pearl() and state.has_misery_mire_medallion()) # sword not required to use medallion for opening in swordless (!)
|
set_rule(world.get_entrance('Misery Mire'), lambda state: state.has_Pearl() and state.has_misery_mire_medallion()) # sword not required to use medallion for opening in swordless (!)
|
||||||
set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword not required to use medallion for opening in swordless (!)
|
set_rule(world.get_entrance('Turtle Rock'), lambda state: state.has_Pearl() and state.has_turtle_rock_medallion() and state.can_reach('Turtle Rock (Top)', 'Region')) # sword not required to use medallion for opening in swordless (!)
|
||||||
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has('Small Key (Skull Woods)', 3) and state.has('Fire Rod')) # no curtain
|
set_rule(world.get_entrance('Skull Woods Torch Room'), lambda state: state.has_key('Small Key (Skull Woods)', 3) and state.has('Fire Rod') and (state.has('Hammer') or state.can_extend_magic(10))) # no curtain
|
||||||
set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos')) #in swordless mode bombos pads are present in the relevant parts of ice palace
|
set_rule(world.get_entrance('Ice Palace Entrance Room'), lambda state: state.has('Fire Rod') or state.has('Bombos')) #in swordless mode bombos pads are present in the relevant parts of ice palace
|
||||||
set_rule(world.get_location('Agahnim 2'), lambda state: state.has('Hammer') or state.has('Bug Catching Net'))
|
set_rule(world.get_location('Agahnim 2'), lambda state: state.has('Hammer') or state.has('Bug Catching Net'))
|
||||||
set_rule(world.get_location('Ganon'), lambda state: state.has('Hammer') and state.has_fire_source() and state.has('Silver Arrows') and state.has('Bow') and state.has('Crystal 1') and state.has('Crystal 2')
|
set_rule(world.get_location('Ganon'), lambda state: state.has('Hammer') and state.has_fire_source() and state.has('Silver Arrows') and state.can_shoot_arrows() and state.has('Crystal 1') and state.has('Crystal 2')
|
||||||
and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7'))
|
and state.has('Crystal 3') and state.has('Crystal 4') and state.has('Crystal 5') and state.has('Crystal 6') and state.has('Crystal 7'))
|
||||||
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has('Hammer')) # need to damage ganon to get tiles to drop
|
set_rule(world.get_entrance('Ganon Drop'), lambda state: state.has('Hammer')) # need to damage ganon to get tiles to drop
|
||||||
|
|
||||||
|
@ -475,7 +489,7 @@ def swordless_rules(world):
|
||||||
def standard_rules(world):
|
def standard_rules(world):
|
||||||
for loc in ['Sanctuary','Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
for loc in ['Sanctuary','Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||||
'Sewers - Secret Room - Right']:
|
'Sewers - Secret Room - Right']:
|
||||||
add_rule(world.get_location(loc), lambda state: state.can_kill_most_things() and state.has('Small Key (Escape)'))
|
add_rule(world.get_location(loc), lambda state: state.can_kill_most_things() and state.has_key('Small Key (Escape)'))
|
||||||
|
|
||||||
# easiest way to enforce key placement not relevant for open
|
# easiest way to enforce key placement not relevant for open
|
||||||
set_rule(world.get_location('Sewers - Dark Cross'), lambda state: state.can_kill_most_things())
|
set_rule(world.get_location('Sewers - Dark Cross'), lambda state: state.can_kill_most_things())
|
||||||
|
@ -500,18 +514,18 @@ def set_trock_key_rules(world):
|
||||||
|
|
||||||
# if we have backdoor access we can waste a key on the trinexx door, then have no lamp to reverse traverse the maze room. We simply require an additional key just to be super safe then. The backdoor access to the chest is otherwise free
|
# if we have backdoor access we can waste a key on the trinexx door, then have no lamp to reverse traverse the maze room. We simply require an additional key just to be super safe then. The backdoor access to the chest is otherwise free
|
||||||
if not can_reach_back:
|
if not can_reach_back:
|
||||||
set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 1))
|
set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has_key('Small Key (Turtle Rock)', 1))
|
||||||
else:
|
else:
|
||||||
set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has('Small Key (Turtle Rock)', 2))
|
set_rule(world.get_entrance('Turtle Rock Pokey Room'), lambda state: state.has_key('Small Key (Turtle Rock)', 2))
|
||||||
|
|
||||||
# if we have front access this transition is useless. If we don't, it's a dead end so cannot hold any small keys
|
# if we have front access this transition is useless. If we don't, it's a dead end so cannot hold any small keys
|
||||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)'), lambda state: state.has('Small Key (Turtle Rock)', 4))
|
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)'), lambda state: state.has_key('Small Key (Turtle Rock)', 4))
|
||||||
|
|
||||||
# this is just the pokey room with one more key
|
# this is just the pokey room with one more key
|
||||||
if not can_reach_back:
|
if not can_reach_back:
|
||||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 2))
|
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has_key('Small Key (Turtle Rock)', 2))
|
||||||
else:
|
else:
|
||||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has('Small Key (Turtle Rock)', 3))
|
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)'), lambda state: state.has_key('Small Key (Turtle Rock)', 3))
|
||||||
|
|
||||||
# the most complicated one
|
# the most complicated one
|
||||||
def tr_big_key_chest_keys_needed(state):
|
def tr_big_key_chest_keys_needed(state):
|
||||||
|
@ -527,8 +541,8 @@ def set_trock_key_rules(world):
|
||||||
# otherwise we could potentially have opened every other door already, so we need all 4 keys.
|
# otherwise we could potentially have opened every other door already, so we need all 4 keys.
|
||||||
return 4
|
return 4
|
||||||
|
|
||||||
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has('Small Key (Turtle Rock)', tr_big_key_chest_keys_needed(state)))
|
set_rule(world.get_location('Turtle Rock - Big Key Chest'), lambda state: state.has_key('Small Key (Turtle Rock)', tr_big_key_chest_keys_needed(state)))
|
||||||
set_always_allow(world.get_location('Turtle Rock - Big Key Chest'), lambda state, item: item.name == 'Small Key (Turtle Rock)' and state.has('Small Key (Turtle Rock)', 3))
|
set_always_allow(world.get_location('Turtle Rock - Big Key Chest'), lambda state, item: item.name == 'Small Key (Turtle Rock)' and state.has_key('Small Key (Turtle Rock)', 3))
|
||||||
|
|
||||||
# set big key restrictions
|
# set big key restrictions
|
||||||
non_big_key_locations = ['Turtle Rock - Big Chest', 'Turtle Rock - Trinexx']
|
non_big_key_locations = ['Turtle Rock - Big Chest', 'Turtle Rock - Trinexx']
|
||||||
|
@ -780,13 +794,8 @@ def set_bunny_rules(world):
|
||||||
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)',
|
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)',
|
||||||
'Turtle Rock (Eye Bridge)', 'Sewers', 'Pyramid', 'Spiral Cave (Top)', 'Desert Palace Main (Inner)']
|
'Turtle Rock (Eye Bridge)', 'Sewers', 'Pyramid', 'Spiral Cave (Top)', 'Desert Palace Main (Inner)']
|
||||||
|
|
||||||
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge']
|
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree', 'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid', 'Hype Cave - Generous Guy', 'Peg Cave', 'Bumper Cave Ledge', 'Dark Blacksmith Ruins']
|
||||||
|
|
||||||
if world.get_region('Dam').is_dark_world:
|
|
||||||
# if Dam is is dark world, then it is required to have the pearl to get the sunken item
|
|
||||||
add_rule(world.get_location('Sunken Treasure'), lambda state: state.has_Pearl())
|
|
||||||
# similarly we need perl to get across the swamp palace moat
|
|
||||||
add_rule(world.get_entrance('Swamp Palace Moat'), lambda state: state.has_Pearl())
|
|
||||||
|
|
||||||
def path_to_access_rule(path, entrance):
|
def path_to_access_rule(path, entrance):
|
||||||
return lambda state: state.can_reach(entrance) and all(rule(state) for rule in path)
|
return lambda state: state.can_reach(entrance) and all(rule(state) for rule in path)
|
||||||
|
@ -805,7 +814,7 @@ def set_bunny_rules(world):
|
||||||
|
|
||||||
# We will search entrances recursively until we find
|
# We will search entrances recursively until we find
|
||||||
# one that leads to an exclusively light world region
|
# one that leads to an exclusively light world region
|
||||||
# for each such entrance a new option ios aded that consist of:
|
# for each such entrance a new option is added that consist of:
|
||||||
# a) being able to reach it, and
|
# a) being able to reach it, and
|
||||||
# b) being able to access all entrances from there to `region`
|
# b) being able to access all entrances from there to `region`
|
||||||
seen = set([region])
|
seen = set([region])
|
||||||
|
@ -836,6 +845,10 @@ def set_bunny_rules(world):
|
||||||
for exit in region.exits:
|
for exit in region.exits:
|
||||||
add_rule(exit, rule)
|
add_rule(exit, rule)
|
||||||
|
|
||||||
|
paradox_shop = world.get_region('Light World Death Mountain Shop')
|
||||||
|
if paradox_shop.is_dark_world:
|
||||||
|
add_rule(paradox_shop.entrances[0], get_rule_to_add(paradox_shop))
|
||||||
|
|
||||||
# Add requirements for all locations that are actually in the dark world, except those available to the bunny
|
# Add requirements for all locations that are actually in the dark world, except those available to the bunny
|
||||||
for location in world.get_locations():
|
for location in world.get_locations():
|
||||||
if location.parent_region.is_dark_world:
|
if location.parent_region.is_dark_world:
|
||||||
|
|
546
Text.py
546
Text.py
|
@ -1,3 +1,4 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
text_addresses = {'Pedestal': (0x180300, 256),
|
text_addresses = {'Pedestal': (0x180300, 256),
|
||||||
'Triforce': (0x180400, 256),
|
'Triforce': (0x180400, 256),
|
||||||
'Uncle': (0x180500, 256),
|
'Uncle': (0x180500, 256),
|
||||||
|
@ -137,10 +138,13 @@ Ganon1_texts = [
|
||||||
"Did you know?\nThe biggest\nand heaviest\ncheese ever\nproduced\nweighed\n57,518 pounds\nand was 32\nfeet long.",
|
"Did you know?\nThe biggest\nand heaviest\ncheese ever\nproduced\nweighed\n57,518 pounds\nand was 32\nfeet long.",
|
||||||
"Now there was\na time, When\nyou loved me\nso. I couldn't\ndo wrong,\nAnd now you\nneed to know.\nSo How you\nlike me now?",
|
"Now there was\na time, When\nyou loved me\nso. I couldn't\ndo wrong,\nAnd now you\nneed to know.\nSo How you\nlike me now?",
|
||||||
"Did you know?\nNutrition\nexperts\nrecommend that\nat least half\nof our daily\ngrains come\nfrom whole\ngrain products",
|
"Did you know?\nNutrition\nexperts\nrecommend that\nat least half\nof our daily\ngrains come\nfrom whole\ngrain products",
|
||||||
|
"The Hemiptera\nor true bugs\nare an order\nof insects\ncovering 50k\nto 80k species\nlike aphids,\ncicadas, and\nshield bugs.",
|
||||||
|
"Thanks for\ndropping in,\nthe first\npassengers\nin a hot\nair balloon.\nwere a duck,\na sheep,\nand a rooster.",
|
||||||
|
"You think you\nare so smart?\n\nI bet you\ndidn't know\nYou can't hum\nwhile holding\nyour nose\nclosed.",
|
||||||
|
"grumble,\n\ngrumble…\ngrumble,\n\ngrumble…\nSeriously you\nwere supposed\nto bring food",
|
||||||
|
"Join me hero,\nand I shall\nmake your face\nthe greatest\nin the dark\nworld!\n\nOr else you\nwill die!",
|
||||||
]
|
]
|
||||||
TavernMan_texts = [
|
TavernMan_texts = [
|
||||||
'Did you know that talking to random NPCs wastes time in a race? I hope this information may be of use to you in the future.'
|
|
||||||
] + [
|
|
||||||
"What do you\ncall a blind\ndinosaur?\nadoyouthink-\nhesaurus\n",
|
"What do you\ncall a blind\ndinosaur?\nadoyouthink-\nhesaurus\n",
|
||||||
"A blind man\nwalks into\na bar.\nAnd a table.\nAnd a chair.\n",
|
"A blind man\nwalks into\na bar.\nAnd a table.\nAnd a chair.\n",
|
||||||
"What do ducks\nlike to eat?\n\nQuackers!\n",
|
"What do ducks\nlike to eat?\n\nQuackers!\n",
|
||||||
|
@ -433,78 +437,236 @@ class SceneLargeCreditLine(SceneCreditLine):
|
||||||
buf += LargeCreditBottomMapper.convert(self.text)
|
buf += LargeCreditBottomMapper.convert(self.text)
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
|
class MultiByteTextMapper(object):
|
||||||
|
@classmethod
|
||||||
|
def convert(cls, text, maxbytes=256):
|
||||||
|
outbuf = MultiByteCoreTextMapper.convert(text)
|
||||||
|
|
||||||
def string_to_alttp_text(s, maxbytes=256):
|
# check for max length
|
||||||
lines = s.upper().split('\n')
|
if len(outbuf) > maxbytes - 2:
|
||||||
|
outbuf = 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()
|
outbuf = bytearray()
|
||||||
lineindex = 0
|
lineindex = 0
|
||||||
|
is_intro = '{INTRO}' in text
|
||||||
|
|
||||||
while lines:
|
while lines:
|
||||||
linespace = 14
|
linespace = wrap
|
||||||
line = lines.pop(0)
|
line = lines.pop(0)
|
||||||
|
if line.startswith('{'):
|
||||||
|
outbuf.extend(cls.special_commands[line])
|
||||||
|
continue
|
||||||
|
|
||||||
words = line.split(' ')
|
words = line.split(' ')
|
||||||
outbuf.append(0x74 if lineindex == 0 else 0x75 if lineindex == 1 else 0x76) # line starter
|
outbuf.append(0x74 if lineindex == 0 else 0x75 if lineindex == 1 else 0x76) # line starter
|
||||||
|
|
||||||
while words:
|
while words:
|
||||||
word = words.pop(0)
|
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
|
# 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 cls.wordlen(word) > wrap:
|
||||||
if linespace < 14:
|
if linespace < wrap:
|
||||||
word = ' ' + word
|
word = ' ' + word
|
||||||
word_first = word[:linespace]
|
(word_first, word_rest) = cls.splitword(word, linespace)
|
||||||
words.insert(0, word[linespace:])
|
words.insert(0, word_rest)
|
||||||
lines.insert(0, ' '.join(words))
|
lines.insert(0, ' '.join(words))
|
||||||
|
|
||||||
write_word(outbuf, word_first)
|
outbuf.extend(RawMBTextMapper.convert(word_first))
|
||||||
break
|
break
|
||||||
|
|
||||||
if len(word) <= (linespace if linespace == 14 else linespace - 1):
|
if cls.wordlen(word) <= (linespace if linespace == wrap else linespace - 1):
|
||||||
if linespace < 14:
|
if linespace < wrap:
|
||||||
word = ' ' + word
|
word = ' ' + word
|
||||||
linespace -= len(word)
|
linespace -= cls.wordlen(word)
|
||||||
write_word(outbuf, word)
|
outbuf.extend(RawMBTextMapper.convert(word))
|
||||||
else:
|
else:
|
||||||
# ran out of space, push word and lines back and continue with next line
|
# ran out of space, push word and lines back and continue with next line
|
||||||
words.insert(0, word)
|
words.insert(0, word)
|
||||||
lines.insert(0, ' '.join(words))
|
lines.insert(0, ' '.join(words))
|
||||||
break
|
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
|
lineindex += 1
|
||||||
if lineindex % 3 == 0 and lines:
|
if pause and lineindex % 3 == 0 and has_more_lines:
|
||||||
outbuf.append(0x7E)
|
outbuf.append(0x7E)
|
||||||
if lineindex >= 3 and lines:
|
if lineindex >= 3 and has_more_lines:
|
||||||
outbuf.append(0x73)
|
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
|
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):
|
@classmethod
|
||||||
for char in word:
|
def splitword(cls, word, length):
|
||||||
buf.extend([0x00, char_to_alttp_char(char)])
|
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,
|
'?': 0xC6,
|
||||||
'!': 0xC7,
|
'!': 0xC7,
|
||||||
',': 0xC8,
|
',': 0xC8,
|
||||||
'-': 0xC9,
|
'-': 0xC9,
|
||||||
|
"🡄": 0xCA,
|
||||||
|
"🡆": 0xCB,
|
||||||
'…': 0xCC,
|
'…': 0xCC,
|
||||||
'.': 0xCD,
|
'.': 0xCD,
|
||||||
'~': 0xCE,
|
'~': 0xCE,
|
||||||
'~': 0xCE,
|
'~': 0xCE,
|
||||||
|
'@': [0x6A], # Links name (only works if compressed)
|
||||||
|
'>': [0x00, 0xD2, 0x00, 0xD3], # Link's face
|
||||||
"'": 0xD8,
|
"'": 0xD8,
|
||||||
'’': 0xD8,
|
'’': 0xD8,
|
||||||
|
'%': 0xDD, # Hylian Bird
|
||||||
|
'^': 0xDE, # Hylian Ankh
|
||||||
|
'=': 0xDF, # Hylian Wavy Lines
|
||||||
'↑': 0xE0,
|
'↑': 0xE0,
|
||||||
'↓': 0xE1,
|
'↓': 0xE1,
|
||||||
'→': 0xE2,
|
'→': 0xE2,
|
||||||
'←': 0xE3,
|
'←': 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,
|
'あ': 0x00,
|
||||||
'い': 0x01,
|
'い': 0x01,
|
||||||
'う': 0x02,
|
'う': 0x02,
|
||||||
|
@ -665,74 +827,338 @@ char_map = {' ': 0xFF,
|
||||||
'ェ': 0x9E,
|
'ェ': 0x9E,
|
||||||
'ォ': 0x9F}
|
'ォ': 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
|
@classmethod
|
||||||
def map_char(cls, char):
|
def map_char(cls, char):
|
||||||
if cls.number_offset is not None:
|
if char in cls.kanji:
|
||||||
if 0x30 <= ord(char) <= 0x39:
|
return [0x01, cls.kanji[char]]
|
||||||
return ord(char) + cls.number_offset
|
return super().map_char(char)
|
||||||
if 0x61 <= ord(char) <= 0x7A:
|
|
||||||
return ord(char) + cls.alpha_offset
|
|
||||||
return cls.char_map.get(char, cls.char_map[' '])
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert(cls, text):
|
def convert(cls, text):
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
for char in text.lower():
|
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
|
return buf
|
||||||
|
|
||||||
|
|
||||||
class GoldCreditMapper(TextMapper):
|
class GoldCreditMapper(CharTextMapper):
|
||||||
char_map = {' ': 0x9F,
|
char_map = {' ': 0x9F,
|
||||||
',': 0x34,
|
',': 0x34,
|
||||||
'.': 0x37,
|
"'": 0x35,
|
||||||
'-': 0x36,
|
'-': 0x36,
|
||||||
"'": 0x35}
|
'.': 0x37,}
|
||||||
alpha_offset = -0x47
|
alpha_offset = -0x47
|
||||||
|
|
||||||
|
|
||||||
class GreenCreditMapper(TextMapper):
|
class GreenCreditMapper(CharTextMapper):
|
||||||
char_map = {' ': 0x9F,
|
char_map = {' ': 0x9F,
|
||||||
'.': 0x52}
|
'·': 0x52}
|
||||||
alpha_offset = -0x29
|
alpha_offset = -0x29
|
||||||
|
|
||||||
class RedCreditMapper(TextMapper):
|
class RedCreditMapper(CharTextMapper):
|
||||||
char_map = {' ': 0x9F} #fixme
|
char_map = {' ': 0x9F}
|
||||||
alpha_offset = -0x61
|
alpha_offset = -0x61
|
||||||
|
|
||||||
class LargeCreditTopMapper(TextMapper):
|
class LargeCreditTopMapper(CharTextMapper):
|
||||||
char_map = {' ': 0x9F,
|
char_map = {' ': 0x9F,
|
||||||
"'": 0x77,
|
"'": 0x77,
|
||||||
'!': 0x78,
|
'!': 0x78,
|
||||||
'.': 0xA0,
|
'.': 0xA0,
|
||||||
'#': 0xA1,
|
'#': 0xA1,
|
||||||
'/': 0xA2,
|
'/': 0xA2,
|
||||||
':': 0xA3}
|
':': 0xA3,
|
||||||
|
',': 0xA4,
|
||||||
|
'?': 0xA5,
|
||||||
|
'=': 0xA6,
|
||||||
|
'"': 0xA7,
|
||||||
|
'-': 0xA8,
|
||||||
|
'·': 0xA9,
|
||||||
|
'•': 0xA9,
|
||||||
|
'◢': 0xAA,
|
||||||
|
'◣': 0xAB,}
|
||||||
alpha_offset = -0x04
|
alpha_offset = -0x04
|
||||||
number_offset = 0x23
|
number_offset = 0x23
|
||||||
|
|
||||||
|
|
||||||
class LargeCreditBottomMapper(TextMapper):
|
class LargeCreditBottomMapper(CharTextMapper):
|
||||||
char_map = {' ': 0x9F,
|
char_map = {' ': 0x9F,
|
||||||
"'": 0x9D,
|
"'": 0x9D,
|
||||||
'!': 0x9E,
|
'!': 0x9E,
|
||||||
'.': 0xC0,
|
'.': 0xC0,
|
||||||
'#': 0xC1,
|
'#': 0xC1,
|
||||||
'/': 0xC2,
|
'/': 0xC2,
|
||||||
':': 0xC3}
|
':': 0xC3,
|
||||||
|
',': 0xC4,
|
||||||
|
'?': 0xC5,
|
||||||
|
'=': 0xC6,
|
||||||
|
'"': 0xC7,
|
||||||
|
'-': 0xC8,
|
||||||
|
'·': 0xC9,
|
||||||
|
'•': 0xC9,
|
||||||
|
'◢': 0xCA,
|
||||||
|
'◣': 0xCB,}
|
||||||
alpha_offset = 0x22
|
alpha_offset = 0x22
|
||||||
number_offset = 0x49
|
number_offset = 0x49
|
||||||
|
|
8
Utils.py
8
Utils.py
|
@ -2,6 +2,14 @@ import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
def int16_as_bytes(value):
|
||||||
|
value = value & 0xFFFF
|
||||||
|
return [value & 0xFF, (value >> 8) & 0xFF]
|
||||||
|
|
||||||
|
def int32_as_bytes(value):
|
||||||
|
value = value & 0xFFFFFFFF
|
||||||
|
return [value & 0xFF, (value >> 8) & 0xFF, (value >> 16) & 0xFF, (value >> 24) & 0xFF]
|
||||||
|
|
||||||
def is_bundled():
|
def is_bundled():
|
||||||
return getattr(sys, 'frozen', False)
|
return getattr(sys, 'frozen', False)
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
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.
Loading…
Reference in New Issue