2017-05-15 18:28:04 +00:00
import copy
2018-01-19 02:51:43 +00:00
from enum import Enum , unique
2017-05-16 19:23:47 +00:00
import logging
2017-07-18 10:44:13 +00:00
import json
from collections import OrderedDict
2018-02-17 23:38:54 +00:00
from Utils import int16_as_bytes
2017-05-15 18:28:04 +00:00
class World ( object ) :
2018-03-14 18:31:36 +00:00
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 ) :
2017-05-16 19:23:47 +00:00
self . shuffle = shuffle
self . logic = logic
self . mode = mode
self . difficulty = difficulty
2017-11-10 10:11:40 +00:00
self . timer = timer
self . progressive = progressive
2017-05-16 19:23:47 +00:00
self . goal = goal
2017-06-04 13:02:27 +00:00
self . algorithm = algorithm
2017-10-15 16:16:07 +00:00
self . dungeons = [ ]
2017-05-15 18:28:04 +00:00
self . regions = [ ]
2018-02-17 23:38:54 +00:00
self . shops = [ ]
2017-05-15 18:28:04 +00:00
self . itempool = [ ]
2017-05-20 12:03:15 +00:00
self . seed = None
2017-05-15 18:28:04 +00:00
self . state = CollectionState ( self )
self . required_medallions = [ ' Ether ' , ' Quake ' ]
self . _cached_locations = None
self . _entrance_cache = { }
self . _region_cache = { }
self . _entrance_cache = { }
self . _location_cache = { }
2017-06-04 14:15:59 +00:00
self . required_locations = [ ]
2017-05-25 13:58:35 +00:00
self . place_dungeon_items = place_dungeon_items # configurable in future
2017-07-14 12:38:13 +00:00
self . shuffle_bonk_prizes = False
2017-05-20 12:03:15 +00:00
self . swamp_patch_required = False
2018-02-12 04:35:02 +00:00
self . powder_patch_required = False
2017-08-01 17:08:46 +00:00
self . ganon_at_pyramid = True
2018-02-25 04:42:18 +00:00
self . ganonstower_vanilla = True
2017-06-03 13:46:05 +00:00
self . sewer_light_cone = mode == ' standard '
2017-06-03 19:27:34 +00:00
self . light_world_light_cone = False
2017-06-03 13:46:05 +00:00
self . dark_world_light_cone = False
2017-06-04 11:10:22 +00:00
self . treasure_hunt_count = 0
2017-08-01 17:07:44 +00:00
self . treasure_hunt_icon = ' Triforce Piece '
2017-06-04 11:10:22 +00:00
self . clock_mode = ' off '
2018-01-22 02:43:44 +00:00
self . rupoor_cost = 10
2017-08-01 16:58:42 +00:00
self . aga_randomness = True
2017-06-04 11:10:22 +00:00
self . lock_aga_door_in_escape = False
2017-06-17 11:16:13 +00:00
self . fix_trock_doors = self . shuffle != ' vanilla '
2018-03-15 21:23:02 +00:00
self . save_and_quit_from_boss = False
2017-06-23 20:15:29 +00:00
self . check_beatable_only = check_beatable_only
2017-06-24 17:11:00 +00:00
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_trock_exit = self . shuffle not in [ ' vanilla ' , ' simple ' , ' restricted ' , ' dungeonssimple ' ]
2017-07-17 20:28:29 +00:00
self . shuffle_ganon = shuffle_ganon
self . fix_gtower_exit = self . shuffle_ganon
2017-07-17 21:13:39 +00:00
self . can_access_trock_eyebridge = None
2017-08-01 17:43:46 +00:00
self . quickswap = quickswap
2017-10-14 16:00:41 +00:00
self . fastmenu = fastmenu
2017-11-28 10:54:14 +00:00
self . disable_music = disable_music
2017-10-28 22:34:37 +00:00
self . keysanity = keysanity
2018-03-14 18:31:36 +00:00
self . retro = retro
2018-01-22 02:43:44 +00:00
self . custom = custom
self . customitemarray = customitemarray
2018-01-02 05:39:53 +00:00
self . can_take_damage = True
2018-01-04 06:06:22 +00:00
self . difficulty_requirements = None
2018-01-25 01:07:33 +00:00
self . fix_fake_world = True
2018-03-23 03:18:40 +00:00
self . dynamic_regions = [ ]
self . dynamic_locations = [ ]
2017-07-18 10:44:13 +00:00
self . spoiler = Spoiler ( self )
2018-02-24 21:16:50 +00:00
self . lamps_needed_for_dark_rooms = 1
2017-05-15 18:28:04 +00:00
2017-10-28 22:34:37 +00:00
def intialize_regions ( self ) :
for region in self . regions :
region . world = self
2017-05-15 18:28:04 +00:00
def get_region ( self , regionname ) :
if isinstance ( regionname , Region ) :
return regionname
try :
return self . _region_cache [ regionname ]
except KeyError :
for region in self . regions :
if region . name == regionname :
self . _region_cache [ regionname ] = region
return region
raise RuntimeError ( ' No such region %s ' % regionname )
def get_entrance ( self , entrance ) :
if isinstance ( entrance , Entrance ) :
return entrance
try :
return self . _entrance_cache [ entrance ]
except KeyError :
for region in self . regions :
for exit in region . exits :
if exit . name == entrance :
self . _entrance_cache [ entrance ] = exit
return exit
raise RuntimeError ( ' No such entrance %s ' % entrance )
def get_location ( self , location ) :
if isinstance ( location , Location ) :
return location
try :
return self . _location_cache [ location ]
except KeyError :
for region in self . regions :
for r_location in region . locations :
if r_location . name == location :
self . _location_cache [ location ] = r_location
return r_location
2017-05-20 18:03:13 +00:00
raise RuntimeError ( ' No such location %s ' % location )
2017-05-15 18:28:04 +00:00
2017-08-05 15:52:18 +00:00
def get_all_state ( self , keys = False ) :
2017-06-17 12:40:37 +00:00
ret = CollectionState ( self )
2017-11-04 18:23:57 +00:00
2017-06-17 12:40:37 +00:00
def soft_collect ( item ) :
if item . name . startswith ( ' Progressive ' ) :
if ' Sword ' in item . name :
if ret . has ( ' Golden Sword ' ) :
pass
2018-01-04 06:06:22 +00:00
elif ret . has ( ' Tempered Sword ' ) and self . difficulty_requirements . progressive_sword_limit > = 4 :
2017-06-17 12:40:37 +00:00
ret . prog_items . append ( ' Golden Sword ' )
2018-01-04 06:06:22 +00:00
elif ret . has ( ' Master Sword ' ) and self . difficulty_requirements . progressive_sword_limit > = 3 :
2017-06-17 12:40:37 +00:00
ret . prog_items . append ( ' Tempered Sword ' )
2018-01-04 06:06:22 +00:00
elif ret . has ( ' Fighter Sword ' ) and self . difficulty_requirements . progressive_sword_limit > = 2 :
2017-06-17 12:40:37 +00:00
ret . prog_items . append ( ' Master Sword ' )
2018-01-04 06:06:22 +00:00
elif self . difficulty_requirements . progressive_sword_limit > = 1 :
2017-06-17 12:40:37 +00:00
ret . prog_items . append ( ' Fighter Sword ' )
elif ' Glove ' in item . name :
if ret . has ( ' Titans Mitts ' ) :
pass
elif ret . has ( ' Power Glove ' ) :
ret . prog_items . append ( ' Titans Mitts ' )
else :
ret . prog_items . append ( ' Power Glove ' )
2017-12-16 20:38:48 +00:00
elif ' Shield ' in item . name :
if ret . has ( ' Mirror Shield ' ) :
pass
2018-01-04 06:06:22 +00:00
elif ret . has ( ' Red Shield ' ) and self . difficulty_requirements . progressive_shield_limit > = 3 :
2017-12-16 20:38:48 +00:00
ret . prog_items . append ( ' Mirror Shield ' )
2018-01-04 06:06:22 +00:00
elif ret . has ( ' Blue Shield ' ) and self . difficulty_requirements . progressive_shield_limit > = 2 :
2017-12-16 20:38:48 +00:00
ret . prog_items . append ( ' Red Shield ' )
2018-01-04 06:06:22 +00:00
elif self . difficulty_requirements . progressive_shield_limit > = 1 :
2017-12-16 20:38:48 +00:00
ret . prog_items . append ( ' Blue Shield ' )
2018-01-04 06:06:22 +00:00
elif item . name . startswith ( ' Bottle ' ) :
if ret . bottle_count ( ) < self . difficulty_requirements . progressive_bottle_limit :
ret . prog_items . append ( item . name )
2017-08-05 15:52:18 +00:00
elif item . advancement or item . key :
2017-06-17 12:40:37 +00:00
ret . prog_items . append ( item . name )
for item in self . itempool :
soft_collect ( item )
2017-08-05 15:52:18 +00:00
if keys :
from Items import ItemFactory
for item in ItemFactory ( [ ' Small Key (Escape) ' , ' Big Key (Eastern Palace) ' , ' Big Key (Desert Palace) ' , ' Small Key (Desert Palace) ' , ' Big Key (Tower of Hera) ' , ' Small Key (Tower of Hera) ' , ' Small Key (Agahnims Tower) ' , ' Small Key (Agahnims Tower) ' ,
' Big Key (Palace of Darkness) ' ] + [ ' Small Key (Palace of Darkness) ' ] * 6 + [ ' Big Key (Thieves Town) ' , ' Small Key (Thieves Town) ' , ' Big Key (Skull Woods) ' ] + [ ' Small Key (Skull Woods) ' ] * 3 + [ ' Big Key (Swamp Palace) ' ,
' Small Key (Swamp Palace) ' , ' Big Key (Ice Palace) ' ] + [ ' Small Key (Ice Palace) ' ] * 2 + [ ' Big Key (Misery Mire) ' , ' Big Key (Turtle Rock) ' , ' Big Key (Ganons Tower) ' ] + [ ' Small Key (Misery Mire) ' ] * 3 + [ ' Small Key (Turtle Rock) ' ] * 4 + [ ' Small Key (Ganons Tower) ' ] * 4 ) :
soft_collect ( item )
2017-07-17 21:14:31 +00:00
ret . sweep_for_events ( )
2017-12-17 05:25:46 +00:00
ret . clear_cached_unreachable ( )
2017-06-17 12:40:37 +00:00
return ret
2018-01-03 01:01:16 +00:00
def get_items ( self ) :
return [ loc . item for loc in self . get_filled_locations ( ) ] + self . itempool
2017-05-15 18:28:04 +00:00
def find_items ( self , item ) :
return [ location for location in self . get_locations ( ) if location . item is not None and location . item . name == item ]
2017-06-24 09:11:56 +00:00
def push_item ( self , location , item , collect = True ) :
2017-05-15 18:28:04 +00:00
if not isinstance ( location , Location ) :
location = self . get_location ( location )
2018-01-02 05:39:53 +00:00
if location . can_fill ( self . state , item , False ) :
2017-05-15 18:28:04 +00:00
location . item = item
item . location = location
if collect :
2018-01-01 20:55:13 +00:00
self . state . collect ( item , location . event , location )
2017-05-15 18:28:04 +00:00
2017-12-17 05:25:46 +00:00
logging . getLogger ( ' ' ) . debug ( ' Placed %s at %s ' , item , location )
2017-05-15 18:28:04 +00:00
else :
raise RuntimeError ( ' Cannot assign item %s to location %s . ' % ( item , location ) )
def get_locations ( self ) :
if self . _cached_locations is None :
self . _cached_locations = [ ]
for region in self . regions :
self . _cached_locations . extend ( region . locations )
return self . _cached_locations
2018-03-23 03:18:40 +00:00
def clear_location_cache ( self ) :
self . _cached_locations = None
2017-05-15 18:28:04 +00:00
def get_unfilled_locations ( self ) :
return [ location for location in self . get_locations ( ) if location . item is None ]
2017-06-17 12:40:37 +00:00
def get_filled_locations ( self ) :
return [ location for location in self . get_locations ( ) if location . item is not None ]
2017-05-15 18:28:04 +00:00
def get_reachable_locations ( self , state = None ) :
if state is None :
state = self . state
return [ location for location in self . get_locations ( ) if state . can_reach ( location ) ]
def get_placeable_locations ( self , state = None ) :
if state is None :
state = self . state
return [ location for location in self . get_locations ( ) if location . item is None and state . can_reach ( location ) ]
def unlocks_new_location ( self , item ) :
temp_state = self . state . copy ( )
2017-12-17 05:25:46 +00:00
temp_state . clear_cached_unreachable ( )
2017-06-17 12:40:37 +00:00
temp_state . collect ( item , True )
2017-05-26 07:55:49 +00:00
for location in self . get_unfilled_locations ( ) :
if temp_state . can_reach ( location ) and not self . state . can_reach ( location ) :
return True
return False
2017-11-19 01:43:37 +00:00
2017-11-06 03:08:36 +00:00
def has_beaten_game ( self , state ) :
2017-12-17 05:25:46 +00:00
if state . has ( ' Triforce ' ) :
return True
2017-11-06 03:08:36 +00:00
if self . goal in [ ' triforcehunt ' ] :
2017-12-17 05:25:46 +00:00
if state . item_count ( ' Triforce Piece ' ) + state . item_count ( ' Power Star ' ) > self . treasure_hunt_count :
2017-11-06 03:08:36 +00:00
return True
return False
2017-05-15 18:28:04 +00:00
2017-06-23 20:15:29 +00:00
def can_beat_game ( self , starting_state = None ) :
if starting_state :
state = starting_state . copy ( )
else :
state = CollectionState ( self )
2018-01-01 20:55:13 +00:00
if self . has_beaten_game ( state ) :
return True
prog_locations = [ location for location in self . get_locations ( ) if location . item is not None and ( location . item . advancement or location . event ) and location not in state . locations_checked ]
treasure_pieces_collected = state . item_count ( ' Triforce Piece ' ) + state . item_count ( ' Power Star ' )
2017-05-16 19:23:47 +00:00
while prog_locations :
sphere = [ ]
# build up spheres of collection radius. Everything in each sphere is independent from each other in dependencies and only depends on lower spheres
for location in prog_locations :
if state . can_reach ( location ) :
if location . item . name == ' Triforce ' :
return True
2017-06-04 12:44:23 +00:00
elif location . item . name in [ ' Triforce Piece ' , ' Power Star ' ] :
treasure_pieces_collected + = 1
2017-08-01 17:07:44 +00:00
if self . goal in [ ' triforcehunt ' ] and treasure_pieces_collected > = self . treasure_hunt_count :
2017-06-04 12:44:23 +00:00
return True
2017-05-16 19:23:47 +00:00
sphere . append ( location )
if not sphere :
# ran out of places and did not find triforce yet, quit
return False
for location in sphere :
prog_locations . remove ( location )
2018-01-01 20:55:13 +00:00
state . collect ( location . item , True , location )
2017-05-16 19:23:47 +00:00
return False
2017-05-30 05:33:23 +00:00
@property
def option_identifier ( self ) :
2018-01-28 02:07:09 +00:00
id_value = 0
id_value_max = 1
def markbool ( value ) :
nonlocal id_value , id_value_max
id_value + = id_value_max * bool ( value )
id_value_max * = 2
def marksequence ( options , value ) :
nonlocal id_value , id_value_max
id_value + = id_value_max * options . index ( value )
id_value_max * = len ( options )
markbool ( self . logic == ' noglitches ' )
marksequence ( [ ' standard ' , ' open ' , ' swordless ' ] , self . mode )
markbool ( self . place_dungeon_items )
marksequence ( [ ' ganon ' , ' pedestal ' , ' dungeons ' , ' triforcehunt ' , ' crystals ' ] , self . goal )
2018-02-20 20:34:39 +00:00
marksequence ( [ ' vanilla ' , ' simple ' , ' restricted ' , ' full ' , ' crossed ' , ' insanity ' , ' restricted_legacy ' , ' full_legacy ' , ' madness_legacy ' , ' insanity_legacy ' , ' dungeonsfull ' , ' dungeonssimple ' ] , self . shuffle )
2018-01-28 02:07:09 +00:00
marksequence ( [ ' easy ' , ' normal ' , ' hard ' , ' expert ' , ' insane ' ] , self . difficulty )
marksequence ( [ ' none ' , ' display ' , ' timed ' , ' timed-ohko ' , ' timed-countdown ' , ' ohko ' ] , self . timer )
marksequence ( [ ' on ' , ' off ' , ' random ' ] , self . progressive )
marksequence ( [ ' freshness ' , ' flood ' , ' vt21 ' , ' vt22 ' , ' vt25 ' , ' vt26 ' , ' balanced ' ] , self . algorithm )
markbool ( self . check_beatable_only )
markbool ( self . shuffle_ganon )
markbool ( self . keysanity )
2018-03-14 18:31:36 +00:00
markbool ( self . retro )
2018-01-28 02:07:09 +00:00
assert id_value_max < = 0xFFFFFFFF
return id_value
2017-05-30 05:33:23 +00:00
2017-05-15 18:28:04 +00:00
class CollectionState ( object ) :
2017-06-17 12:40:37 +00:00
def __init__ ( self , parent ) :
2017-05-16 19:23:47 +00:00
self . prog_items = [ ]
2017-05-15 18:28:04 +00:00
self . world = parent
self . region_cache = { }
self . location_cache = { }
self . entrance_cache = { }
2017-05-26 07:55:49 +00:00
self . recursion_count = 0
2017-06-17 12:40:37 +00:00
self . events = [ ]
2018-01-01 20:55:13 +00:00
self . path = { }
self . locations_checked = set ( )
2017-05-15 18:28:04 +00:00
2017-12-17 05:25:46 +00:00
def clear_cached_unreachable ( self ) :
2017-05-15 18:28:04 +00:00
# we only need to invalidate results which were False, places we could reach before we can still reach after adding more items
self . region_cache = { k : v for k , v in self . region_cache . items ( ) if v }
self . location_cache = { k : v for k , v in self . location_cache . items ( ) if v }
self . entrance_cache = { k : v for k , v in self . entrance_cache . items ( ) if v }
def copy ( self ) :
2017-06-17 12:40:37 +00:00
ret = CollectionState ( self . world )
2017-05-15 18:28:04 +00:00
ret . prog_items = copy . copy ( self . prog_items )
ret . region_cache = copy . copy ( self . region_cache )
ret . location_cache = copy . copy ( self . location_cache )
ret . entrance_cache = copy . copy ( self . entrance_cache )
2017-06-17 12:40:37 +00:00
ret . events = copy . copy ( self . events )
2018-01-01 20:55:13 +00:00
ret . path = copy . copy ( self . path )
ret . locations_checked = copy . copy ( self . locations_checked )
2017-05-15 18:28:04 +00:00
return ret
def can_reach ( self , spot , resolution_hint = None ) :
2017-05-20 12:03:15 +00:00
try :
spot_type = spot . spot_type
2017-05-26 07:55:49 +00:00
if spot_type == ' Location ' :
2017-05-20 12:03:15 +00:00
correct_cache = self . location_cache
2017-05-26 07:55:49 +00:00
elif spot_type == ' Region ' :
correct_cache = self . region_cache
2017-05-20 12:03:15 +00:00
elif spot_type == ' Entrance ' :
correct_cache = self . entrance_cache
else :
raise AttributeError
except AttributeError :
2017-05-15 18:28:04 +00:00
# try to resolve a name
if resolution_hint == ' Location ' :
spot = self . world . get_location ( spot )
correct_cache = self . location_cache
elif resolution_hint == ' Entrance ' :
spot = self . world . get_entrance ( spot )
correct_cache = self . entrance_cache
else :
# default to Region
spot = self . world . get_region ( spot )
correct_cache = self . region_cache
2017-05-26 07:55:49 +00:00
if spot . recursion_count > 0 :
return False
2017-05-15 18:28:04 +00:00
if spot not in correct_cache :
# for the purpose of evaluating results, recursion is resolved by always denying recursive access (as that ia what we are trying to figure out right now in the first place
2017-05-26 07:55:49 +00:00
spot . recursion_count + = 1
self . recursion_count + = 1
2017-05-15 18:28:04 +00:00
can_reach = spot . can_reach ( self )
2017-05-26 07:55:49 +00:00
spot . recursion_count - = 1
self . recursion_count - = 1
2017-05-15 18:28:04 +00:00
# we only store qualified false results (i.e. ones not inside a hypothetical)
if not can_reach :
2017-05-26 07:55:49 +00:00
if self . recursion_count == 0 :
2017-05-15 18:28:04 +00:00
correct_cache [ spot ] = can_reach
else :
correct_cache [ spot ] = can_reach
return can_reach
return correct_cache [ spot ]
2017-06-17 12:40:37 +00:00
def sweep_for_events ( self , key_only = False ) :
# this may need improvement
new_locations = True
checked_locations = 0
while new_locations :
reachable_events = [ location for location in self . world . get_filled_locations ( ) if location . event and ( not key_only or location . item . key ) and self . can_reach ( location ) ]
for event in reachable_events :
if event . name not in self . events :
self . events . append ( event . name )
2018-01-01 20:55:13 +00:00
self . collect ( event . item , True , event )
2017-06-17 12:40:37 +00:00
new_locations = len ( reachable_events ) > checked_locations
checked_locations = len ( reachable_events )
def has ( self , item , count = 1 ) :
if count == 1 :
2017-05-15 18:28:04 +00:00
return item in self . prog_items
2017-12-17 05:25:46 +00:00
return self . item_count ( item ) > = count
2017-11-19 01:43:37 +00:00
2018-03-15 21:23:02 +00:00
def has_key ( self , item , count = 1 ) :
if self . world . retro :
2018-02-17 23:38:54 +00:00
return self . can_buy_unlimited ( ' Small Key (Universal) ' )
2018-03-15 21:23:02 +00:00
if count == 1 :
return item in self . prog_items
return self . item_count ( item ) > = count
2018-02-17 23:38:54 +00:00
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
2017-11-06 03:08:36 +00:00
def item_count ( self , item ) :
return len ( [ pritem for pritem in self . prog_items if pritem == item ] )
2017-05-15 18:28:04 +00:00
def can_lift_rocks ( self ) :
return self . has ( ' Power Glove ' ) or self . has ( ' Titans Mitts ' )
2017-11-12 02:22:44 +00:00
def has_bottle ( self ) :
2018-01-04 06:06:22 +00:00
return self . bottle_count ( ) > 0
def bottle_count ( self ) :
return len ( [ pritem for pritem in self . prog_items if pritem . startswith ( ' Bottle ' ) ] )
2017-11-12 02:22:44 +00:00
2018-01-06 18:39:22 +00:00
def has_hearts ( self , count ) :
# Warning: This oncly considers items that are marked as advancement items
return self . heart_count ( ) > = count
def heart_count ( self ) :
# Warning: This oncly considers items that are marked as advancement items
return (
self . item_count ( ' Boss Heart Container ' )
+ self . item_count ( ' Sanctuary Heart Container ' )
+ self . item_count ( ' Piece of Heart ' ) / / 4
+ 3 # starting hearts
)
2017-05-15 18:28:04 +00:00
def can_lift_heavy_rocks ( self ) :
return self . has ( ' Titans Mitts ' )
2018-01-30 00:56:15 +00:00
def can_extend_magic ( self , smallmagic = 8 , fullrefill = False ) : #This reflects the total magic Link has, not the total extra he has.
2018-01-07 03:07:46 +00:00
basemagic = 8
if self . has ( ' Quarter Magic ' ) :
basemagic = 32
elif self . has ( ' Half Magic ' ) :
basemagic = 16
2018-02-10 22:23:35 +00:00
if self . world . difficulty == ' hard ' and not fullrefill :
2018-01-07 03:07:46 +00:00
basemagic = basemagic + int ( basemagic * 0.5 * self . bottle_count ( ) )
2018-02-10 22:23:35 +00:00
elif self . world . difficulty == ' expert ' and not fullrefill :
2018-01-07 03:07:46 +00:00
basemagic = basemagic + int ( basemagic * 0.25 * self . bottle_count ( ) )
2018-02-10 22:23:35 +00:00
elif self . world . difficulty == ' insane ' and not fullrefill :
2018-01-07 03:07:46 +00:00
basemagic = basemagic
2018-02-17 23:38:54 +00:00
elif self . can_buy_unlimited ( ' Green Potion ' ) or self . can_buy_unlimited ( ' Red Potion ' ) :
2018-01-07 03:07:46 +00:00
basemagic = basemagic + basemagic * self . bottle_count ( )
2018-02-17 23:38:54 +00:00
return basemagic > = smallmagic
2018-01-02 05:39:53 +00:00
def can_kill_most_things ( self , enemies = 5 ) :
return ( self . has_blunt_weapon ( )
or self . has ( ' Cane of Somaria ' )
or ( self . has ( ' Cane of Byrna ' ) and ( enemies < 6 or self . can_extend_Magic ( ) ) )
2018-02-17 23:38:54 +00:00
or self . can_shoot_arrows ( )
2018-01-02 05:39:53 +00:00
or self . has ( ' Fire Rod ' )
)
2018-02-17 23:38:54 +00:00
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 ' )
2017-05-15 18:28:04 +00:00
def has_sword ( self ) :
return self . has ( ' Fighter Sword ' ) or self . has ( ' Master Sword ' ) or self . has ( ' Tempered Sword ' ) or self . has ( ' Golden Sword ' )
def has_beam_sword ( self ) :
return self . has ( ' Master Sword ' ) or self . has ( ' Tempered Sword ' ) or self . has ( ' Golden Sword ' )
def has_blunt_weapon ( self ) :
return self . has_sword ( ) or self . has ( ' Hammer ' )
def has_Mirror ( self ) :
return self . has ( ' Magic Mirror ' )
def has_Boots ( self ) :
return self . has ( ' Pegasus Boots ' )
def has_Pearl ( self ) :
return self . has ( ' Moon Pearl ' )
def has_fire_source ( self ) :
return self . has ( ' Fire Rod ' ) or self . has ( ' Lamp ' )
def has_misery_mire_medallion ( self ) :
return self . has ( self . world . required_medallions [ 0 ] )
def has_turtle_rock_medallion ( self ) :
return self . has ( self . world . required_medallions [ 1 ] )
2018-01-01 20:55:13 +00:00
def collect ( self , item , event = False , location = None ) :
if location :
self . locations_checked . add ( location )
2017-05-26 07:55:49 +00:00
changed = False
2017-05-15 18:28:04 +00:00
if item . name . startswith ( ' Progressive ' ) :
if ' Sword ' in item . name :
if self . has ( ' Golden Sword ' ) :
2017-05-26 07:55:49 +00:00
pass
2018-01-04 06:06:22 +00:00
elif self . has ( ' Tempered Sword ' ) and self . world . difficulty_requirements . progressive_sword_limit > = 4 :
2017-05-16 19:23:47 +00:00
self . prog_items . append ( ' Golden Sword ' )
2017-05-26 07:55:49 +00:00
changed = True
2018-01-04 06:06:22 +00:00
elif self . has ( ' Master Sword ' ) and self . world . difficulty_requirements . progressive_sword_limit > = 3 :
2017-05-16 19:23:47 +00:00
self . prog_items . append ( ' Tempered Sword ' )
2017-05-26 07:55:49 +00:00
changed = True
2018-01-04 06:06:22 +00:00
elif self . has ( ' Fighter Sword ' ) and self . world . difficulty_requirements . progressive_sword_limit > = 2 :
2017-05-16 19:23:47 +00:00
self . prog_items . append ( ' Master Sword ' )
2017-05-26 07:55:49 +00:00
changed = True
2018-01-04 06:06:22 +00:00
elif self . world . difficulty_requirements . progressive_sword_limit > = 1 :
2017-05-16 19:23:47 +00:00
self . prog_items . append ( ' Fighter Sword ' )
2017-05-26 07:55:49 +00:00
changed = True
2017-05-15 18:28:04 +00:00
elif ' Glove ' in item . name :
if self . has ( ' Titans Mitts ' ) :
2017-05-26 07:55:49 +00:00
pass
2017-05-15 18:28:04 +00:00
elif self . has ( ' Power Glove ' ) :
2017-05-16 19:23:47 +00:00
self . prog_items . append ( ' Titans Mitts ' )
2017-05-26 07:55:49 +00:00
changed = True
2017-05-15 18:28:04 +00:00
else :
2017-05-16 19:23:47 +00:00
self . prog_items . append ( ' Power Glove ' )
2017-05-26 07:55:49 +00:00
changed = True
2017-10-14 18:45:59 +00:00
elif ' Shield ' in item . name :
if self . has ( ' Mirror Shield ' ) :
pass
2018-01-04 06:06:22 +00:00
elif self . has ( ' Red Shield ' ) and self . world . difficulty_requirements . progressive_shield_limit > = 3 :
2017-10-14 18:45:59 +00:00
self . prog_items . append ( ' Mirror Shield ' )
changed = True
2018-01-04 06:06:22 +00:00
elif self . has ( ' Blue Shield ' ) and self . world . difficulty_requirements . progressive_shield_limit > = 2 :
2017-10-14 18:45:59 +00:00
self . prog_items . append ( ' Red Shield ' )
changed = True
2018-01-04 06:06:22 +00:00
elif self . world . difficulty_requirements . progressive_shield_limit > = 1 :
2017-10-14 18:45:59 +00:00
self . prog_items . append ( ' Blue Shield ' )
changed = True
2018-01-04 06:06:22 +00:00
elif item . name . startswith ( ' Bottle ' ) :
if self . bottle_count ( ) < self . world . difficulty_requirements . progressive_bottle_limit :
self . prog_items . append ( item . name )
changed = True
2017-06-17 12:40:37 +00:00
elif event or item . advancement :
2017-05-16 19:23:47 +00:00
self . prog_items . append ( item . name )
2017-05-26 07:55:49 +00:00
changed = True
if changed :
2017-12-17 05:25:46 +00:00
self . clear_cached_unreachable ( )
2017-06-17 12:40:37 +00:00
if not event :
self . sweep_for_events ( )
2017-12-17 05:25:46 +00:00
self . clear_cached_unreachable ( )
2017-05-15 18:28:04 +00:00
2017-05-16 19:23:47 +00:00
def remove ( self , item ) :
if item . advancement :
to_remove = item . name
if to_remove . startswith ( ' Progressive ' ) :
if ' Sword ' in to_remove :
if self . has ( ' Golden Sword ' ) :
to_remove = ' Golden Sword '
elif self . has ( ' Tempered Sword ' ) :
to_remove = ' Tempered Sword '
elif self . has ( ' Master Sword ' ) :
to_remove = ' Master Sword '
elif self . has ( ' Fighter Sword ' ) :
to_remove = ' Fighter Sword '
else :
to_remove = None
elif ' Glove ' in item . name :
if self . has ( ' Titans Mitts ' ) :
to_remove = ' Titans Mitts '
elif self . has ( ' Power Glove ' ) :
to_remove = ' Power Glove '
else :
to_remove = None
if to_remove is not None :
try :
self . prog_items . remove ( to_remove )
2017-06-19 19:31:08 +00:00
except ValueError :
2017-05-16 19:23:47 +00:00
return
# invalidate caches, nothing can be trusted anymore now
self . region_cache = { }
self . location_cache = { }
self . entrance_cache = { }
2017-05-26 07:55:49 +00:00
self . recursion_count = 0
2017-05-16 19:23:47 +00:00
2017-05-15 18:28:04 +00:00
def __getattr__ ( self , item ) :
if item . startswith ( ' can_reach_ ' ) :
return self . can_reach ( item [ 10 ] )
elif item . startswith ( ' has_ ' ) :
return self . has ( item [ 4 ] )
raise RuntimeError ( ' Cannot parse %s . ' % item )
2018-01-19 02:51:43 +00:00
@unique
class RegionType ( Enum ) :
LightWorld = 1
DarkWorld = 2
Cave = 3 # Also includes Houses
Dungeon = 4
@property
def is_indoors ( self ) :
""" Shorthand for checking if Cave or Dungeon """
return self in ( RegionType . Cave , RegionType . Dungeon )
2017-05-15 18:28:04 +00:00
class Region ( object ) :
2018-01-19 02:51:43 +00:00
def __init__ ( self , name , type ) :
2017-05-15 18:28:04 +00:00
self . name = name
2018-01-19 02:51:43 +00:00
self . type = type
2017-05-15 18:28:04 +00:00
self . entrances = [ ]
self . exits = [ ]
self . locations = [ ]
2017-10-15 16:16:07 +00:00
self . dungeon = None
2018-02-17 23:38:54 +00:00
self . shop = None
2017-10-28 22:34:37 +00:00
self . world = None
2017-12-13 14:51:53 +00:00
self . is_light_world = False # will be set aftermaking connections.
2018-01-27 22:17:03 +00:00
self . is_dark_world = False
2017-05-20 12:03:15 +00:00
self . spot_type = ' Region '
2017-05-25 15:47:15 +00:00
self . hint_text = ' Hyrule '
2017-05-26 07:55:49 +00:00
self . recursion_count = 0
2017-05-15 18:28:04 +00:00
def can_reach ( self , state ) :
for entrance in self . entrances :
if state . can_reach ( entrance ) :
2018-01-01 20:55:13 +00:00
if not self in state . path :
state . path [ self ] = ( self . name , state . path . get ( entrance , None ) )
2017-05-15 18:28:04 +00:00
return True
return False
2017-10-28 22:34:37 +00:00
2017-10-15 16:16:07 +00:00
def can_fill ( self , item ) :
2017-10-28 22:34:37 +00:00
is_dungeon_item = item . key or item . map or item . compass
sewer_hack = self . world . mode == ' standard ' and item . name == ' Small Key (Escape) '
if sewer_hack or ( is_dungeon_item and not self . world . keysanity ) :
2017-12-17 05:25:46 +00:00
return self . dungeon and self . dungeon . is_dungeon_item ( item )
2017-10-28 22:34:37 +00:00
2017-10-15 16:16:07 +00:00
return True
2017-05-15 18:28:04 +00:00
def __str__ ( self ) :
return str ( self . __unicode__ ( ) )
def __unicode__ ( self ) :
return ' %s ' % self . name
class Entrance ( object ) :
def __init__ ( self , name = ' ' , parent = None ) :
self . name = name
self . parent_region = parent
self . connected_region = None
2017-05-20 12:03:15 +00:00
self . target = None
2017-06-03 13:33:11 +00:00
self . addresses = None
2017-05-20 12:03:15 +00:00
self . spot_type = ' Entrance '
2017-05-26 07:55:49 +00:00
self . recursion_count = 0
2017-06-17 11:16:13 +00:00
self . vanilla = None
2017-12-17 05:25:46 +00:00
self . access_rule = lambda state : True
2017-05-15 18:28:04 +00:00
def can_reach ( self , state ) :
2017-05-20 12:03:15 +00:00
if self . access_rule ( state ) and state . can_reach ( self . parent_region ) :
2018-01-01 20:55:13 +00:00
if not self in state . path :
state . path [ self ] = ( self . name , state . path . get ( self . parent_region , ( self . parent_region . name , None ) ) )
2017-05-20 12:03:15 +00:00
return True
2017-05-15 18:28:04 +00:00
2017-05-20 12:03:15 +00:00
return False
2017-05-15 18:28:04 +00:00
2017-06-17 11:16:13 +00:00
def connect ( self , region , addresses = None , target = None , vanilla = None ) :
2017-05-15 18:28:04 +00:00
self . connected_region = region
2017-05-20 12:03:15 +00:00
self . target = target
2017-06-03 13:33:11 +00:00
self . addresses = addresses
2017-06-17 11:16:13 +00:00
self . vanilla = vanilla
2017-05-15 18:28:04 +00:00
region . entrances . append ( self )
def __str__ ( self ) :
return str ( self . __unicode__ ( ) )
def __unicode__ ( self ) :
return ' %s ' % self . name
2017-10-28 22:34:37 +00:00
2017-10-15 16:16:07 +00:00
class Dungeon ( object ) :
def __init__ ( self , name , regions , big_key , small_keys , dungeon_items ) :
self . name = name
self . regions = regions
self . big_key = big_key
self . small_keys = small_keys
self . dungeon_items = dungeon_items
2017-10-28 22:34:37 +00:00
2017-10-15 16:16:07 +00:00
@property
def keys ( self ) :
return self . small_keys + ( [ self . big_key ] if self . big_key else [ ] )
2017-10-28 22:34:37 +00:00
2017-10-15 16:16:07 +00:00
@property
def all_items ( self ) :
2017-10-28 22:34:37 +00:00
return self . dungeon_items + self . keys
2017-10-15 16:16:07 +00:00
def is_dungeon_item ( self , item ) :
return item . name in [ dungeon_item . name for dungeon_item in self . all_items ]
2017-10-28 22:34:37 +00:00
2017-10-15 16:16:07 +00:00
def __str__ ( self ) :
return str ( self . __unicode__ ( ) )
def __unicode__ ( self ) :
return ' %s ' % self . name
2017-05-15 18:28:04 +00:00
2017-10-28 22:34:37 +00:00
2017-05-15 18:28:04 +00:00
class Location ( object ) :
2017-05-25 15:47:15 +00:00
def __init__ ( self , name = ' ' , address = None , crystal = False , hint_text = None , parent = None ) :
2017-05-15 18:28:04 +00:00
self . name = name
self . parent_region = parent
self . item = None
2017-05-25 15:47:15 +00:00
self . crystal = crystal
self . address = address
2017-05-20 12:03:15 +00:00
self . spot_type = ' Location '
2017-05-25 15:47:15 +00:00
self . hint_text = hint_text if hint_text is not None else ' Hyrule '
2017-05-26 07:55:49 +00:00
self . recursion_count = 0
2017-06-03 19:28:02 +00:00
self . staleness_count = 0
2017-06-17 12:40:37 +00:00
self . event = False
2018-01-02 05:39:53 +00:00
self . always_allow = lambda item , state : False
2017-12-17 05:25:46 +00:00
self . access_rule = lambda state : True
2018-01-02 05:39:53 +00:00
self . item_rule = lambda item : True
2017-10-28 22:34:37 +00:00
2018-01-02 05:39:53 +00:00
def can_fill ( self , state , item , check_access = True ) :
return self . always_allow ( item , self ) or ( self . parent_region . can_fill ( item ) and self . item_rule ( item ) and ( not check_access or self . can_reach ( state ) ) )
2017-05-15 18:28:04 +00:00
def can_reach ( self , state ) :
2017-05-20 12:03:15 +00:00
if self . access_rule ( state ) and state . can_reach ( self . parent_region ) :
return True
return False
2017-05-15 18:28:04 +00:00
def __str__ ( self ) :
return str ( self . __unicode__ ( ) )
def __unicode__ ( self ) :
return ' %s ' % self . name
2017-10-28 22:34:37 +00:00
class Item ( object ) :
2017-05-15 18:28:04 +00:00
2017-10-29 03:42:35 +00:00
def __init__ ( self , name = ' ' , advancement = False , priority = False , type = None , code = None , pedestal_hint = None , pedestal_credit = None , sickkid_credit = None , zora_credit = None , witch_credit = None , fluteboy_credit = None ) :
2017-05-15 18:28:04 +00:00
self . name = name
self . advancement = advancement
2017-06-04 12:43:13 +00:00
self . priority = priority
2017-10-15 16:16:07 +00:00
self . type = type
2017-10-29 03:42:35 +00:00
self . pedestal_hint_text = pedestal_hint
self . pedestal_credit_text = pedestal_credit
2017-05-25 13:58:35 +00:00
self . sickkid_credit_text = sickkid_credit
self . zora_credit_text = zora_credit
self . magicshop_credit_text = witch_credit
self . fluteboy_credit_text = fluteboy_credit
2017-05-20 12:03:15 +00:00
self . code = code
2017-05-25 13:58:35 +00:00
self . location = None
2017-10-28 22:34:37 +00:00
@property
2017-10-15 16:16:07 +00:00
def key ( self ) :
return self . type == ' SmallKey ' or self . type == ' BigKey '
2017-10-28 22:34:37 +00:00
@property
2017-10-15 16:16:07 +00:00
def crystal ( self ) :
return self . type == ' Crystal '
2017-10-28 22:34:37 +00:00
@property
2017-10-15 16:16:07 +00:00
def map ( self ) :
return self . type == ' Map '
2017-10-28 22:34:37 +00:00
@property
2017-10-15 16:16:07 +00:00
def compass ( self ) :
return self . type == ' Compass '
2017-05-15 18:28:04 +00:00
def __str__ ( self ) :
return str ( self . __unicode__ ( ) )
def __unicode__ ( self ) :
return ' %s ' % self . name
2017-05-20 12:03:15 +00:00
# have 6 address that need to be filled
class Crystal ( Item ) :
pass
2017-07-18 10:44:13 +00:00
2018-02-17 23:38:54 +00:00
@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
}
2017-07-18 10:44:13 +00:00
class Spoiler ( object ) :
def __init__ ( self , world ) :
self . world = world
2018-03-24 05:50:54 +00:00
self . entrances = OrderedDict ( )
2017-07-18 10:44:13 +00:00
self . medallions = { }
self . playthrough = { }
self . locations = { }
2018-01-01 20:55:13 +00:00
self . paths = { }
2017-07-18 10:44:13 +00:00
self . metadata = { }
2018-03-24 05:43:10 +00:00
self . shops = [ ]
2017-07-18 10:44:13 +00:00
def set_entrance ( self , entrance , exit , direction ) :
2018-03-24 05:50:54 +00:00
self . entrances [ ( entrance , direction ) ] = OrderedDict ( [ ( ' entrance ' , entrance ) , ( ' exit ' , exit ) , ( ' direction ' , direction ) ] )
2017-07-18 10:44:13 +00:00
def parse_data ( self ) :
self . medallions = OrderedDict ( [ ( ' Misery Mire ' , self . world . required_medallions [ 0 ] ) , ( ' Turtle Rock ' , self . world . required_medallions [ 1 ] ) ] )
2018-03-23 15:03:38 +00:00
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 )
2018-03-24 05:43:10 +00:00
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
2018-03-27 01:39:48 +00:00
shopdata [ ' item_ {} ' . format ( index ) ] = " {} — {} " . format ( item [ ' item ' ] , item [ ' price ' ] ) if item [ ' price ' ] else item [ ' item ' ]
2018-03-24 05:43:10 +00:00
self . shops . append ( shopdata )
2017-07-18 10:44:13 +00:00
from Main import __version__ as ERVersion
self . metadata = { ' version ' : ERVersion ,
' seed ' : self . world . seed ,
' logic ' : self . world . logic ,
' mode ' : self . world . mode ,
' goal ' : self . world . goal ,
' shuffle ' : self . world . shuffle ,
2017-08-01 17:43:46 +00:00
' algorithm ' : self . world . algorithm ,
' difficulty ' : self . world . difficulty ,
2017-11-10 10:11:40 +00:00
' timer ' : self . world . timer ,
' progressive ' : self . world . progressive ,
2017-08-01 17:43:46 +00:00
' completeable ' : not self . world . check_beatable_only ,
' dungeonitems ' : self . world . place_dungeon_items ,
2017-10-14 16:00:41 +00:00
' quickswap ' : self . world . quickswap ,
2017-10-28 22:34:37 +00:00
' fastmenu ' : self . world . fastmenu ,
2017-11-28 10:54:14 +00:00
' disable_music ' : self . world . disable_music ,
2017-10-28 22:34:37 +00:00
' keysanity ' : self . world . keysanity }
2017-07-18 10:44:13 +00:00
def to_json ( self ) :
self . parse_data ( )
out = OrderedDict ( )
2018-03-27 01:39:48 +00:00
out [ ' Entrances ' ] = list ( self . entrances . values ( ) )
2017-07-18 10:44:13 +00:00
out . update ( self . locations )
2018-03-27 01:39:48 +00:00
out [ ' Special ' ] = self . medallions
if self . shops :
out [ ' Shops ' ] = self . shops
2017-07-18 10:44:13 +00:00
out [ ' playthrough ' ] = self . playthrough
2018-01-01 20:55:13 +00:00
out [ ' paths ' ] = self . paths
2017-07-18 10:44:13 +00:00
out [ ' meta ' ] = self . metadata
return json . dumps ( out )
def to_file ( self , filename ) :
self . parse_data ( )
with open ( filename , ' w ' ) as outfile :
outfile . write ( ' ALttP Entrance Randomizer Version %s - Seed: %s \n \n ' % ( self . metadata [ ' version ' ] , self . metadata [ ' seed ' ] ) )
2017-08-01 17:43:46 +00:00
outfile . write ( ' Logic: %s \n ' % self . metadata [ ' logic ' ] )
outfile . write ( ' Mode: %s \n ' % self . metadata [ ' mode ' ] )
outfile . write ( ' Goal: %s \n ' % self . metadata [ ' goal ' ] )
outfile . write ( ' Entrance Shuffle: %s \n ' % self . metadata [ ' shuffle ' ] )
outfile . write ( ' Filling Algorithm: %s \n ' % self . metadata [ ' algorithm ' ] )
outfile . write ( ' All Locations Accessible: %s \n ' % ( ' Yes ' if self . metadata [ ' completeable ' ] else ' No, some locations may be unreachable ' ) )
outfile . write ( ' Maps and Compasses in Dungeons: %s \n ' % ( ' Yes ' if self . metadata [ ' dungeonitems ' ] else ' No ' ) )
2017-10-14 16:00:41 +00:00
outfile . write ( ' L \\ R Quickswap enabled: %s \n ' % ( ' Yes ' if self . metadata [ ' quickswap ' ] else ' No ' ) )
2018-01-05 22:53:29 +00:00
outfile . write ( ' Menu speed: %s \n ' % self . metadata [ ' fastmenu ' ] )
2017-10-28 22:34:37 +00:00
outfile . write ( ' Keysanity enabled: %s ' % ( ' Yes ' if self . metadata [ ' keysanity ' ] else ' No ' ) )
2017-07-18 10:44:13 +00:00
if self . entrances :
outfile . write ( ' \n \n Entrances: \n \n ' )
2018-03-24 05:50:54 +00:00
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 ( ) ] ) )
2017-07-18 10:44:13 +00:00
outfile . write ( ' \n \n Medallions ' )
outfile . write ( ' \n \n Misery Mire Medallion: %s ' % self . medallions [ ' Misery Mire ' ] )
outfile . write ( ' \n Turtle Rock Medallion: %s ' % self . medallions [ ' Turtle Rock ' ] )
outfile . write ( ' \n \n Locations: \n \n ' )
2018-03-24 05:43:10 +00:00
outfile . write ( ' \n ' . join ( [ ' %s : %s ' % ( location , item ) for grouping in self . locations . values ( ) for ( location , item ) in grouping . items ( ) ] ) )
outfile . write ( ' \n \n Shops: \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 ) )
2017-07-18 10:44:13 +00:00
outfile . write ( ' \n \n Playthrough: \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 ( ) ] ) )
2018-01-01 20:55:13 +00:00
outfile . write ( ' \n \n Paths: \n \n ' )
path_listings = [ ]
2018-01-07 06:31:56 +00:00
for location , path in sorted ( self . paths . items ( ) ) :
2018-01-01 20:55:13 +00:00
path_lines = [ ]
2018-01-06 21:25:14 +00:00
for region , exit in path :
2018-01-01 20:55:13 +00:00
if exit is not None :
path_lines . append ( " {} -> {} " . format ( region , exit ) )
else :
path_lines . append ( region )
path_listings . append ( " {} \n {} " . format ( location , " \n => " . join ( path_lines ) ) )
outfile . write ( ' \n ' . join ( path_listings ) )