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
2020-01-09 01:30:00 +00:00
from Rom import patch_rom , LocalRom , write_string_to_rom , apply_rom_settings , get_sprite_from_name
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
2017-12-17 05:25:46 +00:00
def main ( args ) :
2019-10-16 06:20:28 +00:00
start_time = time . process_time ( )
2017-05-26 16:40:48 +00:00
# initialize the world
2020-01-09 16:46:07 +00:00
world = World ( 1 , ' vanilla ' , ' noglitches ' , ' standard ' , ' normal ' , ' none ' , ' on ' , ' ganon ' , ' freshness ' , False , False , False , False , False , False , None , False )
2020-01-14 09:42:27 +00:00
world . player_names [ 1 ] . append ( " Player 1 " )
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
2019-12-16 16:46:21 +00:00
world . difficulty_requirements [ 1 ] = difficulties [ world . difficulty [ 1 ] ]
2018-01-04 06:06:22 +00:00
2019-04-18 09:23:24 +00:00
create_regions ( world , 1 )
create_dungeons ( world , 1 )
2017-05-26 16:40:48 +00:00
2019-04-18 09:23:24 +00:00
link_entrances ( world , 1 )
2017-05-26 16:40:48 +00:00
logger . info ( ' Calculating Access Rules. ' )
2019-04-18 09:23:24 +00:00
set_rules ( world , 1 )
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
2019-04-18 09:23:24 +00:00
if world . get_entrance ( ' Dam ' , 1 ) . connected_region . name != ' Dam ' or world . get_entrance ( ' Swamp Palace ' , 1 ) . connected_region . name != ' Swamp Palace (Entrance) ' :
world . swamp_patch_required [ 1 ] = True
2017-06-17 11:16:30 +00:00
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. ' )
2017-07-14 12:37:34 +00:00
rom = LocalRom ( args . rom )
2020-01-14 09:42:27 +00:00
patch_rom ( world , rom , 1 , 1 , False )
2019-12-09 18:27:56 +00:00
2020-01-09 16:46:07 +00:00
apply_rom_settings ( rom , args . heartbeep , args . heartcolor , args . quickswap , args . fastmenu , args . disablemusic , args . sprite , args . ow_palettes , args . uw_palettes )
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. ' )
2019-10-16 06:20:28 +00:00
logger . debug ( ' Total Time: %s ' , time . process_time ( ) - 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 )
2019-12-16 15:54:46 +00:00
world . mode = { 1 : modestr . strip ( ) }
2017-05-26 16:40:48 +00:00
elif line . startswith ( ' !logic ' ) :
_ , logicstr = line . split ( ' : ' , 1 )
2019-12-16 12:26:07 +00:00
world . logic = { 1 : logicstr . strip ( ) }
2017-05-26 16:40:48 +00:00
elif line . startswith ( ' !goal ' ) :
_ , goalstr = line . split ( ' : ' , 1 )
2019-12-16 14:27:20 +00:00
world . goal = { 1 : goalstr . strip ( ) }
2017-06-17 11:16:30 +00:00
elif line . startswith ( ' !light_cone_sewers ' ) :
_ , sewerstr = line . split ( ' : ' , 1 )
2019-12-16 15:54:46 +00:00
world . sewer_light_cone = { 1 : sewerstr . strip ( ) . lower ( ) == ' true ' }
2017-06-17 11:16:30 +00:00
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 )
2019-12-16 15:54:46 +00:00
world . fix_trock_doors = { 1 : trdstr . strip ( ) . lower ( ) == ' true ' }
2017-06-24 17:11:47 +00:00
elif line . startswith ( ' !fix_trock_exit ' ) :
_ , trfstr = line . split ( ' : ' , 1 )
2019-12-16 17:24:34 +00:00
world . fix_trock_exit = { 1 : 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 )
2019-12-16 17:24:34 +00:00
world . fix_palaceofdarkness_exit = { 1 : podestr . strip ( ) . lower ( ) == ' true ' }
2017-06-24 17:11:47 +00:00
elif line . startswith ( ' !fix_skullwoods_exit ' ) :
_ , swestr = line . split ( ' : ' , 1 )
2019-12-16 17:24:34 +00:00
world . fix_skullwoods_exit = { 1 : swestr . strip ( ) . lower ( ) == ' true ' }
2017-06-24 17:11:47 +00:00
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 )
2019-04-18 09:23:24 +00:00
location = world . get_location ( locationstr . strip ( ) , 1 )
2017-05-26 16:40:48 +00:00
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 :
2019-04-18 09:23:24 +00:00
item = ItemFactory ( itemstr . strip ( ) , 1 )
2017-05-26 16:40:48 +00:00
if item is not None :
world . push_item ( location , item )
2019-12-13 21:37:52 +00:00
if item . smallkey or item . bigkey :
2017-06-17 12:57:12 +00:00
location . event = True
2017-06-03 15:09:53 +00:00
elif ' <=> ' in line :
entrance , exit = line . split ( ' <=> ' , 1 )
2019-04-18 09:23:24 +00:00
connect_two_way ( world , entrance . strip ( ) , exit . strip ( ) , 1 )
2017-06-03 15:09:53 +00:00
elif ' => ' in line :
entrance , exit = line . split ( ' => ' , 1 )
2019-04-18 09:23:24 +00:00
connect_entrance ( world , entrance . strip ( ) , exit . strip ( ) , 1 )
2017-06-03 15:09:53 +00:00
elif ' <= ' in line :
entrance , exit = line . split ( ' <= ' , 1 )
2019-04-18 09:23:24 +00:00
connect_exit ( world , exit . strip ( ) , entrance . strip ( ) , 1 )
2017-05-26 16:40:48 +00:00
2019-04-18 09:23:24 +00:00
world . required_medallions [ 1 ] = ( mm_medallion , tr_medallion )
2017-06-17 12:57:12 +00:00
# set up Agahnim Events
2019-04-18 09:23:24 +00:00
world . get_location ( ' Agahnim 1 ' , 1 ) . event = True
world . get_location ( ' Agahnim 1 ' , 1 ) . item = ItemFactory ( ' Beat Agahnim 1 ' , 1 )
world . get_location ( ' Agahnim 2 ' , 1 ) . event = True
world . get_location ( ' Agahnim 2 ' , 1 ) . item = ItemFactory ( ' Beat Agahnim 2 ' , 1 )
2017-06-17 12:57:12 +00:00
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 ) ' )
2020-01-08 02:43:48 +00:00
parser . add_argument ( ' --ow_palettes ' , default = ' default ' , choices = [ ' default ' , ' random ' , ' blackout ' ] )
parser . add_argument ( ' --uw_palettes ' , default = ' default ' , choices = [ ' default ' , ' random ' , ' blackout ' ] )
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 )
2020-01-09 01:30:00 +00:00
if args . sprite is not None and not os . path . isfile ( args . sprite ) and not get_sprite_from_name ( args . sprite ) :
input ( ' Could not find link sprite sheet at given location. \n Press Enter to exit. ' )
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 ( )