2018-01-01 18:11:11 +00:00
#!/usr/bin/env python3
2017-12-17 05:25:46 +00:00
import argparse
import hashlib
import logging
import os
import random
import time
import sys
2017-06-17 12:57:12 +00:00
from BaseClasses import World
2017-05-26 16:40:48 +00:00
from Regions import create_regions
2017-06-03 15:09:53 +00:00
from EntranceShuffle import link_entrances , connect_entrance , connect_two_way , connect_exit
2017-12-17 22:30:50 +00:00
from Rom import patch_rom , LocalRom , Sprite , write_string_to_rom
2017-05-26 16:40:48 +00:00
from Rules import set_rules
2017-10-15 16:16:07 +00:00
from Dungeons import create_dungeons
2017-05-26 16:40:48 +00:00
from Items import ItemFactory
2018-01-04 06:06:22 +00:00
from ItemList import difficulties
2017-06-17 12:57:12 +00:00
from Main import create_playthrough
2017-05-26 16:40:48 +00:00
2017-06-17 11:16:30 +00:00
__version__ = ' 0.2-dev '
2017-05-26 16:40:48 +00:00
logic_hash = [ 182 , 244 , 144 , 92 , 149 , 200 , 93 , 183 , 124 , 169 , 226 , 46 , 111 , 163 , 5 , 193 , 13 , 112 , 125 , 101 , 128 , 84 , 31 , 67 , 107 , 94 , 184 , 100 , 189 , 18 , 8 , 171 ,
142 , 57 , 173 , 38 , 37 , 211 , 253 , 131 , 98 , 239 , 167 , 116 , 32 , 186 , 70 , 148 , 66 , 151 , 143 , 86 , 59 , 83 , 16 , 51 , 240 , 152 , 60 , 242 , 190 , 117 , 76 , 122 ,
15 , 221 , 62 , 39 , 174 , 177 , 223 , 34 , 150 , 50 , 178 , 238 , 95 , 219 , 10 , 162 , 222 , 0 , 165 , 202 , 74 , 36 , 206 , 209 , 251 , 105 , 175 , 135 , 121 , 88 , 214 , 247 ,
154 , 161 , 71 , 19 , 85 , 157 , 40 , 96 , 225 , 27 , 230 , 49 , 231 , 207 , 64 , 35 , 249 , 134 , 132 , 108 , 63 , 24 , 4 , 127 , 255 , 14 , 145 , 23 , 81 , 216 , 113 , 90 , 194 ,
110 , 65 , 229 , 43 , 1 , 11 , 168 , 147 , 103 , 156 , 77 , 80 , 220 , 28 , 227 , 213 , 198 , 172 , 79 , 75 , 140 , 44 , 146 , 188 , 17 , 6 , 102 , 56 , 235 , 166 , 89 , 218 , 246 ,
99 , 78 , 187 , 126 , 119 , 196 , 69 , 137 , 181 , 55 , 20 , 215 , 199 , 130 , 9 , 45 , 58 , 185 , 91 , 33 , 197 , 72 , 115 , 195 , 114 , 29 , 30 , 233 , 141 , 129 , 155 , 159 , 47 ,
224 , 236 , 21 , 234 , 191 , 136 , 104 , 87 , 106 , 26 , 73 , 250 , 248 , 228 , 48 , 53 , 243 , 237 , 241 , 61 , 180 , 12 , 208 , 245 , 232 , 192 , 2 , 7 , 170 , 123 , 176 , 160 , 201 ,
153 , 217 , 252 , 158 , 25 , 205 , 22 , 133 , 254 , 138 , 203 , 118 , 210 , 204 , 82 , 97 , 52 , 164 , 68 , 139 , 120 , 109 , 54 , 3 , 41 , 179 , 212 , 42 ]
2017-12-17 05:25:46 +00:00
def main ( args ) :
start_time = time . clock ( )
2017-05-26 16:40:48 +00:00
# initialize the world
2018-03-14 18:31:36 +00:00
world = World ( ' vanilla ' , ' noglitches ' , ' standard ' , ' normal ' , ' none ' , ' on ' , ' ganon ' , ' freshness ' , False , False , False , args . quickswap , args . fastmenu , args . disablemusic , False , False , False , None )
2017-05-26 16:40:48 +00:00
logger = logging . getLogger ( ' ' )
hasher = hashlib . md5 ( )
with open ( args . plando , ' rb ' ) as plandofile :
buf = plandofile . read ( )
hasher . update ( buf )
world . seed = int ( hasher . hexdigest ( ) , 16 ) % 1000000000
random . seed ( world . seed )
2017-12-17 05:25:46 +00:00
logger . info ( ' ALttP Plandomizer Version %s - Seed: %s \n \n ' , __version__ , args . plando )
2017-05-26 16:40:48 +00:00
2018-01-04 06:06:22 +00:00
world . difficulty_requirements = difficulties [ world . difficulty ]
2017-05-26 16:40:48 +00:00
create_regions ( world )
2017-10-15 16:16:07 +00:00
create_dungeons ( world )
2017-05-26 16:40:48 +00:00
2017-07-21 03:37:09 +00:00
link_entrances ( world )
2017-05-26 16:40:48 +00:00
logger . info ( ' Calculating Access Rules. ' )
2017-07-21 03:37:09 +00:00
set_rules ( world )
2017-05-26 16:40:48 +00:00
logger . info ( ' Fill the world. ' )
2017-06-17 11:16:30 +00:00
text_patches = [ ]
2017-07-21 03:37:09 +00:00
fill_world ( world , args . plando , text_patches )
2017-05-26 16:40:48 +00:00
2017-06-17 11:16:30 +00:00
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
2017-05-26 16:40:48 +00:00
logger . info ( ' Calculating playthrough. ' )
2017-06-03 15:09:53 +00:00
try :
2017-07-21 03:37:09 +00:00
create_playthrough ( world )
2017-06-03 15:09:53 +00:00
except RuntimeError :
if args . ignore_unsolvable :
pass
else :
raise
2017-05-26 16:40:48 +00:00
logger . info ( ' Patching ROM. ' )
if args . sprite is not None :
2017-12-17 22:30:50 +00:00
sprite = Sprite ( args . sprite )
2017-05-26 16:40:48 +00:00
else :
sprite = None
2017-07-14 12:37:34 +00:00
rom = LocalRom ( args . rom )
2018-03-03 18:42:46 +00:00
patch_rom ( world , rom , logic_hash , args . heartbeep , args . heartcolor , sprite )
2017-05-26 16:40:48 +00:00
2017-06-17 11:16:30 +00:00
for textname , texttype , text in text_patches :
if texttype == ' text ' :
write_string_to_rom ( rom , textname , text )
2017-12-17 05:25:46 +00:00
#elif texttype == 'credit':
# write_credits_string_to_rom(rom, textname, text)
2017-06-17 11:16:30 +00:00
2017-05-26 16:40:48 +00:00
outfilebase = ' Plando_ %s _ %s ' % ( os . path . splitext ( os . path . basename ( args . plando ) ) [ 0 ] , world . seed )
2017-07-14 12:37:34 +00:00
rom . write_to_file ( ' %s .sfc ' % outfilebase )
2017-05-26 16:40:48 +00:00
if args . create_spoiler :
2017-07-21 03:37:09 +00:00
world . spoiler . to_file ( ' %s _Spoiler.txt ' % outfilebase )
2017-05-26 16:40:48 +00:00
logger . info ( ' Done. Enjoy. ' )
2017-12-17 05:25:46 +00:00
logger . debug ( ' Total Time: %s ' , time . clock ( ) - start_time )
2017-05-26 16:40:48 +00:00
return world
2017-06-17 11:16:30 +00:00
def fill_world ( world , plando , text_patches ) :
2017-05-26 16:40:48 +00:00
mm_medallion = ' Ether '
tr_medallion = ' Quake '
logger = logging . getLogger ( ' ' )
with open ( plando , ' r ' ) as plandofile :
for line in plandofile . readlines ( ) :
2017-06-17 11:16:30 +00:00
if line . startswith ( ' # ' ) :
continue
2017-05-26 16:40:48 +00:00
if ' : ' in line :
line = line . lstrip ( )
if line . startswith ( ' ! ' ) :
if line . startswith ( ' !mm_medallion ' ) :
_ , medallionstr = line . split ( ' : ' , 1 )
mm_medallion = medallionstr . strip ( )
elif line . startswith ( ' !tr_medallion ' ) :
_ , medallionstr = line . split ( ' : ' , 1 )
tr_medallion = medallionstr . strip ( )
elif line . startswith ( ' !mode ' ) :
_ , modestr = line . split ( ' : ' , 1 )
world . mode = modestr . strip ( )
elif line . startswith ( ' !logic ' ) :
_ , logicstr = line . split ( ' : ' , 1 )
world . logic = logicstr . strip ( )
elif line . startswith ( ' !goal ' ) :
_ , goalstr = line . split ( ' : ' , 1 )
world . goal = goalstr . strip ( )
2017-06-17 11:16:30 +00:00
elif line . startswith ( ' !light_cone_sewers ' ) :
_ , sewerstr = line . split ( ' : ' , 1 )
world . sewer_light_cone = sewerstr . strip ( ) . lower ( ) == ' true '
elif line . startswith ( ' !light_cone_lw ' ) :
_ , lwconestr = line . split ( ' : ' , 1 )
world . light_world_light_cone = lwconestr . strip ( ) . lower ( ) == ' true '
elif line . startswith ( ' !light_cone_dw ' ) :
_ , dwconestr = line . split ( ' : ' , 1 )
world . dark_world_light_cone = dwconestr . strip ( ) . lower ( ) == ' true '
elif line . startswith ( ' !fix_trock_doors ' ) :
_ , trdstr = line . split ( ' : ' , 1 )
world . fix_trock_doors = trdstr . strip ( ) . lower ( ) == ' true '
2017-06-24 17:11:47 +00:00
elif line . startswith ( ' !fix_trock_exit ' ) :
_ , trfstr = line . split ( ' : ' , 1 )
world . fix_trock_exit = trfstr . strip ( ) . lower ( ) == ' true '
2017-07-14 14:11:07 +00:00
elif line . startswith ( ' !fix_gtower_exit ' ) :
_ , gtfstr = line . split ( ' : ' , 1 )
world . fix_gtower_exit = gtfstr . strip ( ) . lower ( ) == ' true '
2017-06-24 17:11:47 +00:00
elif line . startswith ( ' !fix_pod_exit ' ) :
_ , podestr = line . split ( ' : ' , 1 )
world . fix_palaceofdarkness_exit = podestr . strip ( ) . lower ( ) == ' true '
elif line . startswith ( ' !fix_skullwoods_exit ' ) :
_ , swestr = line . split ( ' : ' , 1 )
world . fix_skullwoods_exit = swestr . strip ( ) . lower ( ) == ' true '
elif line . startswith ( ' !check_beatable_only ' ) :
_ , chkbtstr = line . split ( ' : ' , 1 )
world . check_beatable_only = chkbtstr . strip ( ) . lower ( ) == ' true '
2017-08-01 17:08:46 +00:00
elif line . startswith ( ' !ganon_death_pyramid_respawn ' ) :
_ , gnpstr = line . split ( ' : ' , 1 )
world . ganon_at_pyramid = gnpstr . strip ( ) . lower ( ) == ' true '
2017-06-24 17:11:47 +00:00
elif line . startswith ( ' !save_quit_boss ' ) :
_ , sqbstr = line . split ( ' : ' , 1 )
world . save_and_quite_from_boss = sqbstr . strip ( ) . lower ( ) == ' true '
2017-06-17 11:16:30 +00:00
elif line . startswith ( ' !text_ ' ) :
textname , text = line . split ( ' : ' , 1 )
text_patches . append ( [ textname . lstrip ( ' !text_ ' ) . strip ( ) , ' text ' , text . strip ( ) ] )
2017-11-19 21:00:26 +00:00
#temporarilly removed. New credits system not ready to handle this.
#elif line.startswith('!credits_'):
# textname, text = line.split(':', 1)
# text_patches.append([textname.lstrip('!credits_').strip(), 'credits', text.strip()])
2017-05-26 16:40:48 +00:00
continue
locationstr , itemstr = line . split ( ' : ' , 1 )
location = world . get_location ( locationstr . strip ( ) )
if location is None :
2017-12-17 05:25:46 +00:00
logger . warning ( ' Unknown location: %s ' , locationstr )
2017-05-26 16:40:48 +00:00
continue
else :
item = ItemFactory ( itemstr . strip ( ) )
if item is not None :
world . push_item ( location , item )
2017-06-17 12:57:12 +00:00
if item . key :
location . event = True
2017-06-03 15:09:53 +00:00
elif ' <=> ' in line :
entrance , exit = line . split ( ' <=> ' , 1 )
2017-07-21 03:37:09 +00:00
connect_two_way ( world , entrance . strip ( ) , exit . strip ( ) )
2017-06-03 15:09:53 +00:00
elif ' => ' in line :
entrance , exit = line . split ( ' => ' , 1 )
2017-07-21 03:37:09 +00:00
connect_entrance ( world , entrance . strip ( ) , exit . strip ( ) )
2017-06-03 15:09:53 +00:00
elif ' <= ' in line :
entrance , exit = line . split ( ' <= ' , 1 )
2017-07-21 03:37:09 +00:00
connect_exit ( world , exit . strip ( ) , entrance . strip ( ) )
2017-05-26 16:40:48 +00:00
world . required_medallions = ( mm_medallion , tr_medallion )
2017-06-17 12:57:12 +00:00
# set up Agahnim Events
world . get_location ( ' Agahnim 1 ' ) . event = True
world . get_location ( ' Agahnim 1 ' ) . item = ItemFactory ( ' Beat Agahnim 1 ' )
world . get_location ( ' Agahnim 2 ' ) . event = True
world . get_location ( ' Agahnim 2 ' ) . item = ItemFactory ( ' Beat Agahnim 2 ' )
2017-05-26 16:40:48 +00:00
2017-12-17 05:25:46 +00:00
def start ( ) :
2017-05-26 16:40:48 +00:00
parser = argparse . ArgumentParser ( formatter_class = argparse . ArgumentDefaultsHelpFormatter )
parser . add_argument ( ' --create_spoiler ' , help = ' Output a Spoiler File ' , action = ' store_true ' )
2017-06-03 15:09:53 +00:00
parser . add_argument ( ' --ignore_unsolvable ' , help = ' Do not abort if seed is deemed unsolvable. ' , action = ' store_true ' )
parser . add_argument ( ' --rom ' , default = ' Zelda no Densetsu - Kamigami no Triforce (Japan).sfc ' , help = ' Path to an ALttP JAP(1.0) rom to use as a base. ' )
2017-05-26 16:40:48 +00:00
parser . add_argument ( ' --loglevel ' , default = ' info ' , const = ' info ' , nargs = ' ? ' , choices = [ ' error ' , ' info ' , ' warning ' , ' debug ' ] , help = ' Select level of logging for output. ' )
parser . add_argument ( ' --seed ' , help = ' Define seed number to generate. ' , type = int )
2018-01-05 22:53:29 +00:00
parser . add_argument ( ' --fastmenu ' , default = ' normal ' , const = ' normal ' , nargs = ' ? ' , choices = [ ' normal ' , ' instant ' , ' double ' , ' triple ' , ' quadruple ' , ' half ' ] ,
help = ''' \
Select the rate at which the menu opens and closes .
( default : % ( default ) s )
''' )
2017-05-26 16:40:48 +00:00
parser . add_argument ( ' --quickswap ' , help = ' Enable quick item swapping with L and R. ' , action = ' store_true ' )
2017-11-28 10:54:14 +00:00
parser . add_argument ( ' --disablemusic ' , help = ' Disables game music. ' , action = ' store_true ' )
2017-05-26 16:40:48 +00:00
parser . add_argument ( ' --heartbeep ' , default = ' normal ' , const = ' normal ' , nargs = ' ? ' , choices = [ ' normal ' , ' half ' , ' quarter ' , ' off ' ] ,
help = ' Select the rate at which the heart beep sound is played at low health. ' )
2018-03-03 18:42:46 +00:00
parser . add_argument ( ' --heartcolor ' , default = ' red ' , const = ' red ' , nargs = ' ? ' , choices = [ ' red ' , ' blue ' , ' green ' , ' yellow ' ] ,
help = ' Select the color of Link \' s heart meter. (default: %(default)s ) ' )
2017-05-26 16:40:48 +00:00
parser . add_argument ( ' --sprite ' , help = ' Path to a sprite sheet to use for Link. Needs to be in binary format and have a length of 0x7000 (28672) bytes. ' )
parser . add_argument ( ' --plando ' , help = ' Filled out template to use for setting up the rom. ' )
args = parser . parse_args ( )
# ToDo: Validate files further than mere existance
if not os . path . isfile ( args . rom ) :
input ( ' Could not find valid base rom for patching at expected path %s . Please run with -h to see help for further information. \n Press Enter to exit. ' % args . rom )
2017-11-19 21:00:26 +00:00
sys . exit ( 1 )
2017-05-26 16:40:48 +00:00
if not os . path . isfile ( args . plando ) :
input ( ' Could not find Plandomizer distribution at expected path %s . Please run with -h to see help for further information. \n Press Enter to exit. ' % args . plando )
2017-11-19 21:00:26 +00:00
sys . exit ( 1 )
2017-05-26 16:40:48 +00:00
if args . sprite is not None and not os . path . isfile ( args . rom ) :
input ( ' Could not find link sprite sheet at given location. \n Press Enter to exit. ' % args . sprite )
2017-11-19 21:00:26 +00:00
sys . exit ( 1 )
2017-05-26 16:40:48 +00:00
# set up logger
loglevel = { ' error ' : logging . ERROR , ' info ' : logging . INFO , ' warning ' : logging . WARNING , ' debug ' : logging . DEBUG } [ args . loglevel ]
logging . basicConfig ( format = ' %(message)s ' , level = loglevel )
main ( args = args )
2017-12-17 05:25:46 +00:00
if __name__ == ' __main__ ' :
start ( )