GK Version 1.0.0 #1

Merged
karafruit merged 56 commits from beta into main 2026-01-25 21:29:44 +00:00
29 changed files with 669 additions and 560 deletions

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
* text=auto

2
.gitignore vendored
View File

@@ -32,8 +32,6 @@ weights/
/output/ /output/
/enemizer/ /enemizer/
base2current.json
resources/user/* resources/user/*
!resources/user/.gitkeep !resources/user/.gitkeep

View File

@@ -21,6 +21,7 @@ from Tables import (
spiral_offset_table, spiral_offset_table,
) )
from Utils import int16_as_bytes from Utils import int16_as_bytes
from Versions import DRVersion, GKVersion, ORVersion
class World(object): class World(object):
@@ -79,6 +80,8 @@ class World(object):
self.dark_rooms = {} self.dark_rooms = {}
self.damage_challenge = {} self.damage_challenge = {}
self.shuffle_damage_table = {} self.shuffle_damage_table = {}
self.bosses_ganon = {}
self.bosshunt_include_agas = {}
self.ganon_item = {} self.ganon_item = {}
self.ganon_item_orig = {} self.ganon_item_orig = {}
self.custom = custom self.custom = custom
@@ -154,6 +157,8 @@ class World(object):
set_player_attr('keyshuffle', 'none') set_player_attr('keyshuffle', 'none')
set_player_attr('bigkeyshuffle', 'none') set_player_attr('bigkeyshuffle', 'none')
set_player_attr('prizeshuffle', 'none') set_player_attr('prizeshuffle', 'none')
set_player_attr('showloot', 'never')
set_player_attr('showmap', 'map')
set_player_attr('restrict_boss_items', 'none') set_player_attr('restrict_boss_items', 'none')
set_player_attr('bombbag', False) set_player_attr('bombbag', False)
set_player_attr('flute_mode', 'normal') set_player_attr('flute_mode', 'normal')
@@ -171,6 +176,8 @@ class World(object):
set_player_attr('escape_assist', []) set_player_attr('escape_assist', [])
set_player_attr('crystals_needed_for_ganon', 7) set_player_attr('crystals_needed_for_ganon', 7)
set_player_attr('crystals_needed_for_gt', 7) set_player_attr('crystals_needed_for_gt', 7)
set_player_attr('bosses_ganon', 8)
set_player_attr('bosshunt_include_agas', False)
set_player_attr('ganon_item', 'silver') set_player_attr('ganon_item', 'silver')
set_player_attr('crystals_ganon_orig', {}) set_player_attr('crystals_ganon_orig', {})
set_player_attr('crystals_gt_orig', {}) set_player_attr('crystals_gt_orig', {})
@@ -359,7 +366,7 @@ class World(object):
else: else:
if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district']: if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district']:
return False return False
elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt']: elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt', 'bosshunt']:
return True return True
else: else:
return False return False
@@ -3077,12 +3084,9 @@ class Spoiler(object):
self.doorTypes[(doorNames, player)] = OrderedDict([('player', player), ('doorNames', doorNames), ('type', type)]) self.doorTypes[(doorNames, player)] = OrderedDict([('player', player), ('doorNames', doorNames), ('type', type)])
def parse_meta(self): def parse_meta(self):
from Main import __version__ as ERVersion
from OverworldShuffle import __version__ as ORVersion
self.startinventory = list(map(str, self.world.precollected_items)) self.startinventory = list(map(str, self.world.precollected_items))
self.metadata = {'version': ERVersion, self.metadata = {'version': GKVersion,
'versions': {'Door':ERVersion, 'Overworld':ORVersion}, 'versions': {'Door': DRVersion, 'Overworld': ORVersion},
'logic': self.world.logic, 'logic': self.world.logic,
'mode': self.world.mode, 'mode': self.world.mode,
'bombbag': self.world.bombbag, 'bombbag': self.world.bombbag,
@@ -3121,6 +3125,8 @@ class Spoiler(object):
'beemizer': self.world.beemizer, 'beemizer': self.world.beemizer,
'gt_crystals': self.world.crystals_needed_for_gt, 'gt_crystals': self.world.crystals_needed_for_gt,
'ganon_crystals': self.world.crystals_needed_for_ganon, 'ganon_crystals': self.world.crystals_needed_for_ganon,
'ganon_bosses': self.world.bosses_ganon,
'bosshunt_include_agas': self.world.bosshunt_include_agas,
'ganon_item': self.world.ganon_item, 'ganon_item': self.world.ganon_item,
'open_pyramid': self.world.open_pyramid, 'open_pyramid': self.world.open_pyramid,
'accessibility': self.world.accessibility, 'accessibility': self.world.accessibility,
@@ -3131,6 +3137,8 @@ class Spoiler(object):
'keyshuffle': self.world.keyshuffle, 'keyshuffle': self.world.keyshuffle,
'bigkeyshuffle': self.world.bigkeyshuffle, 'bigkeyshuffle': self.world.bigkeyshuffle,
'prizeshuffle': self.world.prizeshuffle, 'prizeshuffle': self.world.prizeshuffle,
'showloot': self.world.showloot,
'showmap': self.world.showmap,
'boss_shuffle': self.world.boss_shuffle, 'boss_shuffle': self.world.boss_shuffle,
'enemy_shuffle': self.world.enemy_shuffle, 'enemy_shuffle': self.world.enemy_shuffle,
'enemy_health': self.world.enemy_health, 'enemy_health': self.world.enemy_health,
@@ -3295,7 +3303,7 @@ class Spoiler(object):
self.parse_meta() self.parse_meta()
with open(filename, 'w') as outfile: with open(filename, 'w') as outfile:
line_width = 35 line_width = 35
outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed)) outfile.write('ALttP GwaaKiwi Randomizer - Seed: %s\n\n' % (self.world.seed))
for k,v in self.metadata["versions"].items(): for k,v in self.metadata["versions"].items():
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v) outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
for player in range(1, self.world.players + 1): for player in range(1, self.world.players + 1):
@@ -3310,7 +3318,7 @@ class Spoiler(object):
self.parse_meta() self.parse_meta()
with open(filename, 'w') as outfile: with open(filename, 'w') as outfile:
line_width = 35 line_width = 35
outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed)) outfile.write('ALttP GwaaKiwi Randomizer - Seed: %s\n\n' % (self.world.seed))
for k,v in self.metadata["versions"].items(): for k,v in self.metadata["versions"].items():
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v) outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
if self.metadata['user_notes']: if self.metadata['user_notes']:
@@ -3341,6 +3349,10 @@ class Spoiler(object):
if custom['ganongoal'] and 'requirements' in custom['ganongoal']: if custom['ganongoal'] and 'requirements' in custom['ganongoal']:
outfile.write('Ganon Requirement:'.ljust(line_width) + 'custom\n') outfile.write('Ganon Requirement:'.ljust(line_width) + 'custom\n')
outfile.write(' %s\n' % custom['ganongoal']['goaltext']) outfile.write(' %s\n' % custom['ganongoal']['goaltext'])
elif self.metadata['goal'][player] == 'bosshunt':
outfile.write('Ganon Requirement:'.ljust(line_width) + '%s bosses%s\n' %
(str(self.world.bosses_ganon[player]),
' (including both Agahnims)' if self.world.bosshunt_include_agas[player] else ''))
else: else:
outfile.write('Ganon Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_ganon_orig[player])) outfile.write('Ganon Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_ganon_orig[player]))
if custom['pedgoal'] and 'requirements' in custom['pedgoal']: if custom['pedgoal'] and 'requirements' in custom['pedgoal']:
@@ -3395,6 +3407,8 @@ class Spoiler(object):
outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['keyshuffle'][player]) outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['keyshuffle'][player])
outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['bigkeyshuffle'][player]) outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['bigkeyshuffle'][player])
outfile.write('Prize Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['prizeshuffle'][player]) outfile.write('Prize Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['prizeshuffle'][player])
outfile.write('Show Value of Checks:'.ljust(line_width) + '%s\n' % self.metadata['showloot'][player])
outfile.write('Show Map:'.ljust(line_width) + '%s\n' % self.metadata['showmap'][player])
outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player]) outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player])
outfile.write('\n') outfile.write('\n')
outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player]) outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player])
@@ -3748,8 +3762,9 @@ world_mode = {"open": 0, "standard": 1, "inverted": 2}
sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3} sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3}
# byte 2: GGGD DFFH (goal, diff, item_func, hints) # byte 2: GGGD DFFH (goal, diff, item_func, hints)
goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3, 'crystals': 4, 'trinity': 5, goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3,
'ganonhunt': 6, 'completionist': 7, 'sanctuary': 1} 'crystals': 4, 'trinity': 5, 'ganonhunt': 6, 'completionist': 7,
'sanctuary': 1, 'bosshunt': 6}
diff_mode = {"normal": 0, "hard": 1, "expert": 2} diff_mode = {"normal": 0, "hard": 1, "expert": 2}
func_mode = {"normal": 0, "hard": 1, "expert": 2} func_mode = {"normal": 0, "hard": 1, "expert": 2}

10
CLI.py
View File

@@ -106,7 +106,7 @@ def parse_cli(argv, no_defaults=False):
ret = parser.parse_args(argv) ret = parser.parse_args(argv)
if ret.keysanity: if ret.keysanity:
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = 'wild' * 4 ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = ['wild'] * 4
if ret.keydropshuffle: if ret.keydropshuffle:
ret.dropshuffle = 'keys' if ret.dropshuffle == 'none' else ret.dropshuffle ret.dropshuffle = 'keys' if ret.dropshuffle == 'none' else ret.dropshuffle
@@ -133,8 +133,8 @@ def parse_cli(argv, no_defaults=False):
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle', for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle',
'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle', 'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle_followers', 'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle_followers',
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'ganon_item', 'openpyramid', 'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'bosses_ganon', 'bosshunt_include_agas', 'ganon_item', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'startinventory', 'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'showloot', 'showmap', 'startinventory',
'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items', 'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
'triforce_max_difference', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_max_difference', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern', 'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern',
@@ -177,6 +177,8 @@ def parse_settings():
"goal": "ganon", "goal": "ganon",
"crystals_gt": "7", "crystals_gt": "7",
"crystals_ganon": "7", "crystals_ganon": "7",
"bosses_ganon": "8",
"bosshunt_include_agas": False,
"ganon_item": "silver", "ganon_item": "silver",
"swords": "random", "swords": "random",
"flute_mode": "normal", "flute_mode": "normal",
@@ -234,6 +236,8 @@ def parse_settings():
"keyshuffle": "none", "keyshuffle": "none",
"bigkeyshuffle": "none", "bigkeyshuffle": "none",
"prizeshuffle": "none", "prizeshuffle": "none",
"showloot": "never",
"showmap": "map",
"keysanity": False, "keysanity": False,
"door_shuffle": "vanilla", "door_shuffle": "vanilla",
"intensity": 3, "intensity": 3,

View File

@@ -2340,7 +2340,7 @@ def parallel_full_neutralization(dungeon_map, polarized_sectors, global_pole):
increment_depth = True increment_depth = True
current_depth = last_depth + 1 if increment_depth else last_depth current_depth = last_depth + 1 if increment_depth else last_depth
finished = all([(x.polarity()+sum_polarity(solution_list[x])).is_neutral() for x in builders]) finished = all([(x.polarity()+sum_polarity(solution_list[x])).is_neutral() for x in builders])
logging.getLogger('').info(f'-Balanced solution found in {time.process_time()-start}') logging.getLogger('').debug(f'-Balanced solution found in {time.process_time()-start}')
for builder, sectors in solution_list.items(): for builder, sectors in solution_list.items():
for sector in sectors: for sector in sectors:
assign_sector(sector, builder, polarized_sectors, global_pole) assign_sector(sector, builder, polarized_sectors, global_pole)

View File

@@ -11,7 +11,7 @@ import RaceRandom as random
import source.classes.diags as diagnostics import source.classes.diags as diagnostics
from CLI import get_args_priority, parse_cli from CLI import get_args_priority, parse_cli
from Fill import FillError from Fill import FillError
from Main import EnemizerError, __version__, main from Main import EnemizerError, main
from Rom import get_sprite_from_name from Rom import get_sprite_from_name
from source.classes.BabelFish import BabelFish from source.classes.BabelFish import BabelFish
from Utils import close_console, is_bundled from Utils import close_console, is_bundled
@@ -80,7 +80,7 @@ def start():
break break
except (FillError, EnemizerError, Exception, RuntimeError) as err: except (FillError, EnemizerError, Exception, RuntimeError) as err:
failures.append((err, seed)) failures.append((err, seed))
logger.warning('%s: %s', fish.translate("cli","cli","generation.failed"), err) logger.exception('Attempt %d - %s: %s', trynum, fish.translate("cli","cli","generation.failed"), err)
logger.info('') logger.info('')
seed = random.randint(0, 999999999) seed = random.randint(0, 999999999)

View File

@@ -13,7 +13,19 @@ def create_dungeons(world, player):
dungeon.world = world dungeon.world = world
return dungeon return dungeon
ES = make_dungeon('Hyrule Castle', 1, None, hyrule_castle_regions, None, [ItemFactory('Small Key (Escape)', player)], [ItemFactory('Map (Escape)', player)]) hc_dungeon_items = ['Map (Escape)']
at_dungeon_items = []
if world.showloot[player] == 'compass':
if world.dropshuffle[player] == 'underworld' or world.pottery[player] in ['dungeon', 'reduced', 'clustered', 'nonempty', 'lottery']:
hc_dungeon_items.append('Compass (Escape)')
at_dungeon_items.append('Compass (Agahnims Tower)')
elif world.compassshuffle[player] == 'wild':
hc_dungeon_items.append('Compass (Escape)')
if world.keyshuffle[player] == 'wild':
at_dungeon_items.append('Compass (Agahnims Tower)')
ES = make_dungeon('Hyrule Castle', 1, None, hyrule_castle_regions, None, [ItemFactory('Small Key (Escape)', player)], ItemFactory(hc_dungeon_items, player))
EP = make_dungeon('Eastern Palace', 2, 'Armos Knights', eastern_regions, ItemFactory('Big Key (Eastern Palace)', player), [], ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player)) EP = make_dungeon('Eastern Palace', 2, 'Armos Knights', eastern_regions, ItemFactory('Big Key (Eastern Palace)', player), [], ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player))
DP = make_dungeon('Desert Palace', 3, 'Lanmolas', desert_regions, ItemFactory('Big Key (Desert Palace)', player), [ItemFactory('Small Key (Desert Palace)', player)], ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player)) DP = make_dungeon('Desert Palace', 3, 'Lanmolas', desert_regions, ItemFactory('Big Key (Desert Palace)', player), [ItemFactory('Small Key (Desert Palace)', player)], ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player))
ToH = make_dungeon('Tower of Hera', 10, 'Moldorm', hera_regions, ItemFactory('Big Key (Tower of Hera)', player), [ItemFactory('Small Key (Tower of Hera)', player)], ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], player)) ToH = make_dungeon('Tower of Hera', 10, 'Moldorm', hera_regions, ItemFactory('Big Key (Tower of Hera)', player), [ItemFactory('Small Key (Tower of Hera)', player)], ItemFactory(['Map (Tower of Hera)', 'Compass (Tower of Hera)'], player))
@@ -24,7 +36,7 @@ def create_dungeons(world, player):
IP = make_dungeon('Ice Palace', 9, 'Kholdstare', ice_regions, ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player)) IP = make_dungeon('Ice Palace', 9, 'Kholdstare', ice_regions, ItemFactory('Big Key (Ice Palace)', player), ItemFactory(['Small Key (Ice Palace)'] * 2, player), ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player))
MM = make_dungeon('Misery Mire', 7, 'Vitreous', mire_regions, ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player)) MM = make_dungeon('Misery Mire', 7, 'Vitreous', mire_regions, ItemFactory('Big Key (Misery Mire)', player), ItemFactory(['Small Key (Misery Mire)'] * 3, player), ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
TR = make_dungeon('Turtle Rock', 12, 'Trinexx', tr_regions, ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player)) TR = make_dungeon('Turtle Rock', 12, 'Trinexx', tr_regions, ItemFactory('Big Key (Turtle Rock)', player), ItemFactory(['Small Key (Turtle Rock)'] * 4, player), ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))
AT = make_dungeon('Agahnims Tower', 4, 'Agahnim', tower_regions, None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), []) AT = make_dungeon('Agahnims Tower', 4, 'Agahnim', tower_regions, None, ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), ItemFactory(at_dungeon_items, player))
GT = make_dungeon('Ganons Tower', 13, 'Agahnim2', gt_regions, ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player)) GT = make_dungeon('Ganons Tower', 13, 'Agahnim2', gt_regions, ItemFactory('Big Key (Ganons Tower)', player), ItemFactory(['Small Key (Ganons Tower)'] * 4, player), ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
GT.bosses['bottom'] = BossFactory('Armos Knights', player) GT.bosses['bottom'] = BossFactory('Armos Knights', player)

View File

@@ -799,7 +799,7 @@ def sell_potions(world, player):
if shop.region.name in shop_to_location_table and shop.region.name != 'Capacity Upgrade': if shop.region.name in shop_to_location_table and shop.region.name != 'Capacity Upgrade':
loc_choices += [world.get_location(loc, player) for loc in shop_to_location_table[shop.region.name]] loc_choices += [world.get_location(loc, player) for loc in shop_to_location_table[shop.region.name]]
locations = [loc for loc in loc_choices if not loc.item] locations = [loc for loc in loc_choices if not loc.item]
for potion in ['Green Potion', 'Blue Potion', 'Red Potion']: for potion in ['Green Potion', 'Blue Potion', 'Red Potion', 'Bee']:
location = random.choice(filter_locations(ItemFactory(potion, player), locations, world, potion=True)) location = random.choice(filter_locations(ItemFactory(potion, player), locations, world, potion=True))
locations.remove(location) locations.remove(location)
p_item = next((item for item in world.itempool if item.name == potion and item.player == player), None) p_item = next((item for item in world.itempool if item.name == potion and item.player == player), None)

7
Gui.py
View File

@@ -22,8 +22,6 @@ from tkinter import (
from CLI import get_args_priority from CLI import get_args_priority
from DungeonRandomizer import parse_cli from DungeonRandomizer import parse_cli
from GuiUtils import set_icon from GuiUtils import set_icon
from Main import __version__ as ESVersion
from OverworldShuffle import __version__ as ORVersion
from source.classes.BabelFish import BabelFish from source.classes.BabelFish import BabelFish
from source.classes.Empty import Empty from source.classes.Empty import Empty
from source.gui.adjust.overview import adjust_page from source.gui.adjust.overview import adjust_page
@@ -40,13 +38,14 @@ from source.gui.randomize.generation import generation_page
from source.gui.randomize.item import item_page from source.gui.randomize.item import item_page
from source.gui.randomize.overworld import overworld_page from source.gui.randomize.overworld import overworld_page
from source.gui.startinventory.overview import startinventory_page from source.gui.startinventory.overview import startinventory_page
from Versions import DRVersion, GKVersion, ORVersion
def check_python_version(fish): def check_python_version(fish):
import sys import sys
version = sys.version_info version = sys.version_info
if version.major < 3 or version.minor < 7: if version.major < 3 or version.minor < 7:
messagebox.showinfo("Overworld Shuffle %s (DR %s)" % (ORVersion, ESVersion), fish.translate("cli","cli","old.python.version") % sys.version) messagebox.showinfo("GwaaKiwi Randomizer %s (OR %s, DR %s)" % (GKVersion, ORVersion, DRVersion), fish.translate("cli","cli","old.python.version") % sys.version)
# Save settings to file # Save settings to file
@@ -95,7 +94,7 @@ def guiMain(args=None):
mainWindow = Tk() mainWindow = Tk()
self = mainWindow self = mainWindow
mainWindow.wm_title("Overworld Shuffle %s (DR %s)" % (ORVersion, ESVersion)) mainWindow.wm_title("GwaaKiwi Randomizer %s (OR %s, DR %s)" % (GKVersion, ORVersion, DRVersion))
mainWindow.protocol("WM_DELETE_WINDOW", guiExit) # intercept when user clicks the X mainWindow.protocol("WM_DELETE_WINDOW", guiExit) # intercept when user clicks the X
# set program icon # set program icon

View File

@@ -236,8 +236,10 @@ def get_custom_array_key(item):
def generate_itempool(world, player): def generate_itempool(world, player):
if (world.difficulty[player] not in ['normal', 'hard', 'expert'] if (world.difficulty[player] not in ['normal', 'hard', 'expert']
or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals', or world.goal[player] not in ['ganon', 'pedestal', 'dungeons',
'ganonhunt', 'completionist', 'sanctuary'] 'triforcehunt', 'trinity', 'crystals',
'ganonhunt', 'completionist', 'sanctuary',
'bosshunt']
or world.mode[player] not in ['open', 'standard', 'inverted'] or world.mode[player] not in ['open', 'standard', 'inverted']
or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'] or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']
or world.progressive not in ['on', 'off', 'random']): or world.progressive not in ['on', 'off', 'random']):
@@ -379,7 +381,7 @@ def generate_itempool(world, player):
items = ItemFactory(pool, player) items = ItemFactory(pool, player)
if world.shopsanity[player]: if world.shopsanity[player]:
for potion in ['Green Potion', 'Blue Potion', 'Red Potion']: for potion in ['Green Potion', 'Blue Potion', 'Red Potion', 'Bee']:
p_item = next(item for item in items if item.name == potion and item.player == player) p_item = next(item for item in items if item.name == potion and item.player == player)
p_item.priority = True # don't beemize one of each potion p_item.priority = True # don't beemize one of each potion
@@ -1520,7 +1522,7 @@ def make_customizer_pool(world, player):
guaranteed_items.append('Ocarina (Activated)') guaranteed_items.append('Ocarina (Activated)')
missing_items = [] missing_items = []
if world.shopsanity[player]: if world.shopsanity[player]:
guaranteed_items.extend(['Blue Potion', 'Green Potion', 'Red Potion']) guaranteed_items.extend(['Blue Potion', 'Green Potion', 'Red Potion', 'Bee'])
if world.keyshuffle[player] == 'universal': if world.keyshuffle[player] == 'universal':
guaranteed_items.append('Small Key (Universal)') guaranteed_items.append('Small Key (Universal)')
for item in guaranteed_items: for item in guaranteed_items:

36
Main.py
View File

@@ -74,6 +74,7 @@ from Rom import (
) )
from RoomData import create_rooms from RoomData import create_rooms
from Rules import set_rules from Rules import set_rules
from source.classes.BabelFish import BabelFish
from source.classes.CustomSettings import CustomSettings from source.classes.CustomSettings import CustomSettings
from source.enemizer.DamageTables import DamageTable from source.enemizer.DamageTables import DamageTable
from source.enemizer.Enemizer import randomize_enemies from source.enemizer.Enemizer import randomize_enemies
@@ -92,12 +93,7 @@ from UnderworldGlitchRules import (
create_hmg_entrances_regions, create_hmg_entrances_regions,
) )
from Utils import output_path, parse_player_names from Utils import output_path, parse_player_names
from Versions import DRVersion, GKVersion, ORVersion
version_number = '1.5.0'
version_branch = '-u'
__version__ = f'{version_number}{version_branch}'
from source.classes.BabelFish import BabelFish
class EnemizerError(RuntimeError): class EnemizerError(RuntimeError):
@@ -175,15 +171,13 @@ def main(args, seed=None, fish=None):
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)} world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
world.finish_init() world.finish_init()
from OverworldShuffle import __version__ as ORVersion
logger.info( logger.info(
world.fish.translate("cli","cli","app.title") + "\n", world.fish.translate("cli","cli","app.title") + "\n",
ORVersion, GKVersion,
"%s (%s)" % (world.seed, str(args.outputname)) if str(args.outputname).startswith('M') else world.seed, "%s (%s)" % (world.seed, str(args.outputname)) if str(args.outputname).startswith('M') else world.seed,
Settings.make_code(world, 1) if world.players == 1 else ''
) )
for k,v in {"DR":__version__,"OR":ORVersion}.items(): for k,v in {"GK": GKVersion, "OR": ORVersion, "DR": DRVersion}.items():
logger.info((k + ' Version:').ljust(16) + '%s' % v) logger.info((k + ' Version:').ljust(16) + '%s' % v)
parsed_names = parse_player_names(args.names, world.players, args.teams) parsed_names = parse_player_names(args.names, world.players, args.teams)
@@ -195,7 +189,7 @@ def main(args, seed=None, fish=None):
world.player_names[player].append(name) world.player_names[player].append(name)
logger.info('') logger.info('')
outfilebase = f'OR_{args.outputname if args.outputname else world.seed}' outfilebase = f'GK_{args.outputname if args.outputname else world.seed}'
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
world.difficulty_requirements[player] = difficulties[world.difficulty[player]] world.difficulty_requirements[player] = difficulties[world.difficulty[player]]
@@ -474,15 +468,13 @@ def export_yaml(args, fish):
if args.seed and int(args.seed) > 0: if args.seed and int(args.seed) > 0:
world.seed = int(args.seed) world.seed = int(args.seed)
from OverworldShuffle import __version__ as ORVersion
logger.info( logger.info(
world.fish.translate("cli","cli","app.title") + "\n", world.fish.translate("cli","cli","app.title") + "\n",
ORVersion, GKVersion,
"(%s)" % outfilebase, "(%s)" % outfilebase,
Settings.make_code(world, 1) if world.players == 1 else ''
) )
for k,v in {"DR":__version__,"OR":ORVersion}.items(): for k,v in {"GK": GKVersion, "OR": ORVersion, "DR": DRVersion}.items():
logger.info((k + ' Version:').ljust(16) + '%s' % v) logger.info((k + ' Version:').ljust(16) + '%s' % v)
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
@@ -523,12 +515,16 @@ def init_world(args, fish):
world.keyshuffle = args.keyshuffle.copy() world.keyshuffle = args.keyshuffle.copy()
world.bigkeyshuffle = args.bigkeyshuffle.copy() world.bigkeyshuffle = args.bigkeyshuffle.copy()
world.prizeshuffle = args.prizeshuffle.copy() world.prizeshuffle = args.prizeshuffle.copy()
world.showloot = args.showloot.copy()
world.showmap = args.showmap.copy()
world.bombbag = args.bombbag.copy() world.bombbag = args.bombbag.copy()
world.flute_mode = args.flute_mode.copy() world.flute_mode = args.flute_mode.copy()
world.bow_mode = args.bow_mode.copy() world.bow_mode = args.bow_mode.copy()
world.crystals_ganon_orig = args.crystals_ganon.copy() world.crystals_ganon_orig = args.crystals_ganon.copy()
world.crystals_gt_orig = args.crystals_gt.copy() world.crystals_gt_orig = args.crystals_gt.copy()
world.ganon_item_orig = args.ganon_item.copy() world.ganon_item_orig = args.ganon_item.copy()
world.bosses_ganon = {player: int(args.bosses_ganon[player]) for player in range(1, world.players + 1)}
world.bosshunt_include_agas = args.bosshunt_include_agas.copy()
world.owTerrain = args.ow_terrain.copy() world.owTerrain = args.ow_terrain.copy()
world.owKeepSimilar = args.ow_keepsimilar.copy() world.owKeepSimilar = args.ow_keepsimilar.copy()
world.owWhirlpoolShuffle = args.ow_whirlpool.copy() world.owWhirlpoolShuffle = args.ow_whirlpool.copy()
@@ -576,7 +572,7 @@ def init_world(args, fish):
world.money_balance = args.money_balance.copy() world.money_balance = args.money_balance.copy()
# custom settings - these haven't been promoted to full settings yet # custom settings - these haven't been promoted to full settings yet
in_progress_settings = ['force_enemy', 'free_lamp_cone'] in_progress_settings = ['force_enemy']
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
for setting in in_progress_settings: for setting in in_progress_settings:
if world.customizer and world.customizer.has_setting(player, setting): if world.customizer and world.customizer.has_setting(player, setting):
@@ -838,12 +834,16 @@ def copy_world(world):
ret.keyshuffle = world.keyshuffle.copy() ret.keyshuffle = world.keyshuffle.copy()
ret.bigkeyshuffle = world.bigkeyshuffle.copy() ret.bigkeyshuffle = world.bigkeyshuffle.copy()
ret.prizeshuffle = world.prizeshuffle.copy() ret.prizeshuffle = world.prizeshuffle.copy()
ret.showloot = world.showloot.copy()
ret.showmap = world.showmap.copy()
ret.bombbag = world.bombbag.copy() ret.bombbag = world.bombbag.copy()
ret.flute_mode = world.flute_mode.copy() ret.flute_mode = world.flute_mode.copy()
ret.bow_mode = world.bow_mode.copy() ret.bow_mode = world.bow_mode.copy()
ret.free_lamp_cone = world.free_lamp_cone.copy() ret.free_lamp_cone = world.free_lamp_cone.copy()
ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy()
ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy()
ret.bosses_ganon = world.bosses_ganon.copy()
ret.bosshunt_include_agas = world.bosshunt_include_agas.copy()
ret.ganon_item = world.ganon_item.copy() ret.ganon_item = world.ganon_item.copy()
ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() ret.crystals_ganon_orig = world.crystals_ganon_orig.copy()
ret.crystals_gt_orig = world.crystals_gt_orig.copy() ret.crystals_gt_orig = world.crystals_gt_orig.copy()
@@ -1065,12 +1065,16 @@ def copy_world_premature(world, player, create_flute_exits=True):
ret.keyshuffle = world.keyshuffle.copy() ret.keyshuffle = world.keyshuffle.copy()
ret.bigkeyshuffle = world.bigkeyshuffle.copy() ret.bigkeyshuffle = world.bigkeyshuffle.copy()
ret.prizeshuffle = world.prizeshuffle.copy() ret.prizeshuffle = world.prizeshuffle.copy()
ret.showloot = world.showloot.copy()
ret.showmap = world.showmap.copy()
ret.bombbag = world.bombbag.copy() ret.bombbag = world.bombbag.copy()
ret.flute_mode = world.flute_mode.copy() ret.flute_mode = world.flute_mode.copy()
ret.bow_mode = world.bow_mode.copy() ret.bow_mode = world.bow_mode.copy()
ret.free_lamp_cone = world.free_lamp_cone.copy() ret.free_lamp_cone = world.free_lamp_cone.copy()
ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy() ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy()
ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy() ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy()
ret.bosses_ganon = world.bosses_ganon.copy()
ret.bosshunt_include_agas = world.bosshunt_include_agas.copy()
ret.ganon_item = world.ganon_item.copy() ret.ganon_item = world.ganon_item.copy()
ret.crystals_ganon_orig = world.crystals_ganon_orig.copy() ret.crystals_ganon_orig = world.crystals_ganon_orig.copy()
ret.crystals_gt_orig = world.crystals_gt_orig.copy() ret.crystals_gt_orig = world.crystals_gt_orig.copy()

View File

@@ -27,12 +27,6 @@ from Regions import mark_light_dark_world_regions
from source.overworld.EntranceShuffle2 import connect_simple from source.overworld.EntranceShuffle2 import connect_simple
from Utils import bidict from Utils import bidict
version_number = '0.6.1.7'
# branch indicator is intentionally different across branches
version_branch = ''
__version__ = '%s%s' % (version_number, version_branch)
parallel_links_new = None # needs to be globally available, reset every new generation/player parallel_links_new = None # needs to be globally available, reset every new generation/player
def link_overworld(world, player): def link_overworld(world, player):

View File

@@ -29,7 +29,7 @@ from source.overworld.EntranceShuffle2 import (
link_entrances_new, link_entrances_new,
) )
__version__ = '0.2-dev' PlandoVersion = '0.2-dev'
def main(args): def main(args):
start_time = time.process_time() start_time = time.process_time()
@@ -47,7 +47,7 @@ def main(args):
random.seed(world.seed) random.seed(world.seed)
logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n', __version__, args.plando) logger.info('ALttP Plandomizer Version %s - Seed: %s\n\n', PlandoVersion, args.plando)
world.difficulty_requirements[1] = difficulties[world.difficulty[1]] world.difficulty_requirements[1] = difficulties[world.difficulty[1]]

131
Rom.py
View File

@@ -81,9 +81,10 @@ from Text import (
text_addresses, text_addresses,
) )
from Utils import int16_as_bytes, int32_as_bytes, local_path, snes_to_pc from Utils import int16_as_bytes, int32_as_bytes, local_path, snes_to_pc
from Versions import DRVersion, GKVersion, ORVersion
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '76dc2d00e5dd5b925ad01574b327d364' RANDOMIZERBASEHASH = '2647cc28bca3675152576dd1f5ea0bab'
class JsonRom(object): class JsonRom(object):
@@ -201,6 +202,7 @@ class LocalRom(object):
with open(local_path('data/base2current.bps'), 'rb') as stream: with open(local_path('data/base2current.bps'), 'rb') as stream:
bps.apply.apply_to_bytearrays(bps.io.read_bps(stream), orig_buffer, self.buffer) bps.apply.apply_to_bytearrays(bps.io.read_bps(stream), orig_buffer, self.buffer)
if not os.getenv("SKIP_BASEROM_CHECK", False):
# verify md5 # verify md5
patchedmd5 = hashlib.md5() patchedmd5 = hashlib.md5()
patchedmd5.update(self.buffer) patchedmd5.update(self.buffer)
@@ -1225,7 +1227,7 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28]) rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28])
if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']: if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']:
rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player])) rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player]))
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled) rom.write_byte(0x180194, 1) # Must turn in triforce pieces (instant win not enabled)
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
@@ -1254,7 +1256,6 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
rom.write_bytes(0x18016E, [0x04, 0x08, 0x10]) # Set spike cave and MM spike room Cape usage rom.write_bytes(0x18016E, [0x04, 0x08, 0x10]) # Set spike cave and MM spike room Cape usage
rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest
rom.write_byte(0x50599, 0x00) # disable below ganon chest rom.write_byte(0x50599, 0x00) # disable below ganon chest
rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest
if world.is_pyramid_open(player): if world.is_pyramid_open(player):
rom.initial_sram.pre_open_pyramid_hole() rom.initial_sram.pre_open_pyramid_hole()
rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped
@@ -1300,6 +1301,8 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
# 08: Goal items collected (ie. Triforce Pieces) # 08: Goal items collected (ie. Triforce Pieces)
# 09: Max collection rate # 09: Max collection rate
# 0A: Custom goal # 0A: Custom goal
# 0B: Reserved for Bingo
# 0C: All bosses (prize bosses + aga1 + aga2)
def get_goal_bytes(type): def get_goal_bytes(type):
goal_bytes = [] goal_bytes = []
@@ -1354,6 +1357,11 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
ganon_goal += [0x02, world.crystals_needed_for_ganon[player]] ganon_goal += [0x02, world.crystals_needed_for_ganon[player]]
elif world.goal[player] in ['ganonhunt']: elif world.goal[player] in ['ganonhunt']:
ganon_goal += [0x88] # triforce pieces ganon_goal += [0x88] # triforce pieces
elif world.goal[player] in ['bosshunt']:
if world.bosshunt_include_agas[player]:
ganon_goal += [0x0C, world.bosses_ganon[player]] # total bosses
else:
ganon_goal += [0x05, world.bosses_ganon[player]] # prize bosses
elif world.goal[player] in ['completionist']: elif world.goal[player] in ['completionist']:
ganon_goal += [0x81, 0x82, 0x06, 0x07, 0x89] # AD and max collection rate ganon_goal += [0x81, 0x82, 0x06, 0x07, 0x89] # AD and max collection rate
else: else:
@@ -1455,6 +1463,60 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
or world.dropshuffle[player] != 'none' or world.pottery[player] not in ['none', 'cave']): or world.dropshuffle[player] != 'none' or world.pottery[player] not in ['none', 'cave']):
rom.write_byte(0x18003A, 0x01) # show key counts on map pickup rom.write_byte(0x18003A, 0x01) # show key counts on map pickup
loot_source = 0x09
if world.prizeshuffle[player] != 'none':
loot_source |= 0x10
if world.pottery[player] not in ['none', 'cave']:
loot_source |= 0x02
if world.dropshuffle[player] != 'none':
loot_source |= 0x04
rom.write_byte(0x1CFF10, loot_source)
if world.showloot[player] == 'never':
rom.write_bytes(0x1CFF08, [0x00, 0x00, 0x00, 0x00])
rom.write_byte(0x1CFF11, 0x00)
elif world.showloot[player] == 'presence':
rom.write_bytes(0x1CFF08, [0x01, 0x00, 0x00, 0x00])
rom.write_byte(0x1CFF11, 0x00)
elif world.showloot[player] == 'compass':
rom.write_bytes(0x1CFF08, [0x01, 0x00, 0x02, 0x00])
rom.write_byte(0x1CFF11, 0x01)
elif world.showloot[player] == 'always':
rom.write_bytes(0x1CFF08, [0x02, 0x00, 0x00, 0x00])
rom.write_byte(0x1CFF11, 0x00)
if world.showmap[player] == 'visited':
rom.write_bytes(0x1CFF00, [0x01, 0x00, 0x00, 0x05])
elif world.showmap[player] == 'map':
rom.write_bytes(0x1CFF00, [0x01, 0x05, 0x00, 0x05])
elif world.showmap[player] == 'always':
rom.write_bytes(0x1CFF00, [0x05, 0x00, 0x00, 0x00])
loot_icons = 0x1CF900
if world.bombbag[player]:
rom.write_byte(loot_icons + 0x52, 0x0B) # bomb bag is major
triforce_piece_ids = [0x6B, 0x6C]
if world.treasure_hunt_count[player] > 20:
for triforce_piece_id in triforce_piece_ids:
rom.write_byte(loot_icons + triforce_piece_id, 0x04)
crystal_ids = [0x20, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6]
if world.goal[player] in ['ganon', 'dungeons', 'crystals', 'trinity']:
crystal_category = 0x0D
else:
crystal_category = 0x06
for crystal_id in crystal_ids:
rom.write_byte(loot_icons + crystal_id, crystal_category)
pendant_ids = [0x37, 0x38, 0x39]
if world.goal[player] in ['pedestal', 'dungeons', 'trinity']:
pendant_category = 0x0C
else:
pendant_category = 0x06
for pendant_id in pendant_ids:
rom.write_byte(loot_icons + pendant_id, pendant_category)
# compasses showing dungeon count # compasses showing dungeon count
compass_mode = 0x80 if world.compassshuffle[player] not in ['none', 'nearby'] else 0x00 compass_mode = 0x80 if world.compassshuffle[player] not in ['none', 'nearby'] else 0x00
if world.clock_mode != 'none' or world.dungeon_counters[player] == 'off': if world.clock_mode != 'none' or world.dungeon_counters[player] == 'off':
@@ -1601,12 +1663,28 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
# b - Big Key # b - Big Key
# a - Small Key # a - Small Key
# #
dungeon_items_menu = 0x00
if world.doorShuffle[player] not in ['vanilla', 'basic']:
dungeon_items_menu |= 0x0F
if world.keyshuffle[player] not in ['none', 'universal']:
dungeon_items_menu |= 0x01
if world.bigkeyshuffle[player] != 'none':
dungeon_items_menu |= 0x02
enable_menu_map_check = (world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla') or world.prizeshuffle[player] not in ['none', 'dungeon', 'nearby'] enable_menu_map_check = (world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla') or world.prizeshuffle[player] not in ['none', 'dungeon', 'nearby']
rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] not in ['none', 'universal'] else 0x00) if world.mapshuffle[player] != 'none' or enable_menu_map_check:
| (0x02 if world.bigkeyshuffle[player] != 'none' else 0x00) dungeon_items_menu |= 0x04
| (0x04 if world.mapshuffle[player] != 'none' or enable_menu_map_check else 0x00)
| (0x08 if world.compassshuffle[player] != 'none' else 0x00) # free roaming items in menu if world.compassshuffle[player] != 'none':
| (0x10 if world.logic[player] == 'nologic' else 0))) # boss icon dungeon_items_menu |= 0x08
if world.logic[player] == 'nologic' or world.goal[player] == 'bosshunt':
dungeon_items_menu |= 0x10
rom.write_byte(0x180045, dungeon_items_menu)
def get_reveal_bytes(itemName): def get_reveal_bytes(itemName):
if world.prizeshuffle[player] != 'wild': if world.prizeshuffle[player] != 'wild':
@@ -1815,30 +1893,29 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
# set rom name # set rom name
# 21 bytes # 21 bytes
from Main import __version__
from OverworldShuffle import __version__ as ORVersion
if rom_header: if rom_header:
if len(rom_header) > 21: if len(rom_header) > 21:
raise Exception('ROM header too long. Max 21 bytes, found %d bytes.' % len(rom_header)) raise Exception('ROM header too long. Max 21 bytes, found %d bytes.' % len(rom_header))
elif '|' in rom_header:
gen, seedstring = rom_header.split('|', 1)
gen = f'{gen:<3}'
seedstring = f'{int(seedstring):09}' if seedstring.isdigit() else seedstring[:9]
rom.name = bytearray(f'OR{gen}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21]
elif len(rom_header) <= 9:
seedstring = f'{int(rom_header):09}' if rom_header.isdigit() else rom_header
rom.name = bytearray(f'OR{__version__.split("-")[0].replace(".","")[0:3]}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21]
else:
rom.name = bytearray(rom_header, 'utf8')[:21]
else:
seedstring = f'{world.seed:09}' if isinstance(world.seed, int) else world.seed
rom.name = bytearray(f'OR{__version__.split("-")[0].replace(".","")[0:3]}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21]
if world.players > 1 and len(rom_header) <= 12:
rom.name = bytearray(f"GK_{team + 1}_{player}_{rom_header}", 'utf8')
elif len(rom_header) <= 18:
rom.name = bytearray(f"GK_{rom_header}", 'utf8')
else:
rom.name = bytearray(rom_header, 'utf8')
else:
if world.players > 1:
rom.name = bytearray(f'GK_{team + 1}_{player}_{world.seed}', 'utf8')
else:
rom.name = bytearray(f'GK_{world.seed}', 'utf8')
rom.name = rom.name[:21]
rom.name.extend([0] * (21 - len(rom.name))) rom.name.extend([0] * (21 - len(rom.name)))
rom.write_bytes(0x7FC0, rom.name) rom.write_bytes(0x7FC0, rom.name)
rom.write_bytes(0x138010, bytearray(__version__, 'utf8')) rom.write_bytes(0x138010, bytearray(DRVersion, 'utf8'))
rom.write_bytes(0x150010, bytearray(ORVersion, 'utf8')) rom.write_bytes(0x150010, bytearray(ORVersion, 'utf8'))
rom.write_bytes(0x1CEEF0, bytearray(GKVersion, 'utf8'))
# set player names # set player names
for p in range(1, min(world.players, 255) + 1): for p in range(1, min(world.players, 255) + 1):
@@ -2712,6 +2789,12 @@ def write_strings(rom, world, player, team):
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! You can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player]) tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! You can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player])
elif world.goal[player] == 'ganonhunt': elif world.goal[player] == 'ganonhunt':
tt['sign_ganon'] = 'Go find the Triforce pieces to beat Ganon' tt['sign_ganon'] = 'Go find the Triforce pieces to beat Ganon'
elif world.goal[player] == 'bosshunt':
bosshunt_count = '%d guardian%s of %sdungeons' % \
(world.bosses_ganon[player],
'' if world.bosses_ganon[player] == 1 else 's',
'' if world.bosshunt_include_agas[player] else 'prize ')
tt['sign_ganon'] = 'To beat Ganon you must defeat %s.' % bosshunt_count
elif world.goal[player] == 'completionist': elif world.goal[player] == 'completionist':
tt['sign_ganon'] = 'Ganon only respects those who have done everything' tt['sign_ganon'] = 'Ganon only respects those who have done everything'
tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)] tt['ganon_fall_in'] = Ganon1_texts[random.randint(0, len(Ganon1_texts) - 1)]

View File

@@ -90,6 +90,15 @@ def set_rules(world, player):
add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player)) add_rule(world.get_location('Ganon', player), lambda state: state.has_crystals(world.crystals_needed_for_ganon[player], player))
elif world.goal[player] == 'ganonhunt': elif world.goal[player] == 'ganonhunt':
add_rule(world.get_location('Ganon', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player])) add_rule(world.get_location('Ganon', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
elif world.goal[player] == 'bosshunt':
if world.bosshunt_include_agas[player]:
add_rule(world.get_location('Ganon', player), lambda state:
state.item_count('Beat Agahnim 1', player) +
state.item_count('Beat Agahnim 2', player) +
state.item_count('Beat Boss', player) >= world.bosses_ganon[player])
else:
add_rule(world.get_location('Ganon', player), lambda state:
state.item_count('Beat Boss', player) >= world.bosses_ganon[player])
elif world.goal[player] == 'completionist': elif world.goal[player] == 'completionist':
add_rule(world.get_location('Ganon', player), lambda state: state.everything(player)) add_rule(world.get_location('Ganon', player), lambda state: state.everything(player))

View File

@@ -1781,7 +1781,7 @@ class TextTable(object):
text['mastersword_pedestal_translated'] = CompressedTextMapper.convert("A test of strength: If you have 3 pendants, I'm yours.") text['mastersword_pedestal_translated'] = CompressedTextMapper.convert("A test of strength: If you have 3 pendants, I'm yours.")
text['telepathic_tile_spectacle_rock'] = CompressedTextMapper.convert("{NOBORDER}\n{NOBORDER}\nUse the Mirror, or the Hookshot and Hammer, to get to Tower of Hera!") text['telepathic_tile_spectacle_rock'] = CompressedTextMapper.convert("{NOBORDER}\n{NOBORDER}\nUse the Mirror, or the Hookshot and Hammer, to get to Tower of Hera!")
text['telepathic_tile_swamp_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nDrain the floodgate to raise the water here!") text['telepathic_tile_swamp_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nDrain the floodgate to raise the water here!")
text['telepathic_tile_thieves_town_upstairs'] = CompressedTextMapper.convert("{NOBORDER}\nBlind hate's bright light.") text['telepathic_tile_thieves_town_upstairs'] = CompressedTextMapper.convert("{NOBORDER}\nBlind hates bright light.")
text['telepathic_tile_misery_mire'] = CompressedTextMapper.convert("{NOBORDER}\nLighting 4 torches will open your way forward!") text['telepathic_tile_misery_mire'] = CompressedTextMapper.convert("{NOBORDER}\nLighting 4 torches will open your way forward!")
text['hylian_text_2'] = CompressedTextMapper.convert("%%^= %==%\n ^ =%^=\n==%= ^^%^") text['hylian_text_2'] = CompressedTextMapper.convert("%%^= %==%\n ^ =%^=\n==%= ^^%^")
text['desert_entry_translated'] = CompressedTextMapper.convert("Kneel before this stone, and magic will move around you.") text['desert_entry_translated'] = CompressedTextMapper.convert("Kneel before this stone, and magic will move around you.")
@@ -2015,7 +2015,7 @@ class TextTable(object):
text['thief_desert_rupee_cave'] = CompressedTextMapper.convert("So you, like, busted down my door, and are being a jerk by talking to me? Normally I would be angry and make you pay for it, but I bet you're just going to break all my pots and steal my 50 rupees.") text['thief_desert_rupee_cave'] = CompressedTextMapper.convert("So you, like, busted down my door, and are being a jerk by talking to me? Normally I would be angry and make you pay for it, but I bet you're just going to break all my pots and steal my 50 rupees.")
text['thief_ice_rupee_cave'] = CompressedTextMapper.convert("I'm a rupee pot farmer. One day I will take over the world with my skillz. Have you met my brother in the desert? He's way richer than I am.") text['thief_ice_rupee_cave'] = CompressedTextMapper.convert("I'm a rupee pot farmer. One day I will take over the world with my skillz. Have you met my brother in the desert? He's way richer than I am.")
text['telepathic_tile_south_east_darkworld_cave'] = CompressedTextMapper.convert("~~ dev cave ~~\n no farming\n required") text['telepathic_tile_south_east_darkworld_cave'] = CompressedTextMapper.convert("~~ dev cave ~~\n no farming\n required")
text['cukeman'] = CompressedTextMapper.convert("Did you hear that Veetorp beat ajneb174 in a 1 on 1 race at AGDQ?") text['cukeman'] = CompressedTextMapper.convert("Trans rights!")
text['cukeman_2'] = CompressedTextMapper.convert("You found Shabadoo, huh?\nNiiiiice.") text['cukeman_2'] = CompressedTextMapper.convert("You found Shabadoo, huh?\nNiiiiice.")
text['potion_shop_no_cash'] = CompressedTextMapper.convert("Yo! I'm not running a charity here.") text['potion_shop_no_cash'] = CompressedTextMapper.convert("Yo! I'm not running a charity here.")
text['kakariko_powdered_chicken'] = CompressedTextMapper.convert("Smallhacker…\n\n\nWas hiding, you found me!\n\n\nOkay, you can leave now.") text['kakariko_powdered_chicken'] = CompressedTextMapper.convert("Smallhacker…\n\n\nWas hiding, you found me!\n\n\nOkay, you can leave now.")

View File

@@ -8,6 +8,7 @@ import urllib.parse
import urllib.request import urllib.request
import xml.etree.ElementTree as ET import xml.etree.ElementTree as ET
from collections import defaultdict from collections import defaultdict
from hashlib import md5
from itertools import count from itertools import count
from math import factorial from math import factorial
from pathlib import Path from pathlib import Path
@@ -102,31 +103,13 @@ def close_console():
pass pass
def make_new_base2current(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', new_rom='working.sfc'): def get_new_romhash(new_rom='working.sfc'):
import hashlib
import json
from collections import OrderedDict
with open(old_rom, 'rb') as stream:
old_rom_data = bytearray(stream.read())
with open(new_rom, 'rb') as stream: with open(new_rom, 'rb') as stream:
new_rom_data = bytearray(stream.read()) new_rom_data = bytearray(stream.read())
# extend to 2 mb
old_rom_data.extend(bytearray([0x00] * (2097152 - len(old_rom_data))))
out_data = OrderedDict() basemd5 = md5()
for idx, old in enumerate(old_rom_data):
new = new_rom_data[idx]
if old != new:
out_data[idx] = [int(new)]
for offset in reversed(list(out_data.keys())):
if offset - 1 in out_data:
out_data[offset-1].extend(out_data.pop(offset))
with open('data/base2current.json', 'wt') as outfile:
json.dump([{key: value} for key, value in out_data.items()], outfile, separators=(",", ":"))
basemd5 = hashlib.md5()
basemd5.update(new_rom_data) basemd5.update(new_rom_data)
return "New Rom Hash: " + basemd5.hexdigest() return basemd5.hexdigest()
def kth_combination(k, l, r): def kth_combination(k, l, r):
@@ -787,12 +770,11 @@ class bidict(dict):
class HexInt(int): pass class HexInt(int): pass
def hex_representer(dumper, data): def hex_representer(dumper, data):
import yaml
return yaml.ScalarNode('tag:yaml.org,2002:int', f"{data:#0{4}x}") return yaml.ScalarNode('tag:yaml.org,2002:int', f"{data:#0{4}x}")
if __name__ == '__main__': if __name__ == '__main__':
print(make_new_base2current()) print("New Rom Hash:", get_new_romhash())
# read_entrance_data(old_rom=sys.argv[1]) # read_entrance_data(old_rom=sys.argv[1])
# room_palette_data(old_rom=sys.argv[1]) # room_palette_data(old_rom=sys.argv[1])
# extract_data_from_us_rom(sys.argv[1]) # extract_data_from_us_rom(sys.argv[1])

3
Versions.py Normal file
View File

@@ -0,0 +1,3 @@
GKVersion = '1.0.0'
ORVersion = '0.6.1.7'
DRVersion = '1.5.0-u'

View File

@@ -1,6 +0,0 @@
import os
from OverworldShuffle import __version__ as OWVersion
with(open(os.path.join("resources","app","meta","manifests","app_version.txt"),"w+")) as f:
f.write(OWVersion)

Binary file not shown.

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "alttpr-python" name = "alttpr-python"
version = "0.2.0" version = "1.0.0"
description = "Python ALttP Randomizer" description = "Python ALttP Randomizer"
readme = "README.md" readme = "README.md"
requires-python = ">=3.7" requires-python = ">=3.7"

View File

@@ -72,6 +72,7 @@
"trinity", "trinity",
"crystals", "crystals",
"ganonhunt", "ganonhunt",
"bosshunt",
"completionist", "completionist",
"sanctuary" "sanctuary"
] ]
@@ -299,6 +300,27 @@
"random" "random"
] ]
}, },
"bosses_ganon": {
"choices": [
"12",
"11",
"10",
"9",
"8",
"7",
"6",
"5",
"4",
"3",
"2",
"1",
"0"
]
},
"bosshunt_include_agas": {
"action": "store_true",
"type": "bool"
},
"crystals_gt": { "crystals_gt": {
"choices": [ "choices": [
"7", "7",
@@ -431,6 +453,21 @@
"wild" "wild"
] ]
}, },
"showloot": {
"choices": [
"never",
"presence",
"compass",
"always"
]
},
"showmap": {
"choices": [
"visited",
"map",
"always"
]
},
"keysanity": { "keysanity": {
"action": "store_true", "action": "store_true",
"type": "bool", "type": "bool",

View File

@@ -2,7 +2,7 @@
"cli": { "cli": {
"yes": "Yes", "yes": "Yes",
"no": "No", "no": "No",
"app.title": "ALttP Overworld Randomizer Version %s : --seed %s --code %s", "app.title": "ALttP GwaaKiwi Randomizer Version %s : --seed %s",
"version": "Version", "version": "Version",
"seed": "Seed", "seed": "Seed",
"player": "Player", "player": "Player",

View File

@@ -1,18 +0,0 @@
import os
from OverworldShuffle import __version__
OWR_VERSION = __version__
def write_appversion():
APP_VERSION = OWR_VERSION
if "-" in APP_VERSION:
APP_VERSION = APP_VERSION[:APP_VERSION.find("-")]
APP_VERSION_FILE = os.path.join(".","resources","app","meta","manifests","app_version.txt")
with open(APP_VERSION_FILE,"w") as f:
f.seek(0)
f.truncate()
f.write(APP_VERSION)
if __name__ == "__main__":
write_appversion()

View File

@@ -9,27 +9,21 @@ except ModuleNotFoundError as e:
pass pass
import datetime import datetime
from Main import __version__ from Versions import DRVersion, GKVersion, ORVersion
DR_VERSION = __version__
from OverworldShuffle import __version__
OWR_VERSION = __version__
PROJECT_NAME = "ALttP Overworld Randomizer"
def diagpad(str): def diagpad(str):
return str.ljust(len(f"{PROJECT_NAME} Version") + 5,'.') return str.ljust(40, '.')
def output(): def output():
lines = [ lines = [
f"{PROJECT_NAME} Diagnostics", "ALttP GwaaKiwi Randomizer Diagnostics",
"=================================", "=====================================",
diagpad("UTC Time") + str(datetime.datetime.now(datetime.UTC))[:19], diagpad("UTC Time") + str(datetime.datetime.now(datetime.UTC))[:19],
diagpad("ALttP Door Randomizer Version") + DR_VERSION, diagpad("ALttP Door Randomizer Version") + DRVersion,
diagpad(f"{PROJECT_NAME} Version") + OWR_VERSION, diagpad("ALttP Overworld Randomizer Version") + ORVersion,
diagpad("Python Version") + platform.python_version() diagpad("ALttP GwaaKiwi Randomizer Version") + GKVersion,
diagpad("Python Version") + platform.python_version(),
] ]
lines.append(diagpad("OS Version") + "%s %s" % (platform.system(), platform.release())) lines.append(diagpad("OS Version") + "%s %s" % (platform.system(), platform.release()))
if hasattr(sys, "executable"): if hasattr(sys, "executable"):
@@ -43,18 +37,17 @@ def output():
if hasattr(os, "pathsep"): if hasattr(os, "pathsep"):
lines.append(diagpad("Path Env Separator") + os.pathsep) lines.append(diagpad("Path Env Separator") + os.pathsep)
lines.append("") lines.append("")
lines.append("Packages") lines.append("Packages")
lines.append("--------") lines.append("--------")
'''
#this breaks when run from the .exe
reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze']) reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze'])
installed_packages = [r.decode() for r in reqs.split()] installed_packages = [r.decode() for r in reqs.split()]
for pkg in installed_packages: for pkg in installed_packages:
pkg = pkg.split("==") pkg = pkg.split("==")
lines.append(diagpad(pkg[0]) + pkg[1]) lines.append(diagpad(pkg[0]) + pkg[1])
'''
installed_packages = [] installed_packages = []
installed_packages = [str(d) for d in pkg_resources.working_set] #this doesn't work from the .exe either, but it doesn't crash the program installed_packages = [str(d) for d in pkg_resources.working_set]
installed_packages.sort() installed_packages.sort()
for pkg in installed_packages: for pkg in installed_packages:
pkg = pkg.split(' ') pkg = pkg.split(' ')

View File

@@ -471,10 +471,6 @@ vanilla_sheets = [
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x00),
(0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x08), (0x5D, 0x49, 0x00, 0x52), (0x55, 0x49, 0x42, 0x43), (0x00, 0x00, 0x00, 0x00), (0x00, 0x00, 0x00, 0x08), (0x5D, 0x49, 0x00, 0x52), (0x55, 0x49, 0x42, 0x43),
(0x61, 0x62, 0x63, 0x50), (0x61, 0x62, 0x63, 0x50), (0x61, 0x62, 0x63, 0x50), (0x61, 0x62, 0x63, 0x50),
(0x61, 0x62, 0x63, 0x50), (0x61, 0x62, 0x63, 0x50), (0x61, 0x56, 0x57, 0x50), (0x61, 0x62, 0x63, 0x50),
(0x61, 0x62, 0x63, 0x50), (0x61, 0x56, 0x57, 0x50), (0x61, 0x56, 0x63, 0x50), (0x61, 0x56, 0x57, 0x50),
(0x61, 0x56, 0x33, 0x50), (0x61, 0x56, 0x57, 0x50), (0x61, 0x62, 0x63, 0x50), (0x61, 0x62, 0x63, 0x50)
] ]
required_boss_sheets = {EnemySprite.ArmosKnight: 9, EnemySprite.Lanmolas: 11, EnemySprite.Moldorm: 12, required_boss_sheets = {EnemySprite.ArmosKnight: 9, EnemySprite.Lanmolas: 11, EnemySprite.Moldorm: 12,

View File

@@ -20,8 +20,8 @@ from tkinter import (
import source.classes.diags as diagnostics import source.classes.diags as diagnostics
import source.gui.widgets as widgets import source.gui.widgets as widgets
from Main import __version__
from source.classes.Empty import Empty from source.classes.Empty import Empty
from Versions import DRVersion
def generation_page(parent,settings): def generation_page(parent,settings):
@@ -167,7 +167,7 @@ def generation_page(parent,settings):
} }
} }
diag = Tk() diag = Tk()
diag.title("Door Shuffle " + __version__) diag.title("Door Shuffle " + DRVersion)
diag.geometry(str(dims["window"]["width"]) + 'x' + str(dims["window"]["height"])) diag.geometry(str(dims["window"]["width"]) + 'x' + str(dims["window"]["height"]))
text = Text(diag, width=dims["textarea.characters"]["width"], height=dims["textarea.characters"]["height"]) text = Text(diag, width=dims["textarea.characters"]["width"], height=dims["textarea.characters"]["height"])
text.pack() text.pack()

5
uv.lock generated
View File

@@ -11,6 +11,7 @@ resolution-markers = [
name = "aenum" name = "aenum"
version = "3.1.16" version = "3.1.16"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/09/7a/61ed58e8be9e30c3fe518899cc78c284896d246d51381bab59b5db11e1f3/aenum-3.1.16.tar.gz", hash = "sha256:bfaf9589bdb418ee3a986d85750c7318d9d2839c1b1a1d6fe8fc53ec201cf140", size = 137693, upload-time = "2026-01-12T22:34:38.819Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/e3/52/6ad8f63ec8da1bf40f96996d25d5b650fdd38f5975f8c813732c47388f18/aenum-3.1.16-py3-none-any.whl", hash = "sha256:9035092855a98e41b66e3d0998bd7b96280e85ceb3a04cc035636138a1943eaf", size = 165627, upload-time = "2025-04-25T03:17:58.89Z" }, { url = "https://files.pythonhosted.org/packages/e3/52/6ad8f63ec8da1bf40f96996d25d5b650fdd38f5975f8c813732c47388f18/aenum-3.1.16-py3-none-any.whl", hash = "sha256:9035092855a98e41b66e3d0998bd7b96280e85ceb3a04cc035636138a1943eaf", size = 165627, upload-time = "2025-04-25T03:17:58.89Z" },
] ]
@@ -41,8 +42,8 @@ wheels = [
] ]
[[package]] [[package]]
name = "alttpoverworldrandomizer" name = "alttpr-python"
version = "0.1.0" version = "1.0.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "aenum" }, { name = "aenum" },