4 Commits
Author SHA1 Message Date
karafruit 61d7940183 Fix punctuation on Ganon sign 2026-01-25 20:17:57 -06:00
karafruit f539e24ddb GK Version 1.0.0 (#1)
Establish GK as its own fork with versioning, starting with v1.0.0
- bosshunt mode
- dungeon maps are useful
- ensure there's always a bee for sale in shop shuffle

Reviewed-on: #1
Co-authored-by: Kara Alexandra <ardnaxelarak@gmail.com>
Co-committed-by: Kara Alexandra <ardnaxelarak@gmail.com>
2026-01-25 21:29:44 +00:00
karafruit 78dd5c65fc isort 2026-01-25 13:59:45 -06:00
karafruit ec81a900ef Remove unnecessary references to data/base2current.json 2026-01-24 13:50:11 -06:00
114 changed files with 1580 additions and 982 deletions
+1
View File
@@ -0,0 +1 @@
* text=auto
-2
View File
@@ -32,8 +32,6 @@ weights/
/output/
/enemizer/
base2current.json
resources/user/*
!resources/user/.gitkeep
+3 -2
View File
@@ -1,13 +1,14 @@
#!/usr/bin/env python3
import argparse
import os
import logging
import textwrap
import os
import sys
import textwrap
from AdjusterMain import adjust
from Rom import get_sprite_from_name
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
def _get_help_string(self, action):
+2 -2
View File
@@ -1,6 +1,6 @@
import logging
import os
import time
import logging
try:
import bps.apply
@@ -8,9 +8,9 @@ try:
except ImportError:
raise Exception('Could not load BPS module')
from Utils import output_path
from Rom import LocalRom, apply_rom_settings
from source.tools.BPS import bps_read_vlv
from Utils import output_path
def adjust(args):
+35 -15
View File
@@ -2,7 +2,7 @@ import base64
import copy
import json
import logging
from collections import OrderedDict, Counter, deque, defaultdict
from collections import Counter, OrderedDict, defaultdict, deque
from enum import Enum, IntEnum, unique
try:
@@ -10,12 +10,18 @@ try:
except ImportError:
from enum import IntFlag as FastEnum
from source.classes.BabelFish import BabelFish
from Utils import int16_as_bytes
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
from RoomData import Room
from source.classes.BabelFish import BabelFish
from source.dungeon.RoomObject import RoomObject
from source.overworld.EntranceData import door_addresses
from Tables import (
divisor_lookup,
multiply_lookup,
normal_offset_table,
spiral_offset_table,
)
from Utils import int16_as_bytes
from Versions import DRVersion, GKVersion, ORVersion
class World(object):
@@ -74,6 +80,8 @@ class World(object):
self.dark_rooms = {}
self.damage_challenge = {}
self.shuffle_damage_table = {}
self.bosses_ganon = {}
self.bosshunt_include_agas = {}
self.ganon_item = {}
self.ganon_item_orig = {}
self.custom = custom
@@ -149,6 +157,8 @@ class World(object):
set_player_attr('keyshuffle', 'none')
set_player_attr('bigkeyshuffle', '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('bombbag', False)
set_player_attr('flute_mode', 'normal')
@@ -166,6 +176,8 @@ class World(object):
set_player_attr('escape_assist', [])
set_player_attr('crystals_needed_for_ganon', 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('crystals_ganon_orig', {})
set_player_attr('crystals_gt_orig', {})
@@ -354,7 +366,7 @@ class World(object):
else:
if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district']:
return False
elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt']:
elif self.goal[player] in ['crystals', 'trinity', 'ganonhunt', 'bosshunt']:
return True
else:
return False
@@ -1664,8 +1676,8 @@ class Region(object):
self.crystal_switch = False
def can_reach(self, state):
from Utils import stack_size3a
from DungeonGenerator import GenerationException
from Utils import stack_size3a
if stack_size3a() > self.world.players * 1000:
raise GenerationException(f'Infinite loop detected for "{self.name}" located at \'Region.can_reach\'')
@@ -3072,12 +3084,9 @@ class Spoiler(object):
self.doorTypes[(doorNames, player)] = OrderedDict([('player', player), ('doorNames', doorNames), ('type', type)])
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.metadata = {'version': ERVersion,
'versions': {'Door':ERVersion, 'Overworld':ORVersion},
self.metadata = {'version': GKVersion,
'versions': {'Door': DRVersion, 'Overworld': ORVersion},
'logic': self.world.logic,
'mode': self.world.mode,
'bombbag': self.world.bombbag,
@@ -3116,6 +3125,8 @@ class Spoiler(object):
'beemizer': self.world.beemizer,
'gt_crystals': self.world.crystals_needed_for_gt,
'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,
'open_pyramid': self.world.open_pyramid,
'accessibility': self.world.accessibility,
@@ -3126,6 +3137,8 @@ class Spoiler(object):
'keyshuffle': self.world.keyshuffle,
'bigkeyshuffle': self.world.bigkeyshuffle,
'prizeshuffle': self.world.prizeshuffle,
'showloot': self.world.showloot,
'showmap': self.world.showmap,
'boss_shuffle': self.world.boss_shuffle,
'enemy_shuffle': self.world.enemy_shuffle,
'enemy_health': self.world.enemy_health,
@@ -3290,7 +3303,7 @@ class Spoiler(object):
self.parse_meta()
with open(filename, 'w') as outfile:
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():
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
for player in range(1, self.world.players + 1):
@@ -3305,7 +3318,7 @@ class Spoiler(object):
self.parse_meta()
with open(filename, 'w') as outfile:
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():
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
if self.metadata['user_notes']:
@@ -3336,6 +3349,10 @@ class Spoiler(object):
if custom['ganongoal'] and 'requirements' in custom['ganongoal']:
outfile.write('Ganon Requirement:'.ljust(line_width) + 'custom\n')
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:
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']:
@@ -3390,6 +3407,8 @@ class Spoiler(object):
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('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('\n')
outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player])
@@ -3743,8 +3762,9 @@ world_mode = {"open": 0, "standard": 1, "inverted": 2}
sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3}
# byte 2: GGGD DFFH (goal, diff, item_func, hints)
goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3, 'crystals': 4, 'trinity': 5,
'ganonhunt': 6, 'completionist': 7, 'sanctuary': 1}
goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3,
'crystals': 4, 'trinity': 5, 'ganonhunt': 6, 'completionist': 7,
'sanctuary': 1, 'bosshunt': 6}
diff_mode = {"normal": 0, "hard": 1, "expert": 2}
func_mode = {"normal": 0, "hard": 1, "expert": 2}
+1 -1
View File
@@ -1,6 +1,6 @@
import logging
import RaceRandom as random
import RaceRandom as random
from BaseClasses import Boss, FillError
from source.enemizer.Bossmizer import boss_adjust
+9 -6
View File
@@ -2,14 +2,13 @@ import argparse
import copy
import json
import os
import textwrap
import shlex
import sys
import textwrap
from source.classes.BabelFish import BabelFish
from Utils import update_deprecated_args
from source.classes.CustomSettings import CustomSettings
from Utils import update_deprecated_args
class ArgumentDefaultsHelpFormatter(argparse.RawTextHelpFormatter):
@@ -107,7 +106,7 @@ def parse_cli(argv, no_defaults=False):
ret = parser.parse_args(argv)
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:
ret.dropshuffle = 'keys' if ret.dropshuffle == 'none' else ret.dropshuffle
@@ -134,8 +133,8 @@ def parse_cli(argv, no_defaults=False):
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality', 'ow_shuffle',
'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
'flute_mode', 'bow_mode', 'take_any', 'boots_hint', 'shuffle_followers',
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'ganon_item', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'startinventory',
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'bosses_ganon', 'bosshunt_include_agas', 'ganon_item', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'showloot', 'showmap', 'startinventory',
'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
'triforce_max_difference', 'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern',
@@ -178,6 +177,8 @@ def parse_settings():
"goal": "ganon",
"crystals_gt": "7",
"crystals_ganon": "7",
"bosses_ganon": "8",
"bosshunt_include_agas": False,
"ganon_item": "silver",
"swords": "random",
"flute_mode": "normal",
@@ -235,6 +236,8 @@ def parse_settings():
"keyshuffle": "none",
"bigkeyshuffle": "none",
"prizeshuffle": "none",
"showloot": "never",
"showmap": "map",
"keysanity": False,
"door_shuffle": "vanilla",
"intensity": 3,
+1
View File
@@ -3,6 +3,7 @@ from typing import List
import RaceRandom as random
def _load_entries():
entries = []
with open("data/damage_table.bin", 'rb') as stream:
+51 -16
View File
@@ -1,27 +1,62 @@
import RaceRandom as random
from collections import defaultdict, deque
import logging
import time
from enum import unique, Flag
from typing import DefaultDict, Dict, List
from collections import defaultdict, deque
from enum import Flag, unique
from itertools import chain
from typing import DefaultDict, Dict, List
from BaseClasses import RegionType, Region, Door, DoorType, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
from BaseClasses import PotFlags, LocationType, Direction, KeyRuleType
import RaceRandom as random
from BaseClasses import (
CrystalBarrier,
Direction,
Door,
DoorType,
DungeonInfo,
KeyRuleType,
LocationType,
PotFlags,
Region,
RegionType,
Sector,
dungeon_keys,
)
from Doors import reset_portals
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
from Dungeons import dungeon_bigs, dungeon_hints
from DungeonGenerator import (
ExplorationState,
connect_doors,
convert_regions,
count_reserved_locations,
create_dungeon_builders,
default_dungeon_entrances,
determine_required_paths,
drop_entrances,
dungeon_drops,
dungeon_portals,
simple_dungeon_builder,
split_dungeon_builder,
valid_region_to_explore,
)
from Dungeons import (
dungeon_bigs,
dungeon_hints,
dungeon_regions,
region_starts,
split_region_starts,
standard_starts,
)
from Items import ItemFactory
from KeyDoorShuffle import (
DoorRules,
analyze_dungeon,
build_key_layout,
determine_prize_lock,
validate_bk_layout,
validate_key_layout,
)
from RoomData import DoorKind, PairedDoor, reset_rooms
from source.dungeon.DungeonStitcher import GenerationException, generate_dungeon
from source.dungeon.DungeonStitcher import ExplorationState as ExplorationState2
from DungeonGenerator import ExplorationState, convert_regions, determine_required_paths, drop_entrances
from DungeonGenerator import create_dungeon_builders, split_dungeon_builder, simple_dungeon_builder, default_dungeon_entrances
from DungeonGenerator import dungeon_portals, dungeon_drops, connect_doors, count_reserved_locations
from DungeonGenerator import valid_region_to_explore
from KeyDoorShuffle import analyze_dungeon, build_key_layout, validate_key_layout, determine_prize_lock
from KeyDoorShuffle import validate_bk_layout, DoorRules
from Utils import ncr, kth_combination
from source.dungeon.DungeonStitcher import GenerationException, generate_dungeon
from Utils import kth_combination, ncr
def link_doors(world, player):
+1 -1
View File
@@ -1,5 +1,5 @@
from BaseClasses import Door, DoorType, Direction, CrystalBarrier, Portal
from BaseClasses import CrystalBarrier, Direction, Door, DoorType, Portal
from RoomData import PairedDoor
# constants
+19 -9
View File
@@ -1,22 +1,32 @@
import RaceRandom as random
import collections
import itertools
from collections import defaultdict, deque
from functools import reduce
import logging
import math
import operator as op
import time
from collections import defaultdict, deque
from functools import reduce
from typing import List
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys, Sector
from BaseClasses import Hook, hook_from_door, Door
from Regions import location_events, flooded_keys_reverse
import RaceRandom as random
from BaseClasses import (
CrystalBarrier,
Direction,
Door,
DoorType,
Hook,
Polarity,
PolSlot,
RegionType,
Sector,
flooded_keys,
hook_from_door,
)
from Dungeons import split_region_starts
from Regions import flooded_keys_reverse, location_events
from RoomData import DoorKind
from source.dungeon.DungeonStitcher import generate_dungeon_find_proposal
from source.dungeon.DungeonStitcher import GenerationException as OtherGenException
from source.dungeon.DungeonStitcher import generate_dungeon_find_proposal
class GraphPiece:
@@ -2330,7 +2340,7 @@ def parallel_full_neutralization(dungeon_map, polarized_sectors, global_pole):
increment_depth = True
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])
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 sector in sectors:
assign_sector(sector, builder, polarized_sectors, global_pole)
+9 -9
View File
@@ -3,19 +3,19 @@ if __name__ == '__main__':
from source.meta.check_requirements import check_requirements
check_requirements(console=True)
import os
import logging
import RaceRandom as random
import os
import sys
from source.classes.BabelFish import BabelFish
import RaceRandom as random
import source.classes.diags as diagnostics
from CLI import parse_cli, get_args_priority
from Main import main, EnemizerError, __version__
from Rom import get_sprite_from_name
from Utils import is_bundled, close_console
from CLI import get_args_priority, parse_cli
from Fill import FillError
from Main import EnemizerError, main
from Rom import get_sprite_from_name
from source.classes.BabelFish import BabelFish
from Utils import close_console, is_bundled
def start():
args = parse_cli(None)
@@ -80,7 +80,7 @@ def start():
break
except (FillError, EnemizerError, Exception, RuntimeError) as err:
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('')
seed = random.randint(0, 999999999)
+14 -2
View File
@@ -13,7 +13,19 @@ def create_dungeons(world, player):
dungeon.world = world
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))
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))
@@ -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))
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))
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.bosses['bottom'] = BossFactory('Armos Knights', player)
+360 -360
View File
@@ -1,361 +1,361 @@
Hint description:
Hints will appear in the following ratios across the 15 telepathic tiles that have hints and the five storyteller locations:
4 hints for inconvenient entrances.
4 hints for random entrances (this can by coincidence pick inconvenient entrances that aren't used for the first set of hints).
3 hints for inconvenient item locations.
5 hints for valuable items.
4 junk hints.
In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following ratios will be used instead:
5 hints for inconvenient item locations.
8 hints for valuable items.
7 junk hints.
In the simple, restricted shuffles, these are the ratios:
2 hints for inconvenient entrances.
1 hint for an inconvenient dungeon entrance.
4 hints for random entrances (this can by coincidence pick inconvenient entrances that aren't used for the first set of hints).
3 hints for inconvenient item locations.
5 hints for valuable items.
5 junk hints.
These hints will use the following format:
Entrance hints go "[Entrance on overworld] leads to [interior]".
Inconvenient item locations are a little more custom but amount to "[Location] has [item name]". The item name is literal and will specify which dungeon the dungeon specific items hail from (small key/big key/map/compass).
The valuable items are of the format "[item name] can be found [location]". The item name is again literal, and the location text is taken from Ganon's silver arrow hints. Note that the way it works is that every unique valuable item that exists is considered independently, and you won't get multiple hints for the EXACT same item (so you can only get one hint for Progressive Sword no matter how many swords exist in the seed, but if swords are not progressive, you could get hints for both Master Sword and Tempered Sword). More copies of an item existing does not increase the probability of getting a hint for that particular item (you are equally likely to get a hint for a Progressive Sword as for the Hammer). Unlike the IR, item names are never obfuscated by "something unique", and there is no special bias for hints for GT Big Key or Pegasus Boots.
Hint Locations:
Eastern Palace room before Big Chest
Desert Palace bonk torch room
Tower of Hera entrance room
Tower of Hera Big Chest room
Castle Tower after dark rooms
Palace of Darkness before Bow section
Swamp Palace entryway
Thieves' Town upstairs
Ice Palace entrance
Ice Palace after first drop
Ice Palace tall ice floor room
Misery Mire cutscene room
Turtle Rock entrance
Spectacle Rock cave
Spiky Hint cave
PoD Bdlg NPC
Near PoD Storyteller (bug near bomb wall)
Dark Sanctuary Storyteller (long room with tables)
Near Mire Storyteller (feather duster in winding cave)
SE DW Storyteller (owl in winding cave)
Inconvenient entrance list:
Skull Woods Final
Ice Palace
Misery Mire
Turtle Rock
Ganon's Tower
Mimic Ledge
SW DM Foothills Cave (mirror from upper Bumper ledge)
Hammer Pegs (near purple chest)
Super Bomb cracked wall
Inconvenient location list:
Swamp left (two chests)
Mire left (two chests)
Hera basement
Eastern Palace Big Key chest (protected by anti-fairies)
Thieves' Town Big Chest
Ice Palace Big Chest
Ganon's Tower Big Chest
Purple Chest
Spike Cave
Magic Bat
Sahasrahla (Green Pendant)
In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following two locations are added to the inconvenient locations list:
Graveyard Cave
Mimic Cave
Valuable Items are simply all items that are shown on the pause subscreen (Y, B, or A sections) minus Silver Arrows and plus Triforce Pieces, Magic Upgrades (1/2 or 1/4), and the Single Arrow. If key shuffle is being used, you can additionally get hints for Small Keys or Big Keys but not hints for Maps or Compasses.
While the exact verbage of location names and item names can be found in the source code, here's a copy for reference:
Overworld Entrance naming:
Links House: The hero's old residence
Turtle Rock: Turtle Rock Main
Misery Mire: Misery Mire
Ice Palace: Ice Palace
Skull Woods Final Section: The back of Skull Woods
Death Mountain Return Cave (West): The SW DM Foothills Cave
Mimic Cave: Mimic Ledge
Hammer Peg Cave: The rows of pegs
Pyramid Fairy: The crack on the pyramid
Eastern Palace: Eastern Palace
Elder House (East): Elder House
Elder House (West): Elder House
Two Brothers House (East): Eastern Quarreling Brothers' house
Old Man Cave (West): The lower DM entrance
Hyrule Castle Entrance (South): The ground level castle door
Thieves Town: Thieves' Town
Bumper Cave (Bottom): The lower Bumper Cave
Swamp Palace: Swamp Palace
Dark Death Mountain Ledge (West): The East dark DM connector ledge
Dark Death Mountain Ledge (East): The East dark DM connector ledge
Superbunny Cave (Top): The summit of dark DM cave
Superbunny Cave (Bottom): The base of east dark DM
Hookshot Cave: The rock on dark DM
Desert Palace Entrance (South): The book sealed passage
Tower of Hera: The Tower of Hera
Two Brothers House (West): The door near the race game
Old Man Cave (East): The SW-most cave on west DM
Old Man House (Bottom): A cave with a door on west DM
Old Man House (Top): The eastmost cave on west DM
Death Mountain Return Cave (East): The westmost cave on west DM
Spectacle Rock Cave Peak: The highest cave on west DM
Spectacle Rock Cave: The right ledge on west DM
Spectacle Rock Cave (Bottom): The left ledge on west DM
Paradox Cave (Bottom): The right paired cave on east DM
Paradox Cave (Middle): The southmost cave on east DM
Paradox Cave (Top): The east DM summit cave
Fairy Ascension Cave (Bottom): The east DM cave behind rocks
Fairy Ascension Cave (Top): The central ledge on east DM
Spiral Cave: The left ledge on east DM
Spiral Cave (Bottom): The SWmost cave on east DM
Palace of Darkness: Palace of Darkness
Hyrule Castle Entrance (West): The left castle door
Hyrule Castle Entrance (East): The right castle door
Agahnims Tower: The sealed castle door
Desert Palace Entrance (West): The westmost building in the desert
Desert Palace Entrance (North): The northmost cave in the desert
Blinds Hideout: Blind's old house
Lake Hylia Fairy: A cave NE of Lake Hylia
Light Hype Fairy: The cave south of your house
Desert Fairy: The cave near the desert
Chicken House: The chicken lady's house
Tavern North: A backdoor
Aginahs Cave: The open desert cave
Sahasrahlas Hut: The house near armos
Lake Hylia Shop: The cave NW Lake Hylia
Blacksmiths Hut: The old smithery
Sick Kids House: The central house in Kakariko
Lost Woods Gamble: A tree trunk door
Fortune Teller (Light): A building NE of Kakariko
Snitch Lady (East): A house guarded by a snitch
Snitch Lady (West): A house guarded by a snitch
Bush Covered House: A house with an uncut lawn
Tavern (Front): A building with a backdoor
Light World Bomb Hut: A Kakariko building with no door
Kakariko Shop: The old Kakariko shop
Mini Moldorm Cave: The cave south of Lake Hylia
Long Fairy Cave: The eastmost portal cave
Good Bee Cave: The open cave SE Lake Hylia
20 Rupee Cave: The rock SE Lake Hylia
50 Rupee Cave: The rock near the desert
Ice Rod Cave: The sealed cave SE Lake Hylia
Library: The old library
Potion Shop: The witch's building
Dam: The old dam
Lumberjack House: The lumberjack house
Lake Hylia Fortune Teller: The building NW Lake Hylia
Kakariko Gamble Game: The old Kakariko gambling den
Waterfall of Wishing: Going behind the waterfall
Capacity Upgrade: The cave on the island
Bonk Rock Cave: The rock pile near Sanctuary
Graveyard Cave: The graveyard ledge
Checkerboard Cave: The NE desert ledge
Cave 45: The ledge south of haunted grove
Kings Grave: The northeastmost grave
Bonk Fairy (Light): The rock pile near your home
Hookshot Fairy: A cave on east DM
Bonk Fairy (Dark): The rock pile near the old bomb shop
Dark Sanctuary Hint: The dark sanctuary cave
Dark Lake Hylia Fairy: The cave NE dark Lake Hylia
C-Shaped House: The NE house in Village of Outcasts
Big Bomb Shop: The old bomb shop
Dark Death Mountain Fairy: The SW cave on dark DM
Dark Lake Hylia Shop: The building NW dark Lake Hylia
Dark World Shop: The hammer sealed building
Red Shield Shop: The fenced in building
Mire Shed: The western hut in the mire
East Dark World Hint: The dark cave near the eastmost portal
Mire Hint: The cave east of the mire
Spike Cave: The ledge cave on west dark DM
Palace of Darkness Hint: The building south of Kiki
Dark Lake Hylia Ledge Spike Cave: The rock SE dark Lake Hylia
Dark Death Mountain Shop: The base of east dark DM
Dark Potion Shop: The building near the catfish
Archery Game: The old archery game
Dark Lumberjack Shop: The northmost Dark World building
Hype Cave: The cave south of the old bomb shop
Brewery: The Village of Outcasts building with no door
Dark Lake Hylia Ledge Hint: The open cave SE dark Lake Hylia
Chest Game: The westmost building in the Village of Outcasts
Mire Fairy: The eastern hut in the mire
Dark Lake Hylia Ledge Fairy: The sealed cave SE dark Lake Hylia
Fortune Teller (Dark): The building NE the Village of Outcasts
Sanctuary: Sanctuary
Lumberjack Tree Cave: The cave Behind Lumberjacks
Lost Woods Hideout Stump: The stump in Lost Woods
North Fairy Cave: The cave East of Graveyard
Bat Cave Cave: The cave in eastern Kakariko
Kakariko Well Cave: The cave in northern Kakariko
Hyrule Castle Secret Entrance Stairs: The tunnel near the castle
Skull Woods First Section Door: The southeastmost skull
Skull Woods Second Section Door (East): The central open skull
Skull Woods Second Section Door (West): The westmost open skull
Desert Palace Entrance (East): The eastern building in the desert
Turtle Rock Isolated Ledge Entrance: The isolated ledge on east dark DM
Bumper Cave (Top): The upper Bumper Cave
Hookshot Cave Back Entrance: The stairs on the floating island
Destination Entrance Naming:
Hyrule Castle: Hyrule Castle (all three entrances)
Eastern Palace: Eastern Palace
Desert Palace: Desert Palace (all four entrances, including final)
Tower of Hera: Tower of Hera
Palace of Darkness: Palace of Darkness
Swamp Palace: Swamp Palace
Skull Woods: Skull Woods (any entrance including final)
Thieves' Town: Thieves' Town
Ice Palace: Ice Palace
Misery Mire: Misery Mire
Turtle Rock: Turtle Rock (all four entrances)
Ganon's Tower: Ganon's Tower
Castle Tower: Agahnim's Tower
A connector: Paradox Cave, Spectacle Rock Cave, Hookshot Cave, Superbunny Cave, Spiral Cave, Old Man Fetch Cave, Old Man House, Elder House, Quarreling Brothers' House, Bumper Cave, DM Fairy Ascent Cave, DM Exit Cave
A bounty of five items: Mini-moldorm cave, Hype Cave, Blind's Hideout
Sahasrahla: Sahasrahla
A cave with two items: Mire hut, Waterfall Fairy, Pyramid Fairy
A fairy fountain: Any healer fairy cave, either bonk cave with four fairies, the "long fairy" cave
A common shop: Any shop that sells bombs by default
The rare shop: The shop that sells the Red Shield by default
The potion shop: Potion Shop
The bomb shop: Bomb Shop
A fortune teller: Any of the three fortune tellers
A house with a chest: Chicken Lady's house, C-House, Brewery
A cave with an item: Checkerboard cave, Hammer Pegs cave, Cave 45, Graveyard Ledge cave
A cave with a chest: Sanc Bonk Rock Cave, Cape Grave Cave, Ice Rod Cave, Aginah's Cave
The dam: Watergate
The sick kid: Sick Kid
The library: Library
Mimic Cave: Mimic Cave
Spike Cave: Spike Cave
A game of 16 chests: VoO chest game (for the item)
A storyteller: The four DW NPCs who charge 20 rupees for a hint as well as the PoD Bdlg guy who gives a free hint
A cave with some cash: 20 rupee cave, 50 rupee cave (both have thieves and some pots)
A game of chance: Gambling game (just for cash, no items)
A game of skill: Archery minigame
The queen of fairies: Capacity Upgrade Fairy
A drop's exit: Sanctuary, LW Thieves' Hideout, Kakariko Well, Magic Bat, Useless Fairy, Uncle Tunnel, Ganon drop exit
A restock room: The Kakariko bomb/arrow restock room
The tavern: The Kakariko tavern
The grass man: The Kakariko man with many beds
A cold bee: The "wrong side" of Ice Rod cave where you can get a Good Bee
Fairies deep in a cave: Hookshot Fairy
Location naming reference:
Mushroom: in the woods
Master Sword Pedestal: at the pedestal
Bottle Merchant: with a merchant
Stumpy: with tree boy
Flute Spot: underground
Digging Game: underground
Lake Hylia Island: on an island
Floating Island: on an island
Bumper Cave Ledge: on a ledge
Spectacle Rock: atop a rock
Maze Race: at the race
Desert Ledge: in the desert
Pyramid: on the pyramid
Catfish: with a catfish
Ether Tablet: at a monument
Bombos Tablet: at a monument
Hobo: with the hobo
Zora's Ledge: near Zora
King Zora: at a high price
Sunken Treasure: underwater
Floodgate Chest: in the dam
Blacksmith: with the smith
Purple Chest: from a box
Old Man: with the old man
Link's Uncle: with your uncle
Secret Passage: near your uncle
Kakariko Well (5 items): in a well
Lost Woods Hideout: near a thief
Lumberjack Tree: in a hole
Magic Bat: with the bat
Paradox Cave (7 items): in a cave with seven chests
Blind's Hideout (5 items): in a basement
Mini Moldorm Cave (5 items): near Moldorms
Hype Cave (4 back chests): near a bat-like man
Hype Cave - Generous Guy: with a bat-like man
Hookshot Cave (4 items): across pits
Sahasrahla's Hut (chests in back): near the elder
Sahasrahla: with the elder
Waterfall Fairy (2 items): near a fairy
Pyramid Fairy (2 items): near a fairy
Mire Shed (2 items): near sparks
Superbunny Cave (2 items): in a connection
Spiral Cave: in spiral cave
Kakariko Tavern: in the bar
Link's House: in your home
Sick Kid: with the sick
Library: near books
Potion Shop: near potions
Spike Cave: beyond spikes
Mimic Cave: in a cave of mimicry
Chest Game: as a game reward
Chicken House: near poultry
Aginah's Cave: with Aginah
Ice Rod Cave: in a frozen cave
Brewery: alone in a home
C-Shaped House: alone in a home
Spectacle Rock Cave: alone in a cave
King's Tomb: alone in a cave
Cave 45: alone in a cave
Graveyard Cave: alone in a cave
Checkerboard Cave: alone in a cave
Bonk Rock Cave: alone in a cave
Peg Cave: alone in a cave
Sanctuary: in Sanctuary
Hyrule Castle - Boomerang Chest: in Hyrule Castle
Hyrule Castle - Map Chest: in Hyrule Castle
Hyrule Castle - Zelda's Chest: in Hyrule Castle
Sewers - Dark Cross: in the sewers
Sewers - Secret Room (3 items): in the sewers
Eastern Palace - Boss: with the Armos
Eastern Palace (otherwise, 5 items): in Eastern Palace
Desert Palace - Boss: with Lanmolas
Desert Palace (otherwise, 5 items): in Desert Palace
Tower of Hera - Boss: with Moldorm
Tower of Hera (otherwise, 5 items): in Tower of Hera
Castle Tower (2 items): in Castle Tower
Palace of Darkness - Boss: with Helmasaur King
Palace of Darkness (otherwise, 13 items): in Palace of Darkness
Swamp Palace - Boss: with Arrghus
Swamp Palace (otherwise, 9 items): in Swamp Palace
Skull Woods - Bridge Room: near Mothula
Skull Woods - Boss: with Mothula
Skull Woods (otherwise, 6 items): in Skull Woods
Thieves' Town - Boss: with Blind
Thieves' Town (otherwise, 7 items): in Thieves' Town
Ice Palace - Boss: with Kholdstare
Ice Palace (otherwise, 7 items): in Ice Palace
Misery Mire - Boss: with Vitreous
Misery Mire (otherwise, 7 items): in Misery Mire
Turtle Rock - Boss: with Trinexx
Turtle Rock (otherwise, 11 items): in Turtle Rock
Ganons Tower (after climb, 4 items): atop Ganon's Tower
Hint description:
Hints will appear in the following ratios across the 15 telepathic tiles that have hints and the five storyteller locations:
4 hints for inconvenient entrances.
4 hints for random entrances (this can by coincidence pick inconvenient entrances that aren't used for the first set of hints).
3 hints for inconvenient item locations.
5 hints for valuable items.
4 junk hints.
In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following ratios will be used instead:
5 hints for inconvenient item locations.
8 hints for valuable items.
7 junk hints.
In the simple, restricted shuffles, these are the ratios:
2 hints for inconvenient entrances.
1 hint for an inconvenient dungeon entrance.
4 hints for random entrances (this can by coincidence pick inconvenient entrances that aren't used for the first set of hints).
3 hints for inconvenient item locations.
5 hints for valuable items.
5 junk hints.
These hints will use the following format:
Entrance hints go "[Entrance on overworld] leads to [interior]".
Inconvenient item locations are a little more custom but amount to "[Location] has [item name]". The item name is literal and will specify which dungeon the dungeon specific items hail from (small key/big key/map/compass).
The valuable items are of the format "[item name] can be found [location]". The item name is again literal, and the location text is taken from Ganon's silver arrow hints. Note that the way it works is that every unique valuable item that exists is considered independently, and you won't get multiple hints for the EXACT same item (so you can only get one hint for Progressive Sword no matter how many swords exist in the seed, but if swords are not progressive, you could get hints for both Master Sword and Tempered Sword). More copies of an item existing does not increase the probability of getting a hint for that particular item (you are equally likely to get a hint for a Progressive Sword as for the Hammer). Unlike the IR, item names are never obfuscated by "something unique", and there is no special bias for hints for GT Big Key or Pegasus Boots.
Hint Locations:
Eastern Palace room before Big Chest
Desert Palace bonk torch room
Tower of Hera entrance room
Tower of Hera Big Chest room
Castle Tower after dark rooms
Palace of Darkness before Bow section
Swamp Palace entryway
Thieves' Town upstairs
Ice Palace entrance
Ice Palace after first drop
Ice Palace tall ice floor room
Misery Mire cutscene room
Turtle Rock entrance
Spectacle Rock cave
Spiky Hint cave
PoD Bdlg NPC
Near PoD Storyteller (bug near bomb wall)
Dark Sanctuary Storyteller (long room with tables)
Near Mire Storyteller (feather duster in winding cave)
SE DW Storyteller (owl in winding cave)
Inconvenient entrance list:
Skull Woods Final
Ice Palace
Misery Mire
Turtle Rock
Ganon's Tower
Mimic Ledge
SW DM Foothills Cave (mirror from upper Bumper ledge)
Hammer Pegs (near purple chest)
Super Bomb cracked wall
Inconvenient location list:
Swamp left (two chests)
Mire left (two chests)
Hera basement
Eastern Palace Big Key chest (protected by anti-fairies)
Thieves' Town Big Chest
Ice Palace Big Chest
Ganon's Tower Big Chest
Purple Chest
Spike Cave
Magic Bat
Sahasrahla (Green Pendant)
In the vanilla, dungeonssimple, and dungeonsfull shuffles, the following two locations are added to the inconvenient locations list:
Graveyard Cave
Mimic Cave
Valuable Items are simply all items that are shown on the pause subscreen (Y, B, or A sections) minus Silver Arrows and plus Triforce Pieces, Magic Upgrades (1/2 or 1/4), and the Single Arrow. If key shuffle is being used, you can additionally get hints for Small Keys or Big Keys but not hints for Maps or Compasses.
While the exact verbage of location names and item names can be found in the source code, here's a copy for reference:
Overworld Entrance naming:
Links House: The hero's old residence
Turtle Rock: Turtle Rock Main
Misery Mire: Misery Mire
Ice Palace: Ice Palace
Skull Woods Final Section: The back of Skull Woods
Death Mountain Return Cave (West): The SW DM Foothills Cave
Mimic Cave: Mimic Ledge
Hammer Peg Cave: The rows of pegs
Pyramid Fairy: The crack on the pyramid
Eastern Palace: Eastern Palace
Elder House (East): Elder House
Elder House (West): Elder House
Two Brothers House (East): Eastern Quarreling Brothers' house
Old Man Cave (West): The lower DM entrance
Hyrule Castle Entrance (South): The ground level castle door
Thieves Town: Thieves' Town
Bumper Cave (Bottom): The lower Bumper Cave
Swamp Palace: Swamp Palace
Dark Death Mountain Ledge (West): The East dark DM connector ledge
Dark Death Mountain Ledge (East): The East dark DM connector ledge
Superbunny Cave (Top): The summit of dark DM cave
Superbunny Cave (Bottom): The base of east dark DM
Hookshot Cave: The rock on dark DM
Desert Palace Entrance (South): The book sealed passage
Tower of Hera: The Tower of Hera
Two Brothers House (West): The door near the race game
Old Man Cave (East): The SW-most cave on west DM
Old Man House (Bottom): A cave with a door on west DM
Old Man House (Top): The eastmost cave on west DM
Death Mountain Return Cave (East): The westmost cave on west DM
Spectacle Rock Cave Peak: The highest cave on west DM
Spectacle Rock Cave: The right ledge on west DM
Spectacle Rock Cave (Bottom): The left ledge on west DM
Paradox Cave (Bottom): The right paired cave on east DM
Paradox Cave (Middle): The southmost cave on east DM
Paradox Cave (Top): The east DM summit cave
Fairy Ascension Cave (Bottom): The east DM cave behind rocks
Fairy Ascension Cave (Top): The central ledge on east DM
Spiral Cave: The left ledge on east DM
Spiral Cave (Bottom): The SWmost cave on east DM
Palace of Darkness: Palace of Darkness
Hyrule Castle Entrance (West): The left castle door
Hyrule Castle Entrance (East): The right castle door
Agahnims Tower: The sealed castle door
Desert Palace Entrance (West): The westmost building in the desert
Desert Palace Entrance (North): The northmost cave in the desert
Blinds Hideout: Blind's old house
Lake Hylia Fairy: A cave NE of Lake Hylia
Light Hype Fairy: The cave south of your house
Desert Fairy: The cave near the desert
Chicken House: The chicken lady's house
Tavern North: A backdoor
Aginahs Cave: The open desert cave
Sahasrahlas Hut: The house near armos
Lake Hylia Shop: The cave NW Lake Hylia
Blacksmiths Hut: The old smithery
Sick Kids House: The central house in Kakariko
Lost Woods Gamble: A tree trunk door
Fortune Teller (Light): A building NE of Kakariko
Snitch Lady (East): A house guarded by a snitch
Snitch Lady (West): A house guarded by a snitch
Bush Covered House: A house with an uncut lawn
Tavern (Front): A building with a backdoor
Light World Bomb Hut: A Kakariko building with no door
Kakariko Shop: The old Kakariko shop
Mini Moldorm Cave: The cave south of Lake Hylia
Long Fairy Cave: The eastmost portal cave
Good Bee Cave: The open cave SE Lake Hylia
20 Rupee Cave: The rock SE Lake Hylia
50 Rupee Cave: The rock near the desert
Ice Rod Cave: The sealed cave SE Lake Hylia
Library: The old library
Potion Shop: The witch's building
Dam: The old dam
Lumberjack House: The lumberjack house
Lake Hylia Fortune Teller: The building NW Lake Hylia
Kakariko Gamble Game: The old Kakariko gambling den
Waterfall of Wishing: Going behind the waterfall
Capacity Upgrade: The cave on the island
Bonk Rock Cave: The rock pile near Sanctuary
Graveyard Cave: The graveyard ledge
Checkerboard Cave: The NE desert ledge
Cave 45: The ledge south of haunted grove
Kings Grave: The northeastmost grave
Bonk Fairy (Light): The rock pile near your home
Hookshot Fairy: A cave on east DM
Bonk Fairy (Dark): The rock pile near the old bomb shop
Dark Sanctuary Hint: The dark sanctuary cave
Dark Lake Hylia Fairy: The cave NE dark Lake Hylia
C-Shaped House: The NE house in Village of Outcasts
Big Bomb Shop: The old bomb shop
Dark Death Mountain Fairy: The SW cave on dark DM
Dark Lake Hylia Shop: The building NW dark Lake Hylia
Dark World Shop: The hammer sealed building
Red Shield Shop: The fenced in building
Mire Shed: The western hut in the mire
East Dark World Hint: The dark cave near the eastmost portal
Mire Hint: The cave east of the mire
Spike Cave: The ledge cave on west dark DM
Palace of Darkness Hint: The building south of Kiki
Dark Lake Hylia Ledge Spike Cave: The rock SE dark Lake Hylia
Dark Death Mountain Shop: The base of east dark DM
Dark Potion Shop: The building near the catfish
Archery Game: The old archery game
Dark Lumberjack Shop: The northmost Dark World building
Hype Cave: The cave south of the old bomb shop
Brewery: The Village of Outcasts building with no door
Dark Lake Hylia Ledge Hint: The open cave SE dark Lake Hylia
Chest Game: The westmost building in the Village of Outcasts
Mire Fairy: The eastern hut in the mire
Dark Lake Hylia Ledge Fairy: The sealed cave SE dark Lake Hylia
Fortune Teller (Dark): The building NE the Village of Outcasts
Sanctuary: Sanctuary
Lumberjack Tree Cave: The cave Behind Lumberjacks
Lost Woods Hideout Stump: The stump in Lost Woods
North Fairy Cave: The cave East of Graveyard
Bat Cave Cave: The cave in eastern Kakariko
Kakariko Well Cave: The cave in northern Kakariko
Hyrule Castle Secret Entrance Stairs: The tunnel near the castle
Skull Woods First Section Door: The southeastmost skull
Skull Woods Second Section Door (East): The central open skull
Skull Woods Second Section Door (West): The westmost open skull
Desert Palace Entrance (East): The eastern building in the desert
Turtle Rock Isolated Ledge Entrance: The isolated ledge on east dark DM
Bumper Cave (Top): The upper Bumper Cave
Hookshot Cave Back Entrance: The stairs on the floating island
Destination Entrance Naming:
Hyrule Castle: Hyrule Castle (all three entrances)
Eastern Palace: Eastern Palace
Desert Palace: Desert Palace (all four entrances, including final)
Tower of Hera: Tower of Hera
Palace of Darkness: Palace of Darkness
Swamp Palace: Swamp Palace
Skull Woods: Skull Woods (any entrance including final)
Thieves' Town: Thieves' Town
Ice Palace: Ice Palace
Misery Mire: Misery Mire
Turtle Rock: Turtle Rock (all four entrances)
Ganon's Tower: Ganon's Tower
Castle Tower: Agahnim's Tower
A connector: Paradox Cave, Spectacle Rock Cave, Hookshot Cave, Superbunny Cave, Spiral Cave, Old Man Fetch Cave, Old Man House, Elder House, Quarreling Brothers' House, Bumper Cave, DM Fairy Ascent Cave, DM Exit Cave
A bounty of five items: Mini-moldorm cave, Hype Cave, Blind's Hideout
Sahasrahla: Sahasrahla
A cave with two items: Mire hut, Waterfall Fairy, Pyramid Fairy
A fairy fountain: Any healer fairy cave, either bonk cave with four fairies, the "long fairy" cave
A common shop: Any shop that sells bombs by default
The rare shop: The shop that sells the Red Shield by default
The potion shop: Potion Shop
The bomb shop: Bomb Shop
A fortune teller: Any of the three fortune tellers
A house with a chest: Chicken Lady's house, C-House, Brewery
A cave with an item: Checkerboard cave, Hammer Pegs cave, Cave 45, Graveyard Ledge cave
A cave with a chest: Sanc Bonk Rock Cave, Cape Grave Cave, Ice Rod Cave, Aginah's Cave
The dam: Watergate
The sick kid: Sick Kid
The library: Library
Mimic Cave: Mimic Cave
Spike Cave: Spike Cave
A game of 16 chests: VoO chest game (for the item)
A storyteller: The four DW NPCs who charge 20 rupees for a hint as well as the PoD Bdlg guy who gives a free hint
A cave with some cash: 20 rupee cave, 50 rupee cave (both have thieves and some pots)
A game of chance: Gambling game (just for cash, no items)
A game of skill: Archery minigame
The queen of fairies: Capacity Upgrade Fairy
A drop's exit: Sanctuary, LW Thieves' Hideout, Kakariko Well, Magic Bat, Useless Fairy, Uncle Tunnel, Ganon drop exit
A restock room: The Kakariko bomb/arrow restock room
The tavern: The Kakariko tavern
The grass man: The Kakariko man with many beds
A cold bee: The "wrong side" of Ice Rod cave where you can get a Good Bee
Fairies deep in a cave: Hookshot Fairy
Location naming reference:
Mushroom: in the woods
Master Sword Pedestal: at the pedestal
Bottle Merchant: with a merchant
Stumpy: with tree boy
Flute Spot: underground
Digging Game: underground
Lake Hylia Island: on an island
Floating Island: on an island
Bumper Cave Ledge: on a ledge
Spectacle Rock: atop a rock
Maze Race: at the race
Desert Ledge: in the desert
Pyramid: on the pyramid
Catfish: with a catfish
Ether Tablet: at a monument
Bombos Tablet: at a monument
Hobo: with the hobo
Zora's Ledge: near Zora
King Zora: at a high price
Sunken Treasure: underwater
Floodgate Chest: in the dam
Blacksmith: with the smith
Purple Chest: from a box
Old Man: with the old man
Link's Uncle: with your uncle
Secret Passage: near your uncle
Kakariko Well (5 items): in a well
Lost Woods Hideout: near a thief
Lumberjack Tree: in a hole
Magic Bat: with the bat
Paradox Cave (7 items): in a cave with seven chests
Blind's Hideout (5 items): in a basement
Mini Moldorm Cave (5 items): near Moldorms
Hype Cave (4 back chests): near a bat-like man
Hype Cave - Generous Guy: with a bat-like man
Hookshot Cave (4 items): across pits
Sahasrahla's Hut (chests in back): near the elder
Sahasrahla: with the elder
Waterfall Fairy (2 items): near a fairy
Pyramid Fairy (2 items): near a fairy
Mire Shed (2 items): near sparks
Superbunny Cave (2 items): in a connection
Spiral Cave: in spiral cave
Kakariko Tavern: in the bar
Link's House: in your home
Sick Kid: with the sick
Library: near books
Potion Shop: near potions
Spike Cave: beyond spikes
Mimic Cave: in a cave of mimicry
Chest Game: as a game reward
Chicken House: near poultry
Aginah's Cave: with Aginah
Ice Rod Cave: in a frozen cave
Brewery: alone in a home
C-Shaped House: alone in a home
Spectacle Rock Cave: alone in a cave
King's Tomb: alone in a cave
Cave 45: alone in a cave
Graveyard Cave: alone in a cave
Checkerboard Cave: alone in a cave
Bonk Rock Cave: alone in a cave
Peg Cave: alone in a cave
Sanctuary: in Sanctuary
Hyrule Castle - Boomerang Chest: in Hyrule Castle
Hyrule Castle - Map Chest: in Hyrule Castle
Hyrule Castle - Zelda's Chest: in Hyrule Castle
Sewers - Dark Cross: in the sewers
Sewers - Secret Room (3 items): in the sewers
Eastern Palace - Boss: with the Armos
Eastern Palace (otherwise, 5 items): in Eastern Palace
Desert Palace - Boss: with Lanmolas
Desert Palace (otherwise, 5 items): in Desert Palace
Tower of Hera - Boss: with Moldorm
Tower of Hera (otherwise, 5 items): in Tower of Hera
Castle Tower (2 items): in Castle Tower
Palace of Darkness - Boss: with Helmasaur King
Palace of Darkness (otherwise, 13 items): in Palace of Darkness
Swamp Palace - Boss: with Arrghus
Swamp Palace (otherwise, 9 items): in Swamp Palace
Skull Woods - Bridge Room: near Mothula
Skull Woods - Boss: with Mothula
Skull Woods (otherwise, 6 items): in Skull Woods
Thieves' Town - Boss: with Blind
Thieves' Town (otherwise, 7 items): in Thieves' Town
Ice Palace - Boss: with Kholdstare
Ice Palace (otherwise, 7 items): in Ice Palace
Misery Mire - Boss: with Vitreous
Misery Mire (otherwise, 7 items): in Misery Mire
Turtle Rock - Boss: with Trinexx
Turtle Rock (otherwise, 11 items): in Turtle Rock
Ganons Tower (after climb, 4 items): atop Ganon's Tower
Ganon's Tower (otherwise, 23 items): in Ganon's Tower
+12 -6
View File
@@ -1,4 +1,3 @@
import RaceRandom as random
import collections
import itertools
import logging
@@ -6,11 +5,18 @@ import math
from collections import Counter
from contextlib import suppress
import RaceRandom as random
from BaseClasses import CollectionState, FillError, LocationType
from Items import ItemFactory
from Regions import shop_to_location_table, retro_shops
from source.item.FillUtil import filter_locations, classify_major_items, replace_trash_item, vanilla_fallback
from source.item.FillUtil import filter_special_locations, valid_pot_items
from Regions import retro_shops, shop_to_location_table
from source.item.FillUtil import (
classify_major_items,
filter_locations,
filter_special_locations,
replace_trash_item,
valid_pot_items,
vanilla_fallback,
)
def get_dungeon_item_pool(world):
@@ -793,7 +799,7 @@ def sell_potions(world, player):
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]]
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))
locations.remove(location)
p_item = next((item for item in world.itempool if item.name == potion and item.player == player), None)
@@ -1287,4 +1293,4 @@ def set_prize_drops(world, player):
# saved fish prize
world.prizes[player]['fish'] = prizes.pop()
world.prizes[player]['enemies'] = prizes
world.prizes[player]['enemies'] = prizes
+27 -16
View File
@@ -5,36 +5,47 @@ if __name__ == '__main__':
import json
import os
import sys
from tkinter import Tk, Button, BOTTOM, TOP, StringVar, BooleanVar, X, BOTH, RIGHT, ttk, messagebox
from tkinter import (
BOTH,
BOTTOM,
RIGHT,
TOP,
BooleanVar,
Button,
StringVar,
Tk,
X,
messagebox,
ttk,
)
from CLI import get_args_priority
from DungeonRandomizer import parse_cli
from GuiUtils import set_icon
from source.classes.BabelFish import BabelFish
from source.classes.Empty import Empty
from source.gui.adjust.overview import adjust_page
from source.gui.startinventory.overview import startinventory_page
from source.gui.bottom import bottom_frame, create_guiargs
from source.gui.custom.overview import custom_page
from source.gui.loadcliargs import loadcliargs, loadadjustargs
from source.gui.randomize.item import item_page
from source.gui.randomize.overworld import overworld_page
from source.gui.randomize.entrando import entrando_page
from source.gui.randomize.enemizer import enemizer_page
from source.gui.loadcliargs import loadadjustargs, loadcliargs
from source.gui.randomize.dungeon import dungeon_page
from source.gui.randomize.enemizer import enemizer_page
from source.gui.randomize.entrando import entrando_page
#from source.gui.randomize.multiworld import multiworld_page
from source.gui.randomize.gameoptions import gameoptions_page
from source.gui.randomize.generation import generation_page
from source.gui.bottom import bottom_frame, create_guiargs
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.Empty import Empty
from source.gui.randomize.item import item_page
from source.gui.randomize.overworld import overworld_page
from source.gui.startinventory.overview import startinventory_page
from Versions import DRVersion, GKVersion, ORVersion
def check_python_version(fish):
import sys
version = sys.version_info
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
@@ -83,7 +94,7 @@ def guiMain(args=None):
mainWindow = Tk()
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
# set program icon
+2 -1
View File
@@ -1,10 +1,11 @@
import queue
import os
import queue
import threading
import tkinter as tk
from Utils import local_path
def set_icon(window):
er16 = tk.PhotoImage(file=local_path(os.path.join("data","ER16.gif")))
er32 = tk.PhotoImage(file=local_path(os.path.join("data","ER32.gif")))
+41 -21
View File
@@ -1,21 +1,39 @@
from collections import namedtuple, defaultdict
import logging
import math
from collections import defaultdict, namedtuple
import RaceRandom as random
from BaseClasses import LocationType, Region, RegionType, Shop, ShopType, Location, CollectionState, PotItem
from Regions import location_events, shop_to_location_table, retro_shops, shop_table_by_location, valid_pot_location
from Fill import FillError, fill_restrictive, get_dungeon_item_pool, track_dungeon_items, track_outside_keys
from PotShuffle import vanilla_pots
from Tables import bonk_prize_lookup
from Items import ItemFactory
from source.dungeon.EnemyList import add_drop_contents
from source.overworld.EntranceShuffle2 import exit_ids, door_addresses
from source.item.FillUtil import trash_items, pot_items
import source.classes.constants as CONST
from BaseClasses import (
CollectionState,
Location,
LocationType,
PotItem,
Region,
RegionType,
Shop,
ShopType,
)
from Fill import (
FillError,
fill_restrictive,
get_dungeon_item_pool,
track_dungeon_items,
track_outside_keys,
)
from Items import ItemFactory
from PotShuffle import vanilla_pots
from Regions import (
location_events,
retro_shops,
shop_table_by_location,
shop_to_location_table,
valid_pot_location,
)
from source.dungeon.EnemyList import add_drop_contents
from source.item.FillUtil import pot_items, trash_items
from source.overworld.EntranceShuffle2 import door_addresses, exit_ids
from Tables import bonk_prize_lookup
#This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
#Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
@@ -218,12 +236,14 @@ def get_custom_array_key(item):
def generate_itempool(world, player):
if (world.difficulty[player] not in ['normal', 'hard', 'expert']
or world.goal[player] not in ['ganon', 'pedestal', 'dungeons', 'triforcehunt', 'trinity', 'crystals',
'ganonhunt', 'completionist', 'sanctuary']
or world.goal[player] not in ['ganon', 'pedestal', 'dungeons',
'triforcehunt', 'trinity', 'crystals',
'ganonhunt', 'completionist', 'sanctuary',
'bosshunt']
or world.mode[player] not in ['open', 'standard', 'inverted']
or world.timer not in ['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown']
or world.progressive not in ['on', 'off', 'random']):
raise NotImplementedError('Not supported yet')
raise NotImplementedError('Not supported yet')
if world.timer in ['ohko', 'timed-ohko']:
world.can_take_damage[player] = False
@@ -361,7 +381,7 @@ def generate_itempool(world, player):
items = ItemFactory(pool, 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.priority = True # don't beemize one of each potion
@@ -691,7 +711,7 @@ def create_farm_locations(world, player):
world.dynamic_locations.append(loc)
return loc
from Rules import set_rule, add_rule, add_bunny_rule
from Rules import add_bunny_rule, add_rule, set_rule
for region in bush_bombs:
loc = create_and_fill_location(region, 'Bush Drop', 'Farmable Bombs')
add_bunny_rule(loc, player)
@@ -1107,7 +1127,7 @@ def get_pool_core(world, player, progressive, shuffle, difficulty, treasure_hunt
precollected_items.append('Pegasus Boots')
pool.remove('Pegasus Boots')
pool.extend(['Rupees (20)'])
if want_progressives():
pool.extend(progressivegloves)
else:
@@ -1502,7 +1522,7 @@ def make_customizer_pool(world, player):
guaranteed_items.append('Ocarina (Activated)')
missing_items = []
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':
guaranteed_items.append('Small Key (Universal)')
for item in guaranteed_items:
+10 -4
View File
@@ -2,11 +2,17 @@ import itertools
import logging
from collections import defaultdict, deque
from BaseClasses import DoorType, dungeon_keys, KeyRuleType, RegionType
from BaseClasses import DoorType, KeyRuleType, RegionType, dungeon_keys
from DungeonGenerator import (
ExplorationState,
blind_boss_unavail,
count_locations_exclude_big_chest,
get_special_big_key_doors,
prize_or_event,
reserved_location,
)
from Dungeons import dungeon_bigs, dungeon_keys, dungeon_table
from Regions import location_events
from Dungeons import dungeon_keys, dungeon_bigs, dungeon_table
from DungeonGenerator import ExplorationState, get_special_big_key_doors, count_locations_exclude_big_chest, prize_or_event
from DungeonGenerator import reserved_location, blind_boss_unavail
class KeyLayout(object):
+99 -42
View File
@@ -1,50 +1,99 @@
import base64
import copy
from itertools import zip_longest
import json
import logging
import os
import RaceRandom as random
import string
import time
import zlib
import base64
from itertools import zip_longest
from BaseClasses import World, CollectionState, Item, Region, Location, Shop, Entrance, Settings
import RaceRandom as random
from BaseClasses import (
CollectionState,
Entrance,
Item,
Location,
Region,
Settings,
Shop,
World,
)
from Bosses import place_bosses
from Doors import create_doors
from DoorShuffle import connect_portal, link_doors, link_doors_prep
from Dungeons import create_dungeons
from Fill import (
balance_money_progression,
balance_multiworld_progression,
distribute_items_restrictive,
dungeon_tracking,
ensure_good_items,
fill_dungeons_restrictive,
lock_shop_locations,
promote_dungeon_items,
sell_keys,
sell_potions,
set_prize_drops,
)
from ItemList import (
create_farm_locations,
customize_shops,
difficulties,
fill_prizes,
fill_specific_items,
follower_pickups,
generate_itempool,
shuffle_event_items,
)
from Items import ItemFactory
from KeyDoorShuffle import validate_key_placement
from OverworldGlitchRules import create_owg_connections
from PotShuffle import shuffle_pots, shuffle_pot_switches
from Regions import create_regions, create_shops, mark_light_dark_world_regions, create_dungeon_regions, adjust_locations
from OverworldShuffle import (
create_dynamic_flute_exits,
create_dynamic_mirror_exits,
link_overworld,
update_world_regions,
)
from OWEdges import create_owedges
from OverworldShuffle import link_overworld, update_world_regions, create_dynamic_flute_exits, create_dynamic_mirror_exits
from Rom import patch_rom, patch_race_rom, apply_rom_settings, LocalRom, JsonRom, get_hash_string
from Doors import create_doors
from DoorShuffle import link_doors, connect_portal, link_doors_prep
from PotShuffle import shuffle_pot_switches, shuffle_pots
from Regions import (
adjust_locations,
create_dungeon_regions,
create_regions,
create_shops,
mark_light_dark_world_regions,
)
from Rom import (
JsonRom,
LocalRom,
apply_rom_settings,
get_hash_string,
patch_race_rom,
patch_rom,
)
from RoomData import create_rooms
from Rules import set_rules
from Dungeons import create_dungeons
from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dungeons_restrictive, ensure_good_items
from Fill import dungeon_tracking
from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops, fill_specific_items, create_farm_locations, shuffle_event_items, follower_pickups
from UnderworldGlitchRules import connect_hmg_entrances_regions, create_hmg_entrances_regions
from Utils import output_path, parse_player_names
from source.item.District import init_districts
from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config, verify_item_pool_config
from source.overworld.EntranceShuffle2 import link_entrances_new
from source.tools.BPS import create_bps_from_data
from source.classes.BabelFish import BabelFish
from source.classes.CustomSettings import CustomSettings
from source.enemizer.DamageTables import DamageTable
from source.enemizer.Enemizer import randomize_enemies
from source.item.District import init_districts
from source.item.FillUtil import (
create_item_pool_config,
district_item_pool_config,
massage_item_pool,
verify_item_pool_config,
)
from source.overworld.EntranceShuffle2 import link_entrances_new
from source.rom.DataTables import init_data_tables
version_number = '1.5.0'
version_branch = '-u'
__version__ = f'{version_number}{version_branch}'
from source.classes.BabelFish import BabelFish
from source.tools.BPS import create_bps_from_data
from UnderworldGlitchRules import (
connect_hmg_entrances_regions,
create_hmg_entrances_regions,
)
from Utils import output_path, parse_player_names
from Versions import DRVersion, GKVersion, ORVersion
class EnemizerError(RuntimeError):
@@ -80,7 +129,7 @@ def random_ganon_item(sword_mode):
def main(args, seed=None, fish=None):
check_python_version()
if args.print_template_yaml:
return export_yaml(args, fish)
@@ -122,16 +171,14 @@ def main(args, seed=None, fish=None):
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
world.finish_init()
from OverworldShuffle import __version__ as ORVersion
logger.info(
world.fish.translate("cli","cli","app.title") + "\n",
ORVersion,
"%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 ''
world.fish.translate("cli","cli","app.title") + "\n",
GKVersion,
"%s (%s)" % (world.seed, str(args.outputname)) if str(args.outputname).startswith('M') else world.seed,
)
for k,v in {"DR":__version__,"OR":ORVersion}.items():
logger.info((k + ' Version:').ljust(16) + '%s' % v)
for k,v in {"GK": GKVersion, "OR": ORVersion, "DR": DRVersion}.items():
logger.info((k + ' Version:').ljust(16) + '%s' % v)
parsed_names = parse_player_names(args.names, world.players, args.teams)
world.teams = len(parsed_names)
@@ -142,7 +189,7 @@ def main(args, seed=None, fish=None):
world.player_names[player].append(name)
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):
world.difficulty_requirements[player] = difficulties[world.difficulty[player]]
@@ -421,15 +468,13 @@ def export_yaml(args, fish):
if args.seed and int(args.seed) > 0:
world.seed = int(args.seed)
from OverworldShuffle import __version__ as ORVersion
logger.info(
world.fish.translate("cli","cli","app.title") + "\n",
ORVersion,
GKVersion,
"(%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)
for player in range(1, world.players + 1):
@@ -470,12 +515,16 @@ def init_world(args, fish):
world.keyshuffle = args.keyshuffle.copy()
world.bigkeyshuffle = args.bigkeyshuffle.copy()
world.prizeshuffle = args.prizeshuffle.copy()
world.showloot = args.showloot.copy()
world.showmap = args.showmap.copy()
world.bombbag = args.bombbag.copy()
world.flute_mode = args.flute_mode.copy()
world.bow_mode = args.bow_mode.copy()
world.crystals_ganon_orig = args.crystals_ganon.copy()
world.crystals_gt_orig = args.crystals_gt.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.owKeepSimilar = args.ow_keepsimilar.copy()
world.owWhirlpoolShuffle = args.ow_whirlpool.copy()
@@ -523,7 +572,7 @@ def init_world(args, fish):
world.money_balance = args.money_balance.copy()
# 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 setting in in_progress_settings:
if world.customizer and world.customizer.has_setting(player, setting):
@@ -785,12 +834,16 @@ def copy_world(world):
ret.keyshuffle = world.keyshuffle.copy()
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
ret.prizeshuffle = world.prizeshuffle.copy()
ret.showloot = world.showloot.copy()
ret.showmap = world.showmap.copy()
ret.bombbag = world.bombbag.copy()
ret.flute_mode = world.flute_mode.copy()
ret.bow_mode = world.bow_mode.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_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.crystals_ganon_orig = world.crystals_ganon_orig.copy()
ret.crystals_gt_orig = world.crystals_gt_orig.copy()
@@ -1012,12 +1065,16 @@ def copy_world_premature(world, player, create_flute_exits=True):
ret.keyshuffle = world.keyshuffle.copy()
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
ret.prizeshuffle = world.prizeshuffle.copy()
ret.showloot = world.showloot.copy()
ret.showmap = world.showmap.copy()
ret.bombbag = world.bombbag.copy()
ret.flute_mode = world.flute_mode.copy()
ret.bow_mode = world.bow_mode.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_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.crystals_ganon_orig = world.crystals_ganon_orig.copy()
ret.crystals_gt_orig = world.crystals_gt_orig.copy()
+6 -5
View File
@@ -1,19 +1,20 @@
import aioconsole
import argparse
import asyncio
import colorama
import json
import logging
import shlex
import urllib.parse
import aioconsole
import colorama
import websockets
from BaseClasses import PotItem, PotFlags, LocationType
import Items
import Regions
import PotShuffle
import Regions
import source.dungeon.EnemyList as EnemyList
import source.rom.DataTables as DataTables
from BaseClasses import LocationType, PotFlags, PotItem
class ReceivedItem:
@@ -975,8 +976,8 @@ async def track_locations(ctx : Context, roomid, roomdata):
ow_unchecked[location] = (screenid, 0x40)
ow_begin = min(ow_begin, screenid)
ow_end = max(ow_end, screenid + 1)
from Regions import bonk_prize_table
from OWEdges import OWTileRegions
from Regions import bonk_prize_table
for location, (_, flag, _, _, region_name, _) in bonk_prize_table.items():
if location not in ctx.locations_checked:
if region_name in OWTileRegions:
+11 -5
View File
@@ -1,4 +1,3 @@
import aioconsole
import argparse
import asyncio
import functools
@@ -8,16 +7,23 @@ import re
import shlex
import ssl
import urllib.request
import websockets
import zlib
from BaseClasses import PotItem, PotFlags
import aioconsole
import websockets
import Items
import Regions
import PotShuffle
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
import Regions
import source.dungeon.EnemyList as EnemyList
import source.rom.DataTables as DataTables
from BaseClasses import PotFlags, PotItem
from MultiClient import (
ReceivedItem,
get_item_name_from_id,
get_location_name_from_address,
)
class Client:
def __init__(self, socket):
+4 -4
View File
@@ -1,13 +1,13 @@
import argparse
import logging
import RaceRandom as random
from yaml.constructor import SafeConstructor
import RaceRandom as random
from DungeonRandomizer import parse_cli
from Main import main as DRMain
from source.classes.BabelFish import BabelFish
from yaml.constructor import SafeConstructor
from source.tools.MysteryUtils import roll_settings, get_weights
from source.tools.MysteryUtils import get_weights, roll_settings
def add_bool(self, node):
+3 -1
View File
@@ -1,8 +1,10 @@
from BaseClasses import OWEdge, Direction, Terrain, WorldType, PolSlot
from enum import Enum, unique
from BaseClasses import Direction, OWEdge, PolSlot, Terrain, WorldType
from Utils import bidict
@unique
class OpenStd(Enum):
Open = 0
+34 -14
View File
@@ -1,19 +1,32 @@
import RaceRandom as random, logging, copy
import copy
import logging
from collections import OrderedDict, defaultdict
import RaceRandom as random
from BaseClasses import (
Direction,
Entrance,
OWEdge,
PolSlot,
RegionType,
Terrain,
WorldType,
)
from DungeonGenerator import GenerationException
from BaseClasses import OWEdge, WorldType, RegionType, Direction, Terrain, PolSlot, Entrance
from OverworldGlitchRules import create_owg_connections
from OWEdges import (
IsParallel,
OpenStd,
OWEdgeGroups,
OWEdgeGroupsTerrain,
OWExitTypes,
OWTileRegions,
parallel_links,
)
from Regions import mark_light_dark_world_regions
from source.overworld.EntranceShuffle2 import connect_simple
from OWEdges import OWTileRegions, OWEdgeGroups, OWEdgeGroupsTerrain, OWExitTypes, OpenStd, parallel_links, IsParallel
from OverworldGlitchRules import create_owg_connections
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
def link_overworld(world, player):
@@ -1370,8 +1383,8 @@ def update_world_regions(world, player):
world.get_region(name, player).type = RegionType.LightWorld
def can_reach_smith(world, player):
from Items import ItemFactory
from BaseClasses import CollectionState
from Items import ItemFactory
def explore_region(region_name, region=None):
nonlocal found
@@ -1426,7 +1439,7 @@ def can_reach_smith(world, player):
def build_sectors(world, player):
from Main import copy_world_premature
from OWEdges import OWTileRegions
# perform accessibility check on duplicate world
for p in range(1, world.players + 1):
world.key_logic[p] = {}
@@ -1478,8 +1491,8 @@ def build_sectors(world, player):
def build_accessible_region_list(world, start_region, player, build_copy_world=False, cross_world=False, region_rules=True, ignore_ledges=False, restrictive_follower=False):
from BaseClasses import CollectionState
from Main import copy_world_premature
from Items import ItemFactory
from Main import copy_world_premature
from Utils import stack_size3a
def explore_region(region_name, region=None):
@@ -1552,7 +1565,14 @@ def validate_layout(world, player):
}
# TODO: Find a better source for the below lists, original sourced was deprecated
from source.overworld.EntranceData import default_dungeon_connections, default_connector_connections, default_item_connections, default_shop_connections, default_drop_connections, default_dropexit_connections
from source.overworld.EntranceData import (
default_connector_connections,
default_drop_connections,
default_dropexit_connections,
default_dungeon_connections,
default_item_connections,
default_shop_connections,
)
dungeon_entrances = list(zip(*default_dungeon_connections + [('Ganons Tower', '')]))[0]
connector_entrances = list(zip(*default_connector_connections))[0]
+21 -10
View File
@@ -3,22 +3,33 @@ import argparse
import hashlib
import logging
import os
import RaceRandom as random
import time
import sys
import time
import RaceRandom as random
from BaseClasses import World
from Regions import create_regions
from OverworldShuffle import link_overworld
from source.overworld.EntranceShuffle2 import link_entrances_new, connect_entrance, connect_two_way, connect_exit
from Rom import patch_rom, LocalRom, write_string_to_rom, apply_rom_settings, get_sprite_from_name
from Rules import set_rules
from Dungeons import create_dungeons
from Items import ItemFactory
from ItemList import difficulties
from Items import ItemFactory
from Main import create_playthrough
from OverworldShuffle import link_overworld
from Regions import create_regions
from Rom import (
LocalRom,
apply_rom_settings,
get_sprite_from_name,
patch_rom,
write_string_to_rom,
)
from Rules import set_rules
from source.overworld.EntranceShuffle2 import (
connect_entrance,
connect_exit,
connect_two_way,
link_entrances_new,
)
__version__ = '0.2-dev'
PlandoVersion = '0.2-dev'
def main(args):
start_time = time.process_time()
@@ -36,7 +47,7 @@ def main(args):
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]]
+3 -5
View File
@@ -1,11 +1,9 @@
import RaceRandom as random
from collections import defaultdict
from BaseClasses import PotItem, Pot, PotFlags, CrystalBarrier, LocationType, RegionType
from Utils import int16_as_bytes, pc_to_snes, snes_to_pc
import RaceRandom as random
from BaseClasses import CrystalBarrier, LocationType, Pot, PotFlags, PotItem, RegionType
from source.dungeon.RoomObject import RoomObject, Shuffled_Pot
from Utils import int16_as_bytes, pc_to_snes, snes_to_pc
movable_switch_rooms = defaultdict(lambda: [],
{'PoD Stalfos Basement': ['PoD Basement Ledge'],
+15 -4
View File
@@ -1,9 +1,20 @@
import collections
from Items import ItemFactory
from BaseClasses import Region, Location, Entrance, RegionType, Terrain, Shop, ShopType, LocationType, PotItem, PotFlags
from PotShuffle import key_drop_data, vanilla_pots, choose_pots, PotSecretTable
from source.dungeon.EnemyList import setup_enemy_locations, enemy_names
from BaseClasses import (
Entrance,
Location,
LocationType,
PotFlags,
PotItem,
Region,
RegionType,
Shop,
ShopType,
Terrain,
)
from Items import ItemFactory
from PotShuffle import PotSecretTable, choose_pots, key_drop_data, vanilla_pots
from source.dungeon.EnemyList import enemy_names, setup_enemy_locations
def create_regions(world, player):
+178 -80
View File
@@ -1,50 +1,90 @@
import bisect
import collections
import hashlib
import io
import json
import hashlib
import logging
import os
import struct
import sys
import Items
import RaceRandom as random
import struct
import sys
try:
import bps.apply
import bps.io
except ImportError:
raise Exception('Could not load BPS module')
from BaseClasses import ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, LocationType
from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals
from Dungeons import dungeon_music_addresses, dungeon_table
from Regions import location_table, shop_to_location_table, retro_shops
from RoomData import DoorKind
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
from Text import Uncle_texts, Ganon1_texts, Ganon_Phase_3_No_Silvers_texts, Ganon_Phase_3_No_Weakness_texts, TavernMan_texts, Sahasrahla2_texts
from Text import Triforce_texts, Blind_texts, BombShop2_texts, junk_texts
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts
from Text import LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts
from Text import Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Utils import local_path, int16_as_bytes, int32_as_bytes, snes_to_pc
from Items import ItemFactory, prize_item_table
from source.overworld.EntranceData import door_addresses, ow_prize_table
from source.overworld.EntranceShuffle2 import exit_ids
from OverworldShuffle import default_flute_connections, flute_data
from InitialSram import InitialSram
from BaseClasses import (
Door,
DoorType,
Location,
LocationType,
OWEdge,
Region,
RegionType,
ShopType,
)
from DamageTable import DamageTable
from source.classes.SFX import randomize_sfx, randomize_sfxinstruments, randomize_songinstruments
from source.item.FillUtil import valid_pot_items
from DoorShuffle import DROptions, boss_indicator, compass_data, dungeon_portals
from Dungeons import dungeon_music_addresses, dungeon_table
from InitialSram import InitialSram
from Items import ItemFactory, prize_item_table
from OverworldShuffle import default_flute_connections, flute_data
from Regions import location_table, retro_shops, shop_to_location_table
from RoomData import DoorKind
from source.classes.SFX import (
randomize_sfx,
randomize_sfxinstruments,
randomize_songinstruments,
)
from source.dungeon.EnemyList import EnemySprite, setup_enemy_dungeon_tables
from source.dungeon.RoomObject import DoorObject
from source.enemizer.Bossmizer import boss_writes
from source.enemizer.Enemizer import write_enemy_shuffle_settings
from source.item.FillUtil import valid_pot_items
from source.overworld.EntranceData import door_addresses, ow_prize_table
from source.overworld.EntranceShuffle2 import exit_ids
from Text import (
Blacksmiths_texts,
Blind_texts,
BombShop2_texts,
CompressedTextMapper,
Credits,
DeathMountain_texts,
DesertPalace_texts,
FluteBoy_texts,
Ganon1_texts,
Ganon_Phase_3_No_Silvers_texts,
Ganon_Phase_3_No_Weakness_texts,
Kakariko_texts,
KingsReturn_texts,
LinksHouse_texts,
LostWoods_texts,
Lumberjacks_texts,
MagicShop_texts,
MountainTower_texts,
MultiByteTextMapper,
Sahasrahla2_texts,
Sahasrahla_names,
Sanctuary_texts,
SickKid_texts,
TavernMan_texts,
TextTable,
Triforce_texts,
Uncle_texts,
WishingWell_texts,
Zora_texts,
junk_texts,
text_addresses,
)
from Utils import int16_as_bytes, int32_as_bytes, local_path, snes_to_pc
from Versions import DRVersion, GKVersion, ORVersion
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '76dc2d00e5dd5b925ad01574b327d364'
RANDOMIZERBASEHASH = '2647cc28bca3675152576dd1f5ea0bab'
class JsonRom(object):
@@ -162,35 +202,12 @@ class LocalRom(object):
with open(local_path('data/base2current.bps'), 'rb') as stream:
bps.apply.apply_to_bytearrays(bps.io.read_bps(stream), orig_buffer, self.buffer)
self.create_json_patch(orig_buffer)
# verify md5
patchedmd5 = hashlib.md5()
patchedmd5.update(self.buffer)
if RANDOMIZERBASEHASH != patchedmd5.hexdigest():
raise RuntimeError('Provided Base Rom unsuitable for patching. Please provide a JAP(1.0) "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc" rom to use as a base.')
def create_json_patch(self, orig_buffer):
# extend to 2MB
orig_buffer.extend(bytearray([0x00] * (len(self.buffer) - len(orig_buffer))))
i = 0
patches = []
while i < len(self.buffer):
if self.buffer[i] == orig_buffer[i]:
i += 1
continue
patch_start = i
patch_contents = []
while self.buffer[i] != orig_buffer[i]:
patch_contents.append(self.buffer[i])
i += 1
patches.append({patch_start: patch_contents})
with open(local_path('data/base2current.json'), 'w') as fp:
json.dump(patches, fp, separators=(',', ':'))
if not os.getenv("SKIP_BASEROM_CHECK", False):
# verify md5
patchedmd5 = hashlib.md5()
patchedmd5.update(self.buffer)
if RANDOMIZERBASEHASH != patchedmd5.hexdigest():
raise RuntimeError('Provided Base Rom unsuitable for patching. Please provide a JAP(1.0) "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc" rom to use as a base.')
def write_crc(self):
crc = (sum(self.buffer[:0x7FDC] + self.buffer[0x7FE0:]) + 0x01FE) & 0xFFFF
@@ -1210,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])
if world.goal[player] in ['triforcehunt', 'trinity', 'ganonhunt']:
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
@@ -1239,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(0x50563, [0x3F, 0x14]) # 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):
rom.initial_sram.pre_open_pyramid_hole()
rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped
@@ -1285,6 +1301,8 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
# 08: Goal items collected (ie. Triforce Pieces)
# 09: Max collection rate
# 0A: Custom goal
# 0B: Reserved for Bingo
# 0C: All bosses (prize bosses + aga1 + aga2)
def get_goal_bytes(type):
goal_bytes = []
@@ -1339,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]]
elif world.goal[player] in ['ganonhunt']:
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']:
ganon_goal += [0x81, 0x82, 0x06, 0x07, 0x89] # AD and max collection rate
else:
@@ -1440,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']):
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
compass_mode = 0x80 if world.compassshuffle[player] not in ['none', 'nearby'] else 0x00
if world.clock_mode != 'none' or world.dungeon_counters[player] == 'off':
@@ -1586,12 +1663,28 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
# b - Big 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']
rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] not in ['none', 'universal'] else 0x00)
| (0x02 if world.bigkeyshuffle[player] != 'none' else 0x00)
| (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
| (0x10 if world.logic[player] == 'nologic' else 0))) # boss icon
if world.mapshuffle[player] != 'none' or enable_menu_map_check:
dungeon_items_menu |= 0x04
if world.compassshuffle[player] != 'none':
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):
if world.prizeshuffle[player] != 'wild':
@@ -1800,30 +1893,29 @@ def patch_rom(world, rom, player, team, is_mystery=False, rom_header=None):
# set rom name
# 21 bytes
from Main import __version__
from OverworldShuffle import __version__ as ORVersion
if rom_header:
if len(rom_header) > 21:
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.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(0x1CEEF0, bytearray(GKVersion, 'utf8'))
# set player names
for p in range(1, min(world.players, 255) + 1):
@@ -2696,13 +2788,19 @@ def write_strings(rom, world, player, team):
tt['sign_ganon'] = 'Three ways to victory! %s Get to it!' % trinity_crystal_text
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':
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':
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_alt'] = 'You cannot defeat me until you finish your goal!'
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
def get_custom_goal_text(type):
goal_text = world.custom_goals[player][type]['goaltext']
placeholder_count = goal_text.count('%d')
+1
View File
@@ -1,4 +1,5 @@
from enum import Enum, unique
from Tables import door_pair_offset_table
+28 -9
View File
@@ -3,18 +3,28 @@ import logging
from collections import deque
import OverworldGlitchRules
from BaseClasses import CollectionState, RegionType, DoorType, Entrance, CrystalBarrier, KeyRuleType, LocationType, Terrain
from BaseClasses import PotFlags
from BaseClasses import (
CollectionState,
CrystalBarrier,
DoorType,
Entrance,
KeyRuleType,
LocationType,
PotFlags,
RegionType,
Terrain,
)
from Dungeons import dungeon_table
from RoomData import DoorKind
from OWEdges import OWExitTypes
from OverworldGlitchRules import overworld_glitches_rules
from UnderworldGlitchRules import underworld_glitches_rules
from source.logic.Rule import RuleFactory
from OWEdges import OWExitTypes
from RoomData import DoorKind
from source.dungeon.EnemyList import EnemySprite, Sprite
from source.enemizer.EnemyLogic import special_rules_check, special_rules_for_region, defeat_rule_single
from source.enemizer.EnemyLogic import defeat_rule_multiple, and_rule as and_rule_new, or_rule as or_rule_new
from source.enemizer.EnemyLogic import and_rule as and_rule_new
from source.enemizer.EnemyLogic import defeat_rule_multiple, defeat_rule_single
from source.enemizer.EnemyLogic import or_rule as or_rule_new
from source.enemizer.EnemyLogic import special_rules_check, special_rules_for_region
from source.logic.Rule import RuleFactory
from UnderworldGlitchRules import underworld_glitches_rules
def set_rules(world, player):
@@ -80,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))
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]))
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':
add_rule(world.get_location('Ganon', player), lambda state: state.everything(player))
+3 -3
View File
@@ -1,8 +1,8 @@
import argparse
import concurrent.futures
import multiprocessing
import subprocess
import sys
import multiprocessing
import concurrent.futures
import argparse
from collections import OrderedDict
cpu_threads = multiprocessing.cpu_count()
+4 -4
View File
@@ -1,10 +1,10 @@
import argparse
import concurrent.futures
import csv
import multiprocessing
import subprocess
import sys
import multiprocessing
import concurrent.futures
import argparse
from collections import OrderedDict
import csv
cpu_threads = multiprocessing.cpu_count()
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
+4 -3
View File
@@ -1,8 +1,9 @@
# -*- coding: UTF-8 -*-
from collections import OrderedDict
import logging
import re
import warnings
from collections import OrderedDict
warnings.filterwarnings("ignore", category=SyntaxWarning)
text_addresses = {'Pedestal': (0x180300, 256),
@@ -1780,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['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_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['hylian_text_2'] = CompressedTextMapper.convert("%%^= %==%\n ^ =%^=\n==%= ^^%^")
text['desert_entry_translated'] = CompressedTextMapper.convert("Kneel before this stone, and magic will move around you.")
@@ -2014,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_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['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['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.")
+3 -2
View File
@@ -1,7 +1,8 @@
import functools
from BaseClasses import Entrance, DoorType, Door
from DoorShuffle import connect_simple_door
import Rules
from BaseClasses import Door, DoorType, Entrance
from DoorShuffle import connect_simple_door
kikiskip_spots = [
("Kiki Skip", "Spectacle Rock Cave (Bottom)", "Palace of Darkness Portal")
+15 -33
View File
@@ -1,19 +1,20 @@
#!/usr/bin/env python3
import fileinput
import os
import re
import subprocess
import sys
import urllib.parse
import urllib.request
import xml.etree.ElementTree as ET
from collections import defaultdict
from math import factorial
from hashlib import md5
from itertools import count
import fileinput
import urllib.request
import urllib.parse
import yaml
from math import factorial
from pathlib import Path
import yaml
def int16_as_bytes(value):
value = value & 0xFFFF
@@ -102,31 +103,13 @@ def close_console():
pass
def make_new_base2current(old_rom='Zelda no Densetsu - Kamigami no Triforce (Japan).sfc', new_rom='working.sfc'):
from collections import OrderedDict
import json
import hashlib
with open(old_rom, 'rb') as stream:
old_rom_data = bytearray(stream.read())
def get_new_romhash(new_rom='working.sfc'):
with open(new_rom, 'rb') as stream:
new_rom_data = bytearray(stream.read())
# extend to 2 mb
old_rom_data.extend(bytearray([0x00] * (2097152 - len(old_rom_data))))
out_data = OrderedDict()
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 = md5()
basemd5.update(new_rom_data)
return "New Rom Hash: " + basemd5.hexdigest()
return basemd5.hexdigest()
def kth_combination(k, l, r):
@@ -768,18 +751,18 @@ class bidict(dict):
super(bidict, self).__init__(*args, **kwargs)
self.inverse = {}
for key, value in self.items():
self.inverse.setdefault(value,[]).append(key)
self.inverse.setdefault(value,[]).append(key)
def __setitem__(self, key, value):
if key in self:
self.inverse[self[key]].remove(key)
self.inverse[self[key]].remove(key)
super(bidict, self).__setitem__(key, value)
self.inverse.setdefault(value,[]).append(key)
self.inverse.setdefault(value,[]).append(key)
def __delitem__(self, key):
value = self[key]
self.inverse.setdefault(value,[]).remove(key)
if value in self.inverse and not self.inverse[value]:
if value in self.inverse and not self.inverse[value]:
del self.inverse[value]
super(bidict, self).__delitem__(key)
@@ -787,12 +770,11 @@ class bidict(dict):
class HexInt(int): pass
def hex_representer(dumper, data):
import yaml
return yaml.ScalarNode('tag:yaml.org,2002:int', f"{data:#0{4}x}")
if __name__ == '__main__':
print(make_new_base2current())
print("New Rom Hash:", get_new_romhash())
# read_entrance_data(old_rom=sys.argv[1])
# room_palette_data(old_rom=sys.argv[1])
# extract_data_from_us_rom(sys.argv[1])
+3
View File
@@ -0,0 +1,3 @@
GKVersion = '1.0.0'
ORVersion = '0.6.1.7'
DRVersion = '1.5.0-u'
+2 -2
View File
@@ -1,9 +1,9 @@
"""collections_extended contains a few extra basic data structures."""
from ._compat import Collection
from .bags import bag, frozenbag
from .setlists import setlist, frozensetlist
from .bijection import bijection
from .range_map import RangeMap, MappedRange
from .range_map import MappedRange, RangeMap
from .setlists import frozensetlist, setlist
__version__ = '1.0.2'
+1 -1
View File
@@ -12,7 +12,7 @@ else:
if sys.version_info < (3, 6):
from collections import Sized, Iterable, Container
from collections import Container, Iterable, Sized
def _check_methods(C, *methods):
mro = C.__mro__
+1 -1
View File
@@ -1,7 +1,7 @@
"""Bag class definitions."""
import heapq
from collections import Hashable, MutableSet, Set
from operator import itemgetter
from collections import Set, MutableSet, Hashable
from . import _compat
+1 -1
View File
@@ -1,6 +1,6 @@
"""Class definition for bijection."""
from collections import MutableMapping, Mapping
from collections import Mapping, MutableMapping
class bijection(MutableMapping):
+1 -2
View File
@@ -1,7 +1,6 @@
"""RangeMap class definition."""
from bisect import bisect_left, bisect_right
from collections import namedtuple, Mapping, MappingView, Set
from collections import Mapping, MappingView, Set, namedtuple
# Used to mark unmapped ranges
_empty = object()
+6 -7
View File
@@ -1,13 +1,12 @@
"""Setlist class definitions."""
import random as random_
from collections import (
Sequence,
Set,
MutableSequence,
MutableSet,
Hashable,
)
Hashable,
MutableSequence,
MutableSet,
Sequence,
Set,
)
from . import _util
-5
View File
@@ -1,5 +0,0 @@
from OverworldShuffle import __version__ as OWVersion
import os
with(open(os.path.join("resources","app","meta","manifests","app_version.txt"),"w+")) as f:
f.write(OWVersion)
+1 -1
View File
@@ -1,4 +1,4 @@
import sys
import os
import sys
sys.path.append(os.path.join(sys._MEIPASS, "ext"))
Binary file not shown.
+6 -3
View File
@@ -1,7 +1,7 @@
[project]
name = "ALttPOverworldRandomizer"
version = "0.1.0"
description = "Add your description here"
name = "alttpr-python"
version = "1.0.0"
description = "Python ALttP Randomizer"
readme = "README.md"
requires-python = ">=3.7"
dependencies = [
@@ -14,3 +14,6 @@ dependencies = [
"pyyaml>=6.0.1",
"websockets>=11.0.3",
]
[tool.isort]
profile = "black"
+37
View File
@@ -72,6 +72,7 @@
"trinity",
"crystals",
"ganonhunt",
"bosshunt",
"completionist",
"sanctuary"
]
@@ -299,6 +300,27 @@
"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": {
"choices": [
"7",
@@ -431,6 +453,21 @@
"wild"
]
},
"showloot": {
"choices": [
"never",
"presence",
"compass",
"always"
]
},
"showmap": {
"choices": [
"visited",
"map",
"always"
]
},
"keysanity": {
"action": "store_true",
"type": "bool",
+1 -1
View File
@@ -2,7 +2,7 @@
"cli": {
"yes": "Yes",
"no": "No",
"app.title": "ALttP Overworld Randomizer Version %s : --seed %s --code %s",
"app.title": "ALttP GwaaKiwi Randomizer Version %s : --seed %s",
"version": "Version",
"seed": "Seed",
"player": "Player",
+3 -2
View File
@@ -1,6 +1,7 @@
import os # for env vars
import stat # file statistics
import os # for env vars
import stat # file statistics
import sys # default system info
try:
import distro
except ModuleNotFoundError as e:
+5 -3
View File
@@ -1,8 +1,10 @@
import common
import argparse
import os
import urllib.request, ssl
import subprocess # do stuff at the shell level
import ssl
import subprocess # do stuff at the shell level
import urllib.request
import common
env = common.prepare_env()
+6 -6
View File
@@ -1,11 +1,11 @@
# import modules
import common # app common functions
import json # json manipulation
import os # for os data, filesystem manipulation
import subprocess # for running shell commands
import sys # for system commands
import traceback # for errors
import json # json manipulation
import os # for os data, filesystem manipulation
import subprocess # for running shell commands
import sys # for system commands
import traceback # for errors
import common # app common functions
# get env
env = common.prepare_env() # get environment variables
+5 -4
View File
@@ -1,9 +1,10 @@
import common
import os # for env vars
import sys # for path
import urllib.request # for downloads
import os # for env vars
import sys # for path
import urllib.request # for downloads
from shutil import unpack_archive
import common
# only do stuff if we don't have a UPX folder
if not os.path.isdir(os.path.join(".","upx")):
+2 -1
View File
@@ -1,5 +1,6 @@
import subprocess # do stuff at the shell level
import os
import subprocess # do stuff at the shell level
def git_clean(clean_ignored=True, clean_user=False):
excludes = [
+3 -2
View File
@@ -1,8 +1,9 @@
import common
import argparse
import os
import platform
import subprocess # do stuff at the shell level
import subprocess # do stuff at the shell level
import common
env = common.prepare_env()
+2 -1
View File
@@ -6,9 +6,10 @@ import json
import os
import ssl
import urllib.request
import yaml
from json.decoder import JSONDecodeError
import yaml
allACTIONS = {}
listACTIONS = []
+3 -3
View File
@@ -1,8 +1,8 @@
import install
import get_get_pip
import argparse
import get_get_pip
import install
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument('--py', default=0)
parser.add_argument('--user', default=False, action="store_true")
+1
View File
@@ -1,6 +1,7 @@
import os
import sys
def get_py_path():
user_paths = os.environ["PATH"].split(os.pathsep)
(python,py) = ("","")
+3 -2
View File
@@ -1,6 +1,7 @@
import os # for env vars
from shutil import copy # file manipulation
import common
import os # for env vars
from shutil import copy # file manipulation
env = common.prepare_env()
+3 -2
View File
@@ -2,11 +2,12 @@
Locate and prepare binary builds
"""
# import distutils.dir_util # for copying trees
import os # for env vars
import os # for env vars
from shutil import move # file manipulation
# import stat # for file stats
# import subprocess # do stuff at the shell level
import common
from shutil import move # file manipulation
env = common.prepare_env()
+6 -5
View File
@@ -1,10 +1,11 @@
import distutils.dir_util # for copying trees
import os # for env vars
import stat # for file stats
import subprocess # do stuff at the shell level
import distutils.dir_util # for copying trees
import os # for env vars
import stat # for file stats
import subprocess # do stuff at the shell level
from shutil import copy, make_archive, move, rmtree # file manipulation
import common
from git_clean import git_clean
from shutil import copy, make_archive, move, rmtree # file manipulation
env = common.prepare_env() # get env vars
+1
View File
@@ -2,6 +2,7 @@ import json
import locale
import os
class BabelFish():
def __init__(self,subpath=["resources","app","meta"],lang=None):
localization_string = locale.getdefaultlocale()[0] #get set localization
+8 -7
View File
@@ -1,17 +1,18 @@
import os
import urllib.request
import urllib.parse
import yaml
from typing import Any
from yaml.representer import Representer
from Utils import HexInt, hex_representer
import urllib.request
from collections import defaultdict
from pathlib import Path
from typing import Any
import yaml
from yaml.representer import Representer
import RaceRandom as random
from BaseClasses import LocationType, DoorType
from BaseClasses import DoorType, LocationType
from OverworldShuffle import default_flute_connections, flute_data
from source.tools.MysteryUtils import roll_settings, get_weights
from source.tools.MysteryUtils import get_weights, roll_settings
from Utils import HexInt, hex_representer
class CustomSettings(object):
+16 -1
View File
@@ -1,5 +1,20 @@
from tkinter import Button, Canvas, Label, LabelFrame, Frame, PhotoImage, Scrollbar, Toplevel, LEFT, BOTTOM, X, RIGHT, TOP
import os
from tkinter import (
BOTTOM,
LEFT,
RIGHT,
TOP,
Button,
Canvas,
Frame,
Label,
LabelFrame,
PhotoImage,
Scrollbar,
Toplevel,
X,
)
from GuiUtils import ToolTips, set_icon
from Utils import local_path
+2 -1
View File
@@ -1,5 +1,6 @@
from enum import IntEnum
import random
from enum import IntEnum
from Utils import int16_as_bytes, snes_to_pc
+25 -5
View File
@@ -1,16 +1,36 @@
from tkinter import filedialog, messagebox, Button, Canvas, Label, LabelFrame, Frame, PhotoImage, Scrollbar, Toplevel, ALL, LEFT, BOTTOM, X, RIGHT, TOP, EW, NS
from glob import glob
import json
import os
import random
import shutil
import ssl
import webbrowser
from glob import glob
from tkinter import (
ALL,
BOTTOM,
EW,
LEFT,
NS,
RIGHT,
TOP,
Button,
Canvas,
Frame,
Label,
LabelFrame,
PhotoImage,
Scrollbar,
Toplevel,
X,
filedialog,
messagebox,
)
from urllib.parse import urlparse
from urllib.request import urlopen
import webbrowser
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
from GuiUtils import BackgroundTaskProgress, ToolTips, set_icon
from Rom import Sprite
from Utils import is_bundled, local_path, output_path, open_file
from Utils import is_bundled, local_path, open_file, output_path
class SpriteSelector(object):
-17
View File
@@ -1,17 +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()
+44 -45
View File
@@ -1,60 +1,59 @@
import platform, sys, os, subprocess
import os
import platform
import subprocess
import sys
try:
import pkg_resources
except ModuleNotFoundError as e:
pass
import datetime
from Main import __version__
DR_VERSION = __version__
from Versions import DRVersion, GKVersion, ORVersion
from OverworldShuffle import __version__
OWR_VERSION = __version__
PROJECT_NAME = "ALttP Overworld Randomizer"
def diagpad(str):
return str.ljust(len(f"{PROJECT_NAME} Version") + 5,'.')
return str.ljust(40, '.')
def output():
lines = [
f"{PROJECT_NAME} Diagnostics",
"=================================",
diagpad("UTC Time") + str(datetime.datetime.now(datetime.UTC))[:19],
diagpad("ALttP Door Randomizer Version") + DR_VERSION,
diagpad(f"{PROJECT_NAME} Version") + OWR_VERSION,
diagpad("Python Version") + platform.python_version()
]
lines.append(diagpad("OS Version") + "%s %s" % (platform.system(), platform.release()))
if hasattr(sys, "executable"):
lines.append(diagpad("Executable") + sys.executable)
lines.append(diagpad("Build Date") + platform.python_build()[1])
lines.append(diagpad("Compiler") + platform.python_compiler())
if hasattr(sys, "api_version"):
lines.append(diagpad("Python API") + str(sys.api_version))
if hasattr(os, "sep"):
lines.append(diagpad("Filepath Separator") + os.sep)
if hasattr(os, "pathsep"):
lines.append(diagpad("Path Env Separator") + os.pathsep)
lines.append("")
lines.append("Packages")
lines.append("--------")
'''
#this breaks when run from the .exe
reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze'])
installed_packages = [r.decode() for r in reqs.split()]
for pkg in installed_packages:
pkg = pkg.split("==")
lines.append(diagpad(pkg[0]) + pkg[1])
'''
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.sort()
for pkg in installed_packages:
pkg = pkg.split(' ')
lines.append(diagpad(pkg[0]) + pkg[1])
lines = [
"ALttP GwaaKiwi Randomizer Diagnostics",
"=====================================",
diagpad("UTC Time") + str(datetime.datetime.now(datetime.UTC))[:19],
diagpad("ALttP Door Randomizer Version") + DRVersion,
diagpad("ALttP Overworld Randomizer Version") + ORVersion,
diagpad("ALttP GwaaKiwi Randomizer Version") + GKVersion,
diagpad("Python Version") + platform.python_version(),
]
lines.append(diagpad("OS Version") + "%s %s" % (platform.system(), platform.release()))
if hasattr(sys, "executable"):
lines.append(diagpad("Executable") + sys.executable)
lines.append(diagpad("Build Date") + platform.python_build()[1])
lines.append(diagpad("Compiler") + platform.python_compiler())
if hasattr(sys, "api_version"):
lines.append(diagpad("Python API") + str(sys.api_version))
if hasattr(os, "sep"):
lines.append(diagpad("Filepath Separator") + os.sep)
if hasattr(os, "pathsep"):
lines.append(diagpad("Path Env Separator") + os.pathsep)
lines.append("")
return lines
lines.append("Packages")
lines.append("--------")
reqs = subprocess.check_output([sys.executable, '-m', 'pip', 'freeze'])
installed_packages = [r.decode() for r in reqs.split()]
for pkg in installed_packages:
pkg = pkg.split("==")
lines.append(diagpad(pkg[0]) + pkg[1])
installed_packages = []
installed_packages = [str(d) for d in pkg_resources.working_set]
installed_packages.sort()
for pkg in installed_packages:
pkg = pkg.split(' ')
lines.append(diagpad(pkg[0]) + pkg[1])
return lines
if __name__ == "__main__":
raise AssertionError(f"Called main() on utility library {__file__}")
+11 -4
View File
@@ -1,11 +1,18 @@
import RaceRandom as random
import collections
import logging
import time
from BaseClasses import CrystalBarrier, DoorType, Hook, RegionType, Sector
from BaseClasses import hook_from_door, flooded_keys
from Regions import location_events, flooded_keys_reverse
import RaceRandom as random
from BaseClasses import (
CrystalBarrier,
DoorType,
Hook,
RegionType,
Sector,
flooded_keys,
hook_from_door,
)
from Regions import flooded_keys_reverse, location_events
def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
+3 -3
View File
@@ -1,5 +1,5 @@
from collections import defaultdict, deque
import typing
from collections import defaultdict, deque
import yaml
from yaml.representer import Representer
@@ -13,9 +13,9 @@ import RaceRandom as random
from BaseClasses import Location, LocationType, RegionType
from Items import ItemFactory
from PotShuffle import key_drop_special
from Utils import snes_to_pc, pc_to_snes, int16_as_bytes
from source.overworld.EntranceData import door_addresses
from Utils import int16_as_bytes, pc_to_snes, snes_to_pc
class EnemyStats:
def __init__(self, sprite, static, drop_flag=False, prize_pack: typing.Union[tuple, int] = 0,
+1 -2
View File
@@ -3,9 +3,8 @@ try:
except ImportError:
from enum import IntFlag as FastEnum
from RoomData import DoorKind, Position
from source.dungeon.RoomObject import RoomObject, DoorObject
from source.dungeon.RoomObject import DoorObject, RoomObject
class Room:
+3 -4
View File
@@ -1,10 +1,9 @@
import RaceRandom as random
from Utils import snes_to_pc
from source.dungeon.EnemyList import EnemySprite, SpriteType, Sprite
from source.dungeon.RoomList import boss_rooms, gt_boss_room, Room0006
from source.dungeon.EnemyList import EnemySprite, Sprite, SpriteType
from source.dungeon.RoomList import Room0006, boss_rooms, gt_boss_room
from source.dungeon.RoomObject import RoomObject
from source.enemizer.SpriteSheets import required_boss_sheets
from Utils import snes_to_pc
def get_dungeon_boss_room(dungeon_name, level):
+9 -5
View File
@@ -1,13 +1,17 @@
import RaceRandom as random
from collections import defaultdict
from Utils import snes_to_pc
from source.dungeon.EnemyList import SpriteType, EnemySprite, sprite_translation
import RaceRandom as random
from source.dungeon.EnemyList import EnemySprite, SpriteType, sprite_translation
from source.dungeon.RoomList import Room010C
from source.enemizer.SpecialEnemyModes import set_mimics, write_mimic_changes
from source.enemizer.SpriteSheets import sub_group_choices, sheets_with_free_gfx
from source.enemizer.SpriteSheets import randomize_underworld_sprite_sheets, randomize_overworld_sprite_sheets
from source.enemizer.SpriteSheets import (
randomize_overworld_sprite_sheets,
randomize_underworld_sprite_sheets,
sheets_with_free_gfx,
sub_group_choices,
)
from source.enemizer.TilePattern import tile_patterns
from Utils import snes_to_pc
shutter_sprites = {
0xb8: {0, 1, 2, 3, 4, 5}, 0xb: {4, 5, 6, 7, 8, 9}, 0x1b: {3, 4, 5}, 0x4b: {0, 3, 4}, 0x4: {9, 13, 14},
+12 -6
View File
@@ -1,12 +1,18 @@
from types import SimpleNamespace
from collections import Counter, defaultdict
from types import SimpleNamespace
from source.dungeon.EnemyList import enemy_names, SpriteType
from source.enemizer.Enemizer import randomize_underworld_rooms, randomize_overworld_enemies
from source.enemizer.SpriteSheets import randomize_underworld_sprite_sheets, randomize_overworld_sprite_sheets
from source.rom.DataTables import init_data_tables
from source.enemizer.DamageTables import DamageTable
import RaceRandom as random
from source.dungeon.EnemyList import SpriteType, enemy_names
from source.enemizer.DamageTables import DamageTable
from source.enemizer.Enemizer import (
randomize_overworld_enemies,
randomize_underworld_rooms,
)
from source.enemizer.SpriteSheets import (
randomize_overworld_sprite_sheets,
randomize_underworld_sprite_sheets,
)
from source.rom.DataTables import init_data_tables
def calculate_odds():
+1 -2
View File
@@ -2,9 +2,8 @@ import math
from collections import defaultdict
import RaceRandom as random
from source.logic.Rule import RuleFactory
from source.dungeon.EnemyList import EnemySprite
from source.logic.Rule import RuleFactory
# these are for drops only
+2 -1
View File
@@ -1,4 +1,5 @@
from source.dungeon.EnemyList import Sprite, EnemySprite
from source.dungeon.EnemyList import EnemySprite, Sprite
vanilla_sprites_ow = {}
+6 -2
View File
@@ -1,8 +1,12 @@
import RaceRandom as random
from source.dungeon.EnemyList import (
EnemySprite,
SpriteType,
enemy_names,
sprite_translation,
)
from Utils import snes_to_pc
from source.dungeon.EnemyList import EnemySprite, SpriteType, sprite_translation, enemy_names
change_idx_1 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 13, 16, 18, 19, 20, 21, 22, 23, 24, 25, 26, 28, 31, 32, 35, 38, 65, 66, 67, 68, 70, 71, 76, 77, 78, 81, 83, 89, 91, 92, 94, 97, 100, 101, 102, 103, 104, 105, 106, 107]
+8 -6
View File
@@ -1,8 +1,14 @@
import logging
from collections import defaultdict
import RaceRandom as random
from source.dungeon.EnemyList import EnemySprite, SpriteType, enemy_names, sprite_translation, overlord_names
import RaceRandom as random
from source.dungeon.EnemyList import (
EnemySprite,
SpriteType,
enemy_names,
overlord_names,
sprite_translation,
)
from source.dungeon.RoomConstants import *
@@ -465,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, 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,
+2 -2
View File
@@ -1,6 +1,6 @@
import os
import json
import codecs
import json
import os
if __name__ == '__main__':
directory = './EnemizerCLI.Core/tiles'
+22 -6
View File
@@ -1,15 +1,31 @@
from tkinter import ttk, filedialog, messagebox, StringVar, Button, Entry, Frame, Label, E, W, LEFT, RIGHT, X, BOTTOM
from AdjusterMain import adjust, patch
from argparse import Namespace
from source.classes.SpriteSelector import SpriteSelector
from source.classes.ItemGfxSelector import ItemGfxSelector
import source.gui.widgets as widgets
import json
import logging
import os
from argparse import Namespace
from tkinter import (
BOTTOM,
LEFT,
RIGHT,
Button,
E,
Entry,
Frame,
Label,
StringVar,
W,
X,
filedialog,
messagebox,
ttk,
)
import source.gui.widgets as widgets
from AdjusterMain import adjust, patch
from source.classes.ItemGfxSelector import ItemGfxSelector
from source.classes.SpriteSelector import SpriteSelector
from Utils import output_path
def adjust_page(top, parent, settings):
# Adjust page
self = ttk.Frame(parent)
+19 -7
View File
@@ -1,17 +1,29 @@
from tkinter import ttk, messagebox, StringVar, Button, Entry, Frame, Label, LEFT, RIGHT, X
from argparse import Namespace
import logging
import os
import random
import re
from argparse import Namespace
from tkinter import (
LEFT,
RIGHT,
Button,
Entry,
Frame,
Label,
StringVar,
X,
messagebox,
ttk,
)
import source.classes.constants as CONST
import source.gui.widgets as widgets
from CLI import parse_cli
from Fill import FillError
from Main import main, export_yaml, EnemizerError
from Utils import local_path, output_path, open_file, update_deprecated_args
import source.classes.constants as CONST
from source.gui.randomize.multiworld import multiworld_page
import source.gui.widgets as widgets
from Main import EnemizerError, export_yaml, main
from source.classes.Empty import Empty
from source.gui.randomize.multiworld import multiworld_page
from Utils import local_path, open_file, output_path, update_deprecated_args
def bottom_frame(self, parent, args=None):
+3 -2
View File
@@ -1,9 +1,10 @@
from tkinter import ttk, Frame, N, E, W, LEFT, TOP, X, VERTICAL, Y
import source.gui.widgets as widgets
import json
import os
from tkinter import LEFT, TOP, VERTICAL, E, Frame, N, W, X, Y, ttk
import source.classes.constants as CONST
import source.gui.widgets as widgets
def custom_page(top,parent):
# Custom Item Pool
+5 -4
View File
@@ -1,10 +1,11 @@
from source.classes.SpriteSelector import SpriteSelector as spriteSelector
from source.gui.randomize.gameoptions import set_sprite
from Rom import Sprite, get_sprite_from_name
from Utils import update_deprecated_args
import source.classes.constants as CONST
from Rom import Sprite, get_sprite_from_name
from source.classes.BabelFish import BabelFish
from source.classes.Empty import Empty
from source.classes.SpriteSelector import SpriteSelector as spriteSelector
from source.gui.randomize.gameoptions import set_sprite
from Utils import update_deprecated_args
# Load args/settings for most tabs
def loadcliargs(gui, args, settings=None):
+4 -2
View File
@@ -1,7 +1,9 @@
from tkinter import ttk, Frame, Label, E, W, LEFT, RIGHT, TOP
import source.gui.widgets as widgets
import json
import os
from tkinter import LEFT, RIGHT, TOP, E, Frame, Label, W, ttk
import source.gui.widgets as widgets
def dungeon_page(parent):
# Dungeon Shuffle
+19 -2
View File
@@ -1,10 +1,27 @@
from tkinter import ttk, filedialog, StringVar, Button, Entry, Frame, Label, N, E, W, LEFT, RIGHT, BOTTOM, X
import source.gui.widgets as widgets
import json
import os
import webbrowser
from tkinter import (
BOTTOM,
LEFT,
RIGHT,
Button,
E,
Entry,
Frame,
Label,
N,
StringVar,
W,
X,
filedialog,
ttk,
)
import source.gui.widgets as widgets
from source.classes.Empty import Empty
def enemizer_page(parent,settings):
# Enemizer
self = ttk.Frame(parent)
+4 -2
View File
@@ -1,7 +1,9 @@
from tkinter import ttk, Frame, E, W, LEFT, RIGHT
import source.gui.widgets as widgets
import json
import os
from tkinter import LEFT, RIGHT, E, Frame, W, ttk
import source.gui.widgets as widgets
def entrando_page(parent):
# Entrance Randomizer
+20 -5
View File
@@ -1,10 +1,25 @@
from tkinter import ttk, StringVar, Button, Entry, Frame, Label, NE, NW, E, W, LEFT, RIGHT
from functools import partial
import source.classes.SpriteSelector as spriteSelector
import source.classes.ItemGfxSelector as itemGfxSelector
import source.gui.widgets as widgets
import json
import os
from functools import partial
from tkinter import (
LEFT,
NE,
NW,
RIGHT,
Button,
E,
Entry,
Frame,
Label,
StringVar,
W,
ttk,
)
import source.classes.ItemGfxSelector as itemGfxSelector
import source.classes.SpriteSelector as spriteSelector
import source.gui.widgets as widgets
def gameoptions_page(top, parent):
# Game Options
+23 -6
View File
@@ -1,11 +1,28 @@
from tkinter import ttk, filedialog, StringVar, Button, Entry, Frame, Label, E, W, LEFT, X, Text, Tk, INSERT
import source.classes.diags as diagnostics
import source.gui.widgets as widgets
import json
import os
from functools import partial
from tkinter import (
INSERT,
LEFT,
Button,
E,
Entry,
Frame,
Label,
StringVar,
Text,
Tk,
W,
X,
filedialog,
ttk,
)
import source.classes.diags as diagnostics
import source.gui.widgets as widgets
from source.classes.Empty import Empty
from Main import __version__
from Versions import DRVersion
def generation_page(parent,settings):
# Generation Setup
@@ -148,9 +165,9 @@ def generation_page(parent,settings):
"width": 120,
"height": 50
}
}
}
diag = Tk()
diag.title("Door Shuffle " + __version__)
diag.title("Door Shuffle " + DRVersion)
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.pack()
+19 -3
View File
@@ -1,8 +1,24 @@
from tkinter import messagebox, ttk, font, Button, Frame, E, W, TOP, LEFT, RIGHT, X, Y, Label
import source.gui.widgets as widgets
from source.classes.Empty import Empty
import json
import os
from tkinter import (
LEFT,
RIGHT,
TOP,
Button,
E,
Frame,
Label,
W,
X,
Y,
font,
messagebox,
ttk,
)
import source.gui.widgets as widgets
from source.classes.Empty import Empty
def item_page(parent):
# Item Randomizer
+4 -2
View File
@@ -1,9 +1,11 @@
from tkinter import ttk, StringVar, Entry, Frame, Label, N, E, W, X, LEFT
import source.gui.widgets as widgets
import json
import os
from tkinter import LEFT, E, Entry, Frame, Label, N, StringVar, W, X, ttk
import source.gui.widgets as widgets
from source.classes.Empty import Empty
def multiworld_page(parent,settings):
# Multiworld
self = ttk.Frame(parent)
+4 -2
View File
@@ -1,7 +1,9 @@
from tkinter import ttk, Frame, Label, W, E, NW, LEFT, RIGHT, X, Y, TOP
import source.gui.widgets as widgets
import json
import os
from tkinter import LEFT, NW, RIGHT, TOP, E, Frame, Label, W, X, Y, ttk
import source.gui.widgets as widgets
def overworld_page(parent):
# Overworld Shuffle
+3 -2
View File
@@ -1,9 +1,10 @@
from tkinter import ttk, Frame, N, E, W, LEFT, TOP, X, VERTICAL, Y
import source.gui.widgets as widgets
import json
import os
from tkinter import LEFT, TOP, VERTICAL, E, Frame, N, W, X, Y, ttk
import source.classes.constants as CONST
import source.gui.widgets as widgets
def startinventory_page(top,parent):
# Starting Inventory
+17 -2
View File
@@ -1,7 +1,22 @@
from tkinter import messagebox, Checkbutton, Entry, Frame, IntVar, Label, OptionMenu, Spinbox, StringVar, LEFT, RIGHT, X
from tkinter import Button
from tkinter import (
LEFT,
RIGHT,
Button,
Checkbutton,
Entry,
Frame,
IntVar,
Label,
OptionMenu,
Spinbox,
StringVar,
X,
messagebox,
)
from source.classes.Empty import Empty
# Override Spinbox to include mousewheel support for changing value
class mySpinbox(Spinbox):
def __init__(self, *args, **kwargs):
+2 -1
View File
@@ -2,7 +2,8 @@ from collections import deque
from BaseClasses import CollectionState, RegionType
from Dungeons import dungeon_table
from OWEdges import OWTileRegions, OWTileDistricts
from OWEdges import OWTileDistricts, OWTileRegions
class District(object):
+4 -4
View File
@@ -1,13 +1,13 @@
import RaceRandom as random
import logging
from collections import defaultdict
from source.item.District import resolve_districts
from BaseClasses import PotItem, PotFlags, LocationType
import RaceRandom as random
from BaseClasses import LocationType, PotFlags, PotItem
from DoorShuffle import validate_vanilla_reservation
from Dungeons import dungeon_table
from Items import item_table, ItemFactory
from Items import ItemFactory, item_table
from PotShuffle import vanilla_pots
from source.item.District import resolve_districts
class ItemPoolConfig(object):
+1 -1
View File
@@ -1,6 +1,6 @@
import itertools
from collections import OrderedDict
try:
from fast_enum import FastEnum
except ImportError:
+2 -2
View File
@@ -2,11 +2,11 @@
Build Entrypoints
'''
import json
import platform
import os # for checking for dirs
import platform
import re
from json.decoder import JSONDecodeError
from subprocess import Popen, PIPE, STDOUT, CalledProcessError
from subprocess import PIPE, STDOUT, CalledProcessError, Popen
DEST_DIRECTORY = "."
+1 -1
View File
@@ -25,7 +25,7 @@ def check_requirements(console=False):
logger.error('https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/BUILDING.md')
else:
import webbrowser
from tkinter import Tk, Label, Button, Frame
from tkinter import Button, Frame, Label, Tk
master = Tk()
master.title('Error')
+4 -5
View File
@@ -1,10 +1,9 @@
import RaceRandom as random
import logging
import copy
import logging
from collections import OrderedDict, defaultdict
from collections import defaultdict, OrderedDict
import RaceRandom as random
from BaseClasses import RegionType
from source.overworld.EntranceData import door_addresses
@@ -857,9 +856,9 @@ def get_nearby_entrances(avail, start_region):
def get_accessible_entrances(start_region, avail, assumed_inventory=[], cross_world=False, region_rules=True, exit_rules=True, include_one_ways=False, restrictive_follower=False):
from Main import copy_world_premature
from BaseClasses import CollectionState
from Items import ItemFactory
from Main import copy_world_premature
from OverworldShuffle import build_accessible_region_list, one_way_ledges
for p in range(1, avail.world.players + 1):
+20 -5
View File
@@ -1,13 +1,28 @@
from collections import defaultdict
from Utils import snes_to_pc, int24_as_bytes, int16_as_bytes, load_cached_yaml, pc_to_snes
from source.dungeon.EnemyList import EnemyTable, init_vanilla_sprites, vanilla_sprites, init_enemy_stats, EnemySprite
from source.dungeon.EnemyList import sprite_translation
from source.dungeon.EnemyList import (
EnemySprite,
EnemyTable,
init_enemy_stats,
init_vanilla_sprites,
sprite_translation,
vanilla_sprites,
)
from source.dungeon.RoomHeader import init_room_headers
from source.dungeon.RoomList import Room0127
from source.enemizer.OwEnemyList import init_vanilla_sprites_ow, vanilla_sprites_ow
from source.enemizer.SpriteSheets import init_sprite_sheets, init_sprite_requirements, SheetChoice
from source.enemizer.SpriteSheets import (
SheetChoice,
init_sprite_requirements,
init_sprite_sheets,
)
from Utils import (
int16_as_bytes,
int24_as_bytes,
load_cached_yaml,
pc_to_snes,
snes_to_pc,
)
def convert_area_id_to_offset(area_id):

Some files were not shown because too many files have changed in this diff Show More