Merge Unstable into EdgeWork

This commit is contained in:
aerinon
2020-04-24 14:02:47 -06:00
43 changed files with 1520 additions and 309 deletions
+4
View File
@@ -220,6 +220,7 @@ jobs:
body: ${{ steps.release_notes.outputs.body }} body: ${{ steps.release_notes.outputs.body }}
draft: true draft: true
prerelease: true prerelease: true
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
# upload linux archive asset # upload linux archive asset
- name: Upload Linux Archive Asset - name: Upload Linux Archive Asset
id: upload-linux-asset id: upload-linux-asset
@@ -231,6 +232,7 @@ jobs:
asset_path: ../deploy/linux/ALttPDoorRandomizer.tar.gz asset_path: ../deploy/linux/ALttPDoorRandomizer.tar.gz
asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-linux-bionic.tar.gz asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-linux-bionic.tar.gz
asset_content_type: application/gzip asset_content_type: application/gzip
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
# upload macos archive asset # upload macos archive asset
- name: Upload MacOS Archive Asset - name: Upload MacOS Archive Asset
id: upload-macos-asset id: upload-macos-asset
@@ -242,6 +244,7 @@ jobs:
asset_path: ../deploy/macos/ALttPDoorRandomizer.tar.gz asset_path: ../deploy/macos/ALttPDoorRandomizer.tar.gz
asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-osx.tar.gz asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-osx.tar.gz
asset_content_type: application/gzip asset_content_type: application/gzip
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
# upload windows archive asset # upload windows archive asset
- name: Upload Windows Archive Asset - name: Upload Windows Archive Asset
id: upload-windows-asset id: upload-windows-asset
@@ -253,3 +256,4 @@ jobs:
asset_path: ../deploy/windows/ALttPDoorRandomizer.zip asset_path: ../deploy/windows/ALttPDoorRandomizer.zip
asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-windows.zip asset_name: ALttPDoorRandomizer-${{ steps.debug_info.outputs.github_tag }}-windows.zip
asset_content_type: application/zip asset_content_type: application/zip
if: contains(github.ref, 'master') || contains(github.ref, 'stable') || contains(github.ref, 'dev') || contains(github.ref, 'DoorRelease')
+6
View File
@@ -1,6 +1,9 @@
.idea .idea
.vscode .vscode
*_Spoiler.txt *_Spoiler.txt
*.bmbp
*.log
*_Spoiler.json
*.pyc *.pyc
*.sfc *.sfc
*.srm *.srm
@@ -18,6 +21,9 @@ EnemizerCLI/
RaceRom.py RaceRom.py
upx/ upx/
weights/ weights/
/MultiMystery/
/Players/
/QUsb2Snes/
resources/user/* resources/user/*
!resources/user/.gitkeep !resources/user/.gitkeep
+47 -1
View File
@@ -1031,6 +1031,44 @@ class Direction(Enum):
Down = 5 Down = 5
@unique
class Hook(Enum):
North = 0
West = 1
South = 2
East = 3
Stairs = 4
NEdge = 5
SEdge = 6
WEdge = 7
EEdge = 8
hook_dir_map = {
Direction.North: Hook.North,
Direction.South: Hook.South,
Direction.West: Hook.West,
Direction.East: Hook.East,
}
edge_map = {
Direction.North: Hook.NEdge,
Direction.South: Hook.SEdge,
Direction.West: Hook.WEdge,
Direction.East: Hook.EEdge,
}
def hook_from_door(door):
if door.type == DoorType.SpiralStairs:
return Hook.Stairs
if door.type == DoorType.Normal:
return hook_dir_map[door.direction]
if door.type == DoorType.Open:
return edge_map[door.direction]
return None
class Polarity: class Polarity:
def __init__(self): def __init__(self):
self.vector = [0, 0, 0] self.vector = [0, 0, 0]
@@ -1306,6 +1344,7 @@ class Sector(object):
self.branch_factor = None self.branch_factor = None
self.dead_end_cnt = None self.dead_end_cnt = None
self.entrance_sector = None self.entrance_sector = None
self.destination_entrance = False
self.equations = None self.equations = None
def region_set(self): def region_set(self):
@@ -1327,6 +1366,13 @@ class Sector(object):
magnitude[idx] = magnitude[idx] + 1 magnitude[idx] = magnitude[idx] + 1
return magnitude return magnitude
def hook_magnitude(self):
magnitude = [0] * len(Hook)
for door in self.outstanding_doors:
idx = hook_from_door(door).value
magnitude[idx] = magnitude[idx] + 1
return magnitude
def outflow(self): def outflow(self):
outflow = 0 outflow = 0
for door in self.outstanding_doors: for door in self.outstanding_doors:
@@ -1349,7 +1395,7 @@ class Sector(object):
self.branch_factor -= cnt_dead - 1 self.branch_factor -= cnt_dead - 1
for region in self.regions: for region in self.regions:
for ent in region.entrances: for ent in region.entrances:
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]: if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld] or ent.parent_region.name == 'Sewer Drop':
# same sector as another entrance # same sector as another entrance
if region.name not in ['Skull Pot Circle', 'Skull Back Drop', 'Desert East Lobby', 'Desert West Lobby']: if region.name not in ['Skull Pot Circle', 'Skull Back Drop', 'Desert East Lobby', 'Desert West Lobby']:
self.branch_factor += 1 self.branch_factor += 1
+7 -3
View File
@@ -123,6 +123,7 @@ def parse_settings():
"accessibility": "items", "accessibility": "items",
"algorithm": "balanced", "algorithm": "balanced",
# Shuffle Ganon defaults to TRUE
"openpyramid": False, "openpyramid": False,
"shuffleganon": True, "shuffleganon": True,
"shuffle": "vanilla", "shuffle": "vanilla",
@@ -146,7 +147,9 @@ def parse_settings():
"multi": 1, "multi": 1,
"names": "", "names": "",
# Hints default to TRUE
"hints": True, "hints": True,
"no_hints": False,
"disablemusic": False, "disablemusic": False,
"quickswap": False, "quickswap": False,
"heartcolor": "red", "heartcolor": "red",
@@ -156,10 +159,11 @@ def parse_settings():
"ow_palettes": "default", "ow_palettes": "default",
"uw_palettes": "default", "uw_palettes": "default",
# Spoiler defaults to FALSE
# Playthrough defaults to TRUE
# ROM defaults to TRUE
"create_spoiler": False, "create_spoiler": False,
"skip_playthrough": False,
"calc_playthrough": True, "calc_playthrough": True,
"suppress_rom": False,
"create_rom": True, "create_rom": True,
"usestartinventory": False, "usestartinventory": False,
"custom": False, "custom": False,
@@ -309,9 +313,9 @@ def get_args_priority(settings_args, gui_args, cli_args):
else: else:
newArgs[key] = args[key] newArgs[key] = args[key]
newArgs[key] = update_deprecated_args(newArgs[key])
else: else:
newArgs[key] = args[key] newArgs[key] = args[key]
newArgs[key] = update_deprecated_args(newArgs[key])
args = newArgs args = newArgs
+20 -20
View File
@@ -409,11 +409,6 @@ def determine_entrance_list(world, player):
return entrance_map, potential_entrances, connections return entrance_map, potential_entrances, connections
# todo: kill drop exceptions
def drop_exception(name):
return name in ['Skull Pot Circle', 'Skull Back Drop']
def add_shuffled_entrances(sectors, region_list, entrance_list): def add_shuffled_entrances(sectors, region_list, entrance_list):
for sector in sectors: for sector in sectors:
for region in sector.regions: for region in sector.regions:
@@ -431,8 +426,6 @@ def find_enabled_origins(sectors, enabled, entrance_list, entrance_map, key):
if key not in entrance_map.keys(): if key not in entrance_map.keys():
key = ' '.join(key.split(' ')[:-1]) key = ' '.join(key.split(' ')[:-1])
entrance_map[key].append(region.name) entrance_map[key].append(region.name)
if drop_exception(region.name): # only because they have unique regions
entrance_list.append(region.name)
def remove_drop_origins(entrance_list): def remove_drop_origins(entrance_list):
@@ -504,7 +497,7 @@ def cross_dungeon(world, player):
all_sectors = [] all_sectors = []
for key in dungeon_regions.keys(): for key in dungeon_regions.keys():
all_sectors.extend(convert_to_sectors(dungeon_regions[key], world, player)) all_sectors.extend(convert_to_sectors(dungeon_regions[key], world, player))
dungeon_builders = create_dungeon_builders(all_sectors, world, player) dungeon_builders = create_dungeon_builders(all_sectors, connections_tuple, world, player)
for builder in dungeon_builders.values(): for builder in dungeon_builders.values():
builder.entrance_list = list(entrances_map[builder.name]) builder.entrance_list = list(entrances_map[builder.name])
dungeon_obj = world.get_dungeon(builder.name, player) dungeon_obj = world.get_dungeon(builder.name, player)
@@ -524,25 +517,15 @@ def cross_dungeon(world, player):
check_required_paths(paths, world, player) check_required_paths(paths, world, player)
hc = world.get_dungeon('Hyrule Castle', player) hc = world.get_dungeon('Hyrule Castle', player)
del hc.dungeon_items[0] # removes map
hc.dungeon_items.append(ItemFactory('Compass (Escape)', player)) hc.dungeon_items.append(ItemFactory('Compass (Escape)', player))
at = world.get_dungeon('Agahnims Tower', player) at = world.get_dungeon('Agahnims Tower', player)
at.dungeon_items.append(ItemFactory('Compass (Agahnims Tower)', player)) at.dungeon_items.append(ItemFactory('Compass (Agahnims Tower)', player))
gt = world.get_dungeon('Ganons Tower', player) at.dungeon_items.append(ItemFactory('Map (Agahnims Tower)', player))
del gt.dungeon_items[0] # removes map
assign_cross_keys(dungeon_builders, world, player) assign_cross_keys(dungeon_builders, world, player)
all_dungeon_items = [y for x in world.dungeons if x.player == player for y in x.all_items] all_dungeon_items = [y for x in world.dungeons if x.player == player for y in x.all_items]
target_items = 34 if world.retro[player] else 63 target_items = 34 if world.retro[player] else 63
d_items = target_items - len(all_dungeon_items) d_items = target_items - len(all_dungeon_items)
if d_items > 0:
if d_items >= 1: # restore HC map
world.get_dungeon('Hyrule Castle', player).dungeon_items.append(ItemFactory('Map (Escape)', player))
if d_items >= 2: # restore GT map
world.get_dungeon('Ganons Tower', player).dungeon_items.append(ItemFactory('Map (Ganons Tower)', player))
if d_items > 2:
world.pool_adjustment[player] = d_items - 2
elif d_items < 0:
world.pool_adjustment[player] = d_items world.pool_adjustment[player] = d_items
smooth_door_pairs(world, player) smooth_door_pairs(world, player)
@@ -1127,7 +1110,7 @@ def stateful_door(door, kind):
def random_door_type(door, partner, world, player, type_a, type_b, room_a, room_b): def random_door_type(door, partner, world, player, type_a, type_b, room_a, room_b):
r_kind = random.choices([DoorKind.Normal, DoorKind.Bombable, DoorKind.Dashable], [5, 2, 3], k=1)[0] r_kind = random.choices([DoorKind.Normal, DoorKind.Bombable, DoorKind.Dashable], [15, 4, 6], k=1)[0]
if r_kind != DoorKind.Normal: if r_kind != DoorKind.Normal:
if door.type == DoorType.Normal: if door.type == DoorType.Normal:
add_pair(door, partner, world, player) add_pair(door, partner, world, player)
@@ -1319,6 +1302,7 @@ class DROptions(Flag):
NoOptions = 0x00 NoOptions = 0x00
Eternal_Mini_Bosses = 0x01 # If on, GT minibosses marked as defeated when they try to spawn a heart Eternal_Mini_Bosses = 0x01 # If on, GT minibosses marked as defeated when they try to spawn a heart
Town_Portal = 0x02 # If on, Players will start with mirror scroll Town_Portal = 0x02 # If on, Players will start with mirror scroll
Map_Info = 0x04
Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required Open_Desert_Wall = 0x80 # If on, pre opens the desert wall, no fire required
# DATA GOES DOWN HERE # DATA GOES DOWN HERE
@@ -2081,3 +2065,19 @@ compass_data = {
'Turtle Rock': (0x11F, 0xcb, 0x15e, 0, 0x18), 'Turtle Rock': (0x11F, 0xcb, 0x15e, 0, 0x18),
'Ganons Tower': (0x13A, 0xcc, 0x170, 2, 0x1a) 'Ganons Tower': (0x13A, 0xcc, 0x170, 2, 0x1a)
} }
# For compass boss indicator
boss_indicator = {
'Eastern Palace': (0x04, 'Eastern Boss SE'),
'Desert Palace': (0x06, 'Desert Boss SW'),
'Agahnims Tower': (0x08, 'Tower Agahnim 1 SW'),
'Swamp Palace': (0x0a, 'Swamp Boss SW'),
'Palace of Darkness': (0x0c, 'PoD Boss SE'),
'Misery Mire': (0x0e, 'Mire Boss SW'),
'Skull Woods': (0x10, 'Skull Spike Corner SW'),
'Ice Palace': (0x12, 'Ice Antechamber NE'),
'Tower of Hera': (0x14, 'Hera Boss Down Stairs'),
'Thieves Town': (0x16, 'Thieves Boss SE'),
'Turtle Rock': (0x18, 'TR Boss SW'),
'Ganons Tower': (0x1a, 'GT Agahnim 2 SW')
}
+1 -1
View File
@@ -333,7 +333,7 @@ def create_doors(world, player):
create_door(player, 'PoD Basement Ledge Drop Down', Lgcl), create_door(player, 'PoD Basement Ledge Drop Down', Lgcl),
create_door(player, 'PoD Stalfos Basement Warp', Warp), create_door(player, 'PoD Stalfos Basement Warp', Warp),
create_door(player, 'PoD Arena Main SW', Nrml).dir(So, 0x2a, Left, High).pos(4), create_door(player, 'PoD Arena Main SW', Nrml).dir(So, 0x2a, Left, High).pos(4),
create_door(player, 'PoD Arena Bridge SE', Nrml).dir(So, 0x2a, Right, High).pos(5).kill(), create_door(player, 'PoD Arena Bridge SE', Nrml).dir(So, 0x2a, Right, High).pos(5),
create_door(player, 'PoD Arena Main NW', Nrml).dir(No, 0x2a, Left, High).small_key().pos(1), create_door(player, 'PoD Arena Main NW', Nrml).dir(No, 0x2a, Left, High).small_key().pos(1),
create_door(player, 'PoD Arena Main NE', Nrml).dir(No, 0x2a, Right, High).no_exit().trap(0x4).pos(0), create_door(player, 'PoD Arena Main NE', Nrml).dir(No, 0x2a, Right, High).no_exit().trap(0x4).pos(0),
create_door(player, 'PoD Arena Main Crystal Path', Lgcl), create_door(player, 'PoD Arena Main Crystal Path', Lgcl),
+862 -123
View File
File diff suppressed because it is too large Load Diff
+10 -2
View File
@@ -9,9 +9,10 @@ import shlex
import sys import sys
from source.classes.BabelFish import BabelFish from source.classes.BabelFish import BabelFish
import source.classes.diags as diagnostics
from CLI import parse_cli, get_args_priority from CLI import parse_cli, get_args_priority
from Main import main, EnemizerError from Main import main, EnemizerError, __version__
from Rom import get_sprite_from_name from Rom import get_sprite_from_name
from Utils import is_bundled, close_console from Utils import is_bundled, close_console
from Fill import FillError from Fill import FillError
@@ -19,6 +20,13 @@ from Fill import FillError
def start(): def start():
args = parse_cli(None) args = parse_cli(None)
# print diagnostics
# usage: py DungeonRandomizer.py --diags
if args.diags:
diags = diagnostics.output(__version__)
print("\n".join(diags))
sys.exit(0)
if is_bundled() and len(sys.argv) == 1: if is_bundled() and len(sys.argv) == 1:
# for the bundled builds, if we have no arguments, the user # for the bundled builds, if we have no arguments, the user
# probably wants the gui. Users of the bundled build who want the command line # probably wants the gui. Users of the bundled build who want the command line
@@ -67,7 +75,7 @@ def start():
logger.warning('%s: %s', fish.translate("cli","cli","generation.failed"), err) logger.warning('%s: %s', fish.translate("cli","cli","generation.failed"), err)
seed = random.randint(0, 999999999) seed = random.randint(0, 999999999)
for fail in failures: for fail in failures:
logger.info('%s seed failed with: %s', fail[1], fail[0]) logger.info('%s\tseed failed with: %s', fail[1], fail[0])
fail_rate = 100 * len(failures) / args.count fail_rate = 100 * len(failures) / args.count
success_rate = 100 * (args.count - len(failures)) / args.count success_rate = 100 * (args.count - len(failures)) / args.count
fail_rate = str(fail_rate).split('.') fail_rate = str(fail_rate).split('.')
+4 -2
View File
@@ -5,6 +5,8 @@ import sys
block_cipher = None block_cipher = None
console = True console = True
BINARY_SLUG = "DungeonRandomizer"
def recurse_for_py_files(names_so_far): def recurse_for_py_files(names_so_far):
returnvalue = [] returnvalue = []
for name in os.listdir(os.path.join(*names_so_far)): for name in os.listdir(os.path.join(*names_so_far)):
@@ -28,7 +30,7 @@ binaries = []
#if sys.platform.find("windows"): #if sys.platform.find("windows"):
# binaries.append(("ucrtbase.dll",".")) # binaries.append(("ucrtbase.dll","."))
a = Analysis(['DungeonRandomizer.py'], a = Analysis([f"./{BINARY_SLUG}.py"],
pathex=[], pathex=[],
binaries=binaries, binaries=binaries,
datas=[], datas=[],
@@ -56,7 +58,7 @@ exe = EXE(pyz,
a.zipfiles, a.zipfiles,
a.datas, a.datas,
[], [],
name='DungeonRandomizer', name=BINARY_SLUG,
debug=False, debug=False,
bootloader_ignore_signals=False, bootloader_ignore_signals=False,
strip=False, strip=False,
+3 -2
View File
@@ -7,6 +7,7 @@ console = True
if sys.platform.find("mac") or sys.platform.find("osx"): if sys.platform.find("mac") or sys.platform.find("osx"):
console = False console = False
BINARY_SLUG = "Gui"
def recurse_for_py_files(names_so_far): def recurse_for_py_files(names_so_far):
returnvalue = [] returnvalue = []
@@ -31,7 +32,7 @@ binaries = []
#if sys.platform.find("windows"): #if sys.platform.find("windows"):
# binaries.append(("ucrtbase.dll",".")) # binaries.append(("ucrtbase.dll","."))
a = Analysis(['DungeonRandomizer.py'], a = Analysis([f"./{BINARY_SLUG}.py"],
pathex=[], pathex=[],
binaries=binaries, binaries=binaries,
datas=[], datas=[],
@@ -59,7 +60,7 @@ exe = EXE(pyz,
a.zipfiles, a.zipfiles,
a.datas, a.datas,
[], [],
name='Gui', name=BINARY_SLUG,
debug=False, debug=False,
bootloader_ignore_signals=False, bootloader_ignore_signals=False,
strip=False, strip=False,
+4 -3
View File
@@ -1,13 +1,14 @@
import queue import queue
import os
import threading import threading
import tkinter as tk import tkinter as tk
from Utils import local_path from Utils import local_path
def set_icon(window): def set_icon(window):
er16 = tk.PhotoImage(file=local_path('data/ER16.gif')) er16 = tk.PhotoImage(file=local_path(os.path.join("data","ER16.gif")))
er32 = tk.PhotoImage(file=local_path('data/ER32.gif')) er32 = tk.PhotoImage(file=local_path(os.path.join("data","ER32.gif")))
er48 = tk.PhotoImage(file=local_path('data/ER32.gif')) er48 = tk.PhotoImage(file=local_path(os.path.join("data","ER48.gif")))
window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) # pylint: disable=protected-access window.tk.call('wm', 'iconphoto', window._w, er16, er32, er48) # pylint: disable=protected-access
# Although tkinter is intended to be thread safe, there are many reports of issues # Although tkinter is intended to be thread safe, there are many reports of issues
+3 -2
View File
@@ -62,8 +62,8 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
'Progressive Glove': (True, False, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a glove'), 'Progressive Glove': (True, False, None, 0x61, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a glove'),
'Silver Arrows': (True, False, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the silver arrows'), 'Silver Arrows': (True, False, None, 0x58, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the silver arrows'),
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], None, None, None, None, None, None, None), 'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], None, None, None, None, None, None, None),
'Red Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], None, None, None, None, None, None, None), 'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], None, None, None, None, None, None, None),
'Blue Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03], None, None, None, None, None, None, None), 'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03], None, None, None, None, None, None, None),
'Triforce': (True, False, None, 0x6A, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'), 'Triforce': (True, False, None, 0x6A, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'),
'Power Star': (True, False, None, 0x6B, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'), 'Power Star': (True, False, None, 0x6B, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'),
'Triforce Piece': (True, False, None, 0x6C, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'), 'Triforce Piece': (True, False, None, 0x6C, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'),
@@ -130,6 +130,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 'You have\nchosen the\narcher cla
'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Castle Tower'), 'Small Key (Agahnims Tower)': (False, False, 'SmallKey', 0xA4, 'A small key to Agahnim', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Castle Tower'),
'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 'A big key to Agahnim', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'), 'Big Key (Agahnims Tower)': (False, False, 'BigKey', 0x9B, 'A big key to Agahnim', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Castle Tower'),
'Compass (Agahnims Tower)': (False, True, 'Compass', 0x8B, 'Now you can find Aga1!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Castle Tower'), 'Compass (Agahnims Tower)': (False, True, 'Compass', 0x8B, 'Now you can find Aga1!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds null again', 'a compass to Castle Tower'),
'Map (Agahnims Tower)': (False, True, 'Map', 0x7B, 'A tightly folded map rests here', 'and the map', 'cartography kid', 'map for sale', 'a map to shrooms', 'map boy navigates again', 'a map to Castle Tower'),
'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'), 'Small Key (Palace of Darkness)': (False, False, 'SmallKey', 0xA6, 'A small key to darkness', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a small key to Palace of Darkness'),
'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'), 'Big Key (Palace of Darkness)': (False, False, 'BigKey', 0x99, 'A big key to darkness', 'and the big key', 'the big-unlock kid', 'big key for sale', 'face key fungus', 'key boy opens chest again', 'a big key to Palace of Darkness'),
'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'), 'Compass (Palace of Darkness)': (False, True, 'Compass', 0x89, 'Now you can find Helmasaur King!', 'and the compass', 'the magnetic kid', 'compass for sale', 'magnetic fungus', 'compass boy finds boss again', 'a compass to Palace of Darkness'),
+48 -13
View File
@@ -19,16 +19,17 @@ from Doors import create_doors
from DoorShuffle import link_doors from DoorShuffle import link_doors
from RoomData import create_rooms from RoomData import create_rooms
from Rules import set_rules from Rules import set_rules
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive, dungeon_regions from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression
from ItemList import generate_itempool, difficulties, fill_prizes from ItemList import generate_itempool, difficulties, fill_prizes
from Utils import output_path, parse_player_names, print_wiki_doors_by_region, print_wiki_doors_by_room from Utils import output_path, parse_player_names
__version__ = '0.1.0-dev' __version__ = '0.1.0.0-u'
class EnemizerError(RuntimeError): class EnemizerError(RuntimeError):
pass pass
def main(args, seed=None, fish=None): def main(args, seed=None, fish=None):
if args.outputpath: if args.outputpath:
os.makedirs(args.outputpath, exist_ok=True) os.makedirs(args.outputpath, exist_ok=True)
@@ -186,20 +187,18 @@ def main(args, seed=None, fish=None):
rom_names = [] rom_names = []
jsonout = {} jsonout = {}
enemized = False
if not args.suppress_rom: if not args.suppress_rom:
logger.info(world.fish.translate("cli","cli","patching.rom")) logger.info(world.fish.translate("cli","cli","patching.rom"))
for team in range(world.teams): for team in range(world.teams):
for player in range(1, world.players + 1): for player in range(1, world.players + 1):
sprite_random_on_hit = type(args.sprite[player]) is str and args.sprite[player].lower() == 'randomonhit' sprite_random_on_hit = type(args.sprite[player]) is str and args.sprite[player].lower() == 'randomonhit'
enemized = False
use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] != 'none' use_enemizer = (world.boss_shuffle[player] != 'none' or world.enemy_shuffle[player] != 'none'
or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default' or world.enemy_health[player] != 'default' or world.enemy_damage[player] != 'default'
or args.shufflepots[player] or sprite_random_on_hit) or args.shufflepots[player] or sprite_random_on_hit)
rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom) rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom)
patch_rom(world, rom, player, team, use_enemizer)
if use_enemizer and (args.enemizercli or not args.jsonout): if use_enemizer and (args.enemizercli or not args.jsonout):
if args.rom and not(os.path.isfile(args.rom)): if args.rom and not(os.path.isfile(args.rom)):
raise RuntimeError("Could not find valid base rom for enemizing at expected path %s." % args.rom) raise RuntimeError("Could not find valid base rom for enemizing at expected path %s." % args.rom)
@@ -242,13 +241,44 @@ def main(args, seed=None, fish=None):
outfilepname += f'_P{player}' outfilepname += f'_P{player}'
if world.players > 1 or world.teams > 1: if world.players > 1 or world.teams > 1:
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else '' outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else ''
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s_%s-%s%s%s%s%s' % (world.logic[player], world.difficulty[player], world.difficulty_adjustments[player], outfilestuffs = {
world.mode[player], world.goal[player], "logic": world.logic[player], # 0
"" if world.timer in ['none', 'display'] else "-" + world.timer, "difficulty": world.difficulty[player], # 1
world.shuffle[player], world.doorShuffle[player], world.algorithm, mcsb_name, "difficulty_adjustments": world.difficulty_adjustments[player], # 2
"-retro" if world.retro[player] else "", "mode": world.mode[player], # 3
"-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "goal": world.goal[player], # 4
"-nohints" if not world.hints[player] else "")) if not args.outputname else '' "timer": str(world.timer), # 5
"shuffle": world.shuffle[player], # 6
"doorShuffle": world.doorShuffle[player], # 7
"algorithm": world.algorithm, # 8
"mscb": mcsb_name, # 9
"retro": world.retro[player], # A
"progressive": world.progressive, # B
"hints": 'True' if world.hints[player] else 'False' # C
}
# 0 1 2 3 4 5 6 7 8 9 A B C
outfilesuffix = ('_%s_%s-%s-%s-%s%s_%s_%s-%s%s%s%s%s' % (
# 0 1 2 3 4 5 6 7 8 9 A B C
# _noglitches_normal-normal-open-ganon-ohko_simple_basic-balanced-keysanity-retro-prog_swords-nohints
# _noglitches_normal-normal-open-ganon _simple_basic-balanced-keysanity-retro
# _noglitches_normal-normal-open-ganon _simple_basic-balanced-keysanity -prog_swords
# _noglitches_normal-normal-open-ganon _simple_basic-balanced-keysanity -nohints
outfilestuffs["logic"], # 0
outfilestuffs["difficulty"], # 1
outfilestuffs["difficulty_adjustments"], # 2
outfilestuffs["mode"], # 3
outfilestuffs["goal"], # 4
"" if outfilestuffs["timer"] in ['False', 'none', 'display'] else "-" + outfilestuffs["timer"], # 5
outfilestuffs["shuffle"], # 6
outfilestuffs["doorShuffle"], # 7
outfilestuffs["algorithm"], # 8
outfilestuffs["mscb"], # 9
"-retro" if outfilestuffs["retro"] == "True" else "", # A
"-prog_" + outfilestuffs["progressive"] if outfilestuffs["progressive"] in ['off', 'random'] else "", # B
"-nohints" if not outfilestuffs["hints"] == "True" else "")) if not args.outputname else '' # C
rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')) rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc'))
if world.players > 1: if world.players > 1:
@@ -272,6 +302,10 @@ def main(args, seed=None, fish=None):
print(json.dumps({**jsonout, 'spoiler': world.spoiler.to_json()})) print(json.dumps({**jsonout, 'spoiler': world.spoiler.to_json()}))
elif args.create_spoiler: elif args.create_spoiler:
logger.info(world.fish.translate("cli","cli","patching.spoiler")) logger.info(world.fish.translate("cli","cli","patching.spoiler"))
if args.jsonout:
with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile:
outfile.write(world.spoiler.to_json())
else:
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase)) world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
YES = world.fish.translate("cli","cli","yes") YES = world.fish.translate("cli","cli","yes")
@@ -282,6 +316,7 @@ def main(args, seed=None, fish=None):
logger.info(world.fish.translate("cli","cli","made.rom") % (YES if (args.create_rom) else NO)) logger.info(world.fish.translate("cli","cli","made.rom") % (YES if (args.create_rom) else NO))
logger.info(world.fish.translate("cli","cli","made.playthrough") % (YES if (args.calc_playthrough) else NO)) logger.info(world.fish.translate("cli","cli","made.playthrough") % (YES if (args.calc_playthrough) else NO))
logger.info(world.fish.translate("cli","cli","made.spoiler") % (YES if (not args.jsonout and args.create_spoiler) else NO)) logger.info(world.fish.translate("cli","cli","made.spoiler") % (YES if (not args.jsonout and args.create_spoiler) else NO))
logger.info(world.fish.translate("cli","cli","used.enemizer") % (YES if enemized else NO))
logger.info(world.fish.translate("cli","cli","seed") + ": %d", world.seed) logger.info(world.fish.translate("cli","cli","seed") + ": %d", world.seed)
logger.info(world.fish.translate("cli","cli","total.time"), time.perf_counter() - start) logger.info(world.fish.translate("cli","cli","total.time"), time.perf_counter() - start)
+6 -1
View File
@@ -7,6 +7,7 @@ import re
from DungeonRandomizer import parse_cli from DungeonRandomizer import parse_cli
from Main import main as DRMain from Main import main as DRMain
from source.classes.BabelFish import BabelFish
def parse_yaml(txt): def parse_yaml(txt):
def strip(s): def strip(s):
@@ -100,7 +101,7 @@ def main():
loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[erargs.loglevel] loglevel = {'error': logging.ERROR, 'info': logging.INFO, 'warning': logging.WARNING, 'debug': logging.DEBUG}[erargs.loglevel]
logging.basicConfig(format='%(message)s', level=loglevel) logging.basicConfig(format='%(message)s', level=loglevel)
DRMain(erargs, seed) DRMain(erargs, seed, BabelFish())
def get_weights(path): def get_weights(path):
try: try:
@@ -150,6 +151,10 @@ def roll_settings(weights):
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla' ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
ret.experimental = get_choice('experimental') == 'on' ret.experimental = get_choice('experimental') == 'on'
ret.dungeon_counters = get_choice('dungeon_counters')
if ret.dungeon_counters == 'default':
ret.dungeon_counters = 'pickup' if ret.door_shuffle != 'vanilla' or ret.compassshuffle == 'on' else 'off'
goal = get_choice('goals') goal = get_choice('goals')
ret.goal = {'ganon': 'ganon', ret.goal = {'ganon': 'ganon',
'fast_ganon': 'crystals', 'fast_ganon': 'crystals',
+6 -4
View File
@@ -1,6 +1,8 @@
# Features # New Features
## Door Randomizer * None yet
* Native GUI executables # Bug Fixes
* Native Dungeon Randomizer CLI executables
* Crossed Dungeon generation improvements
* Fix for Animated Tiles in crossed dungeon
+38 -8
View File
@@ -10,7 +10,7 @@ import sys
import subprocess import subprocess
from BaseClasses import CollectionState, ShopType, Region, Location, DoorType from BaseClasses import CollectionState, ShopType, Region, Location, DoorType
from DoorShuffle import compass_data, DROptions from DoorShuffle import compass_data, DROptions, boss_indicator
from Dungeons import dungeon_music_addresses from Dungeons import dungeon_music_addresses
from Regions import location_table from Regions import location_table
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
@@ -22,7 +22,7 @@ from EntranceShuffle import door_addresses, exit_ids
JAP10HASH = '03a63945398191337e896e5771f77173' JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = '5e01caffabb4509a0987ef2f2f0bcd56' RANDOMIZERBASEHASH = '6042de1d3a63417efe4397b69c626b88'
class JsonRom(object): class JsonRom(object):
@@ -162,7 +162,7 @@ def read_rom(stream):
def patch_enemizer(world, player, rom, baserom_path, enemizercli, shufflepots, random_sprite_on_hit): def patch_enemizer(world, player, rom, baserom_path, enemizercli, shufflepots, random_sprite_on_hit):
baserom_path = os.path.abspath(baserom_path) baserom_path = os.path.abspath(baserom_path)
basepatch_path = os.path.abspath(local_path('data/base2current.json')) basepatch_path = os.path.abspath(local_path(os.path.join("data","base2current.json")))
enemizer_basepatch_path = os.path.join(os.path.dirname(enemizercli), "enemizerBasePatch.json") enemizer_basepatch_path = os.path.join(os.path.dirname(enemizercli), "enemizerBasePatch.json")
randopatch_path = os.path.abspath(output_path('enemizer_randopatch.json')) randopatch_path = os.path.abspath(output_path('enemizer_randopatch.json'))
options_path = os.path.abspath(output_path('enemizer_options.json')) options_path = os.path.abspath(output_path('enemizer_options.json'))
@@ -305,7 +305,7 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, shufflepots, r
_sprite_table = {} _sprite_table = {}
def _populate_sprite_table(): def _populate_sprite_table():
if not _sprite_table: if not _sprite_table:
for dir in [local_path('data/sprites/official'), local_path('data/sprites/unofficial')]: for dir in [local_path(os.path.join("data","sprites","official")), local_path(os.path.join("data","sprites","unofficial"))]:
for file in os.listdir(dir): for file in os.listdir(dir):
filepath = os.path.join(dir, file) filepath = os.path.join(dir, file)
if not os.path.isfile(filepath): if not os.path.isfile(filepath):
@@ -387,7 +387,7 @@ class Sprite(object):
@staticmethod @staticmethod
def default_link_sprite(): def default_link_sprite():
return Sprite(local_path('data/default.zspr')) return get_sprite_from_name('Link')
def decode8(self, pos): def decode8(self, pos):
arr = [[0 for _ in range(8)] for _ in range(8)] arr = [[0 for _ in range(8)] for _ in range(8)]
@@ -594,10 +594,22 @@ def patch_rom(world, rom, player, team, enemized):
if world.mode[player] == 'inverted': if world.mode[player] == 'inverted':
patch_shuffled_dark_sanc(world, rom, player) patch_shuffled_dark_sanc(world, rom, player)
# setup dr option flags based on experimental, etc.
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
if world.experimental[player]:
dr_flags |= DROptions.Map_Info
# patch doors # patch doors
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' or not world.experimental[player] else DROptions.Town_Portal
if world.doorShuffle[player] == 'crossed': if world.doorShuffle[player] == 'crossed':
rom.write_byte(0x139004, 2) rom.write_byte(0x139004, 2)
for name, layout in world.key_layout[player].items():
offset = compass_data[name][4]//2
rom.write_byte(0x13f01c+offset, layout.max_chests + layout.max_drops)
rom.write_byte(0x13f02a+offset, layout.max_chests)
builder = world.dungeon_layouts[player][name]
bk_status = 1 if builder.bk_required else 0
bk_status = 2 if builder.bk_provided else bk_status
rom.write_byte(0x13f038+offset*2, bk_status)
rom.write_byte(0x151f1, 2) rom.write_byte(0x151f1, 2)
rom.write_byte(0x15270, 2) rom.write_byte(0x15270, 2)
rom.write_byte(0x1597b, 2) rom.write_byte(0x1597b, 2)
@@ -622,6 +634,13 @@ def patch_rom(world, rom, player, team, enemized):
if builder.pre_open_stonewall: if builder.pre_open_stonewall:
if builder.pre_open_stonewall.name == 'Desert Wall Slide NW': if builder.pre_open_stonewall.name == 'Desert Wall Slide NW':
dr_flags |= DROptions.Open_Desert_Wall dr_flags |= DROptions.Open_Desert_Wall
for name, pair in boss_indicator.items():
dungeon_id, boss_door = pair
opposite_door = world.get_door(boss_door, player).dest
if opposite_door.roomIndex > -1:
dungeon_name = opposite_door.entrance.parent_region.dungeon.name
dungeon_id = boss_indicator[dungeon_name][0]
rom.write_byte(0x13f000+dungeon_id, opposite_door.roomIndex)
rom.write_byte(0x139006, dr_flags.value) rom.write_byte(0x139006, dr_flags.value)
if dr_flags & DROptions.Town_Portal and world.mode[player] == 'inverted': if dr_flags & DROptions.Town_Portal and world.mode[player] == 'inverted':
rom.write_byte(0x139008, 1) rom.write_byte(0x139008, 1)
@@ -1048,6 +1067,7 @@ def patch_rom(world, rom, player, team, enemized):
'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10), 'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10),
'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20), 'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20),
'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0), 'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0),
'Big Key (Agahnims Tower)': (0x367, 0x08), 'Compass (Agahnims Tower)': (0x365, 0x08), 'Map (Agahnims Tower)': (0x369, 0x08),
'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02), 'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02),
'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10), 'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10),
'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80), 'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80),
@@ -1173,8 +1193,8 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] else 0x00) rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] else 0x00)
| (0x02 if world.bigkeyshuffle[player] else 0x00) | (0x02 if world.bigkeyshuffle[player] else 0x00)
| (0x04 if world.compassshuffle[player] else 0x00) | (0x04 if world.mapshuffle[player] else 0x00)
| (0x08 if world.mapshuffle[player] else 0x00))) # free roaming items in menu | (0x08 if world.compassshuffle[player] else 0x00))) # free roaming items in menu
# Map reveals # Map reveals
reveal_bytes = { reveal_bytes = {
@@ -1232,6 +1252,7 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_bytes(0x180185, [0,0,0]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [0,0,0]) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180188, [0,0,0]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0,0,0]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0,0,0]) # Mantle respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0,0,0]) # Mantle respawn refills (magic, bombs, arrows)
bow_max, bomb_max, magic_max = 0, 0, 0
if world.mode[player] == 'standard': if world.mode[player] == 'standard':
if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']: if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']:
rom.write_byte(0x18004E, 1) # Escape Fill (arrows) rom.write_byte(0x18004E, 1) # Escape Fill (arrows)
@@ -1239,16 +1260,25 @@ def patch_rom(world, rom, player, team, enemized):
rom.write_bytes(0x180185, [0,0,70]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [0,0,70]) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180188, [0,0,10]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0,0,10]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0,0,10]) # Mantle respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0,0,10]) # Mantle respawn refills (magic, bombs, arrows)
bow_max = 70
elif uncle_location.item is not None and uncle_location.item.name in ['Bombs (10)']: elif uncle_location.item is not None and uncle_location.item.name in ['Bombs (10)']:
rom.write_byte(0x18004E, 2) # Escape Fill (bombs) rom.write_byte(0x18004E, 2) # Escape Fill (bombs)
rom.write_bytes(0x180185, [0,50,0]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [0,50,0]) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180188, [0,3,0]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0,3,0]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0,3,0]) # Mantle respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0,3,0]) # Mantle respawn refills (magic, bombs, arrows)
bomb_max = 50
elif uncle_location.item is not None and uncle_location.item.name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']: elif uncle_location.item is not None and uncle_location.item.name in ['Cane of Somaria', 'Cane of Byrna', 'Fire Rod']:
rom.write_byte(0x18004E, 4) # Escape Fill (magic) rom.write_byte(0x18004E, 4) # Escape Fill (magic)
rom.write_bytes(0x180185, [0x80,0,0]) # Uncle respawn refills (magic, bombs, arrows) rom.write_bytes(0x180185, [0x80,0,0]) # Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180188, [0x20,0,0]) # Zelda respawn refills (magic, bombs, arrows) rom.write_bytes(0x180188, [0x20,0,0]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0x20,0,0]) # Mantle respawn refills (magic, bombs, arrows) rom.write_bytes(0x18018B, [0x20,0,0]) # Mantle respawn refills (magic, bombs, arrows)
magic_max = 0x80
if world.doorShuffle[player] == 'crossed':
# Uncle respawn refills (magic, bombs, arrows)
rom.write_bytes(0x180185, [max(0x20, magic_max), max(3, bomb_max), max(10, bow_max)])
rom.write_bytes(0x180188, [0x20, 3, 10]) # Zelda respawn refills (magic, bombs, arrows)
rom.write_bytes(0x18018B, [0x20, 3, 10]) # Mantle respawn refills (magic, bombs, arrows)
# patch swamp: Need to enable permanent drain of water as dam or swamp were moved # patch swamp: Need to enable permanent drain of water as dam or swamp were moved
rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00) rom.write_byte(0x18003D, 0x01 if world.swamp_patch_required[player] else 0x00)
+39 -3
View File
@@ -35,8 +35,6 @@ def is_bundled():
return getattr(sys, 'frozen', False) return getattr(sys, 'frozen', False)
def local_path(path): def local_path(path):
return path
if local_path.cached_path is not None: if local_path.cached_path is not None:
return os.path.join(local_path.cached_path, path) return os.path.join(local_path.cached_path, path)
@@ -269,17 +267,54 @@ def print_wiki_doors_by_region(d_regions, world, player):
f.write(toprint) f.write(toprint)
def update_deprecated_args(args): def update_deprecated_args(args):
if args:
argVars = vars(args) argVars = vars(args)
truthy = [ 1, True, "True", "true" ] truthy = [ 1, True, "True", "true" ]
# Hints default to TRUE
# Don't do: Yes
# Do: No
if "no_hints" in argVars:
src = "no_hints"
if isinstance(argVars["hints"],dict):
tmp = {}
for idx in range(1,len(argVars["hints"]) + 1):
tmp[idx] = argVars[src] not in truthy # tmp = !src
args.hints = tmp # dest = tmp
else:
args.hints = args.no_hints not in truthy # dest = !src
# Don't do: No
# Do: Yes
if "hints" in argVars:
src = "hints"
if isinstance(argVars["hints"],dict):
tmp = {}
for idx in range(1,len(argVars["hints"]) + 1):
tmp[idx] = argVars[src] not in truthy # tmp = !src
args.no_hints = tmp # dest = tmp
else:
args.no_hints = args.hints not in truthy # dest = !src
# Spoiler defaults to FALSE
# Don't do: No
# Do: Yes
if "create_spoiler" in argVars:
args.suppress_spoiler = not args.create_spoiler in truthy
# Don't do: Yes
# Do: No
if "suppress_spoiler" in argVars:
args.create_spoiler = not args.suppress_spoiler in truthy
# ROM defaults to TRUE
# Don't do: Yes # Don't do: Yes
# Do: No # Do: No
if "suppress_rom" in argVars: if "suppress_rom" in argVars:
args.create_rom = args.suppress_rom not in truthy args.create_rom = not args.suppress_rom in truthy
# Don't do: No # Don't do: No
# Do: Yes # Do: Yes
if "create_rom" in argVars: if "create_rom" in argVars:
args.suppress_rom = not args.create_rom in truthy args.suppress_rom = not args.create_rom in truthy
# Shuffle Ganon defaults to TRUE
# Don't do: Yes # Don't do: Yes
# Do: No # Do: No
if "no_shuffleganon" in argVars: if "no_shuffleganon" in argVars:
@@ -289,6 +324,7 @@ def update_deprecated_args(args):
if "shuffleganon" in argVars: if "shuffleganon" in argVars:
args.no_shuffleganon = not args.shuffleganon in truthy args.no_shuffleganon = not args.shuffleganon in truthy
# Playthrough defaults to TRUE
# Don't do: Yes # Don't do: Yes
# Do: No # Do: No
if "skip_playthrough" in argVars: if "skip_playthrough" in argVars:
+1
View File
@@ -22,6 +22,7 @@ incsrc keydoors.asm
incsrc overrides.asm incsrc overrides.asm
incsrc edges.asm incsrc edges.asm
incsrc math.asm incsrc math.asm
incsrc hudadditions.asm
warnpc $279000 warnpc $279000
; Data Section ; Data Section
+37
View File
@@ -1,3 +1,23 @@
org $279500
TilesetTable:
; 0 1 2 3 4 5 6 7 8 9 a b c d e f --Offset Ruler
db $13,$04,$04,$06,$0d,$ff,$08,$05,$06,$07,$07,$07,$0e,$0e,$0b,$ff
db $13,$04,$04,$0d,$0d,$0d,$08,$05,$06,$07,$07,$07,$0e,$0e,$0b,$0b
db $04,$04,$04,$0d,$0d,$ff,$08,$05,$08,$09,$07,$07,$06,$ff,$0b,$06
db $04,$05,$04,$12,$08,$08,$08,$08,$08,$09,$07,$07,$06,$0e,$0b,$0b
db $04,$04,$04,$12,$0a,$0a,$08,$ff,$ff,$09,$07,$07,$0e,$0e,$0b,$0b
db $04,$04,$04,$12,$08,$01,$09,$09,$09,$09,$07,$0e,$0e,$0e,$0b,$0b
db $04,$04,$04,$12,$0a,$0a,$08,$09,$09,$ff,$07,$0e,$0e,$0e,$0b,$ff
db $04,$04,$04,$12,$12,$12,$08,$05,$ff,$ff,$ff,$0e,$0e,$0e,$0b,$0b
db $04,$04,$04,$12,$12,$12,$ff,$05,$ff,$05,$ff,$0e,$0e,$0e,$0b,$ff
db $0c,$0c,$0c,$0c,$ff,$0e,$0e,$0c,$0c,$05,$ff,$0e,$0e,$0e,$0b,$0b
db $0c,$0c,$0c,$0c,$0d,$0e,$0e,$05,$05,$05,$05,$0a,$0a,$ff,$0b,$0b
db $04,$0c,$0c,$0c,$0d,$0d,$0d,$0d,$05,$05,$05,$0a,$0a,$ff,$0b,$0b
db $04,$0c,$0c,$0c,$0d,$0d,$0d,$0d,$05,$05,$ff,$0a,$0a,$ff,$0b,$ff
db $04,$0c,$0c,$ff,$ff,$0d,$0d,$ff,$05,$05,$05,$0a,$0a,$ff,$0b,$06
db $04,$06,$06,$06,$06,$06,$06,$06,$06,$ff,$06,$06,$ff,$06,$06,$06
db $06,$06,$03,$03,$03,$03,$ff,$ff,$06,$06,$06,$06,$ff,$06,$06,$06
org $279700 org $279700
KeyDoorOffset: KeyDoorOffset:
; 0 1 2 3 4 5 6 7 8 9 a b c d e f --Offset Ruler ; 0 1 2 3 4 5 6 7 8 9 a b c d e f --Offset Ruler
@@ -545,3 +565,20 @@ db $58,$50,$30, $98,$50,$70 ; TT Ambush
db $58,$50,$30 ; TT Nook db $58,$50,$30 ; TT Nook
MultDivInfo: ; (1placeholder, 1, 2, 3, 4, 5, 6, 10, 20) MultDivInfo: ; (1placeholder, 1, 2, 3, 4, 5, 6, 10, 20)
db $01, $01, $02, $03, $04, $05, $06, $0a, $14 db $01, $01, $02, $03, $04, $05, $06, $0a, $14
; dungeon tables
; HC HC EP DP AT SP PD MM SW IP TH TT TR GT
org $27f000
CompassBossIndicator:
dw $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000
TotalKeys: ;27f01c
db $04, $04, $02, $04, $04, $06, $06, $06, $05, $06, $01, $03, $06, $08
ChestKeys: ;27f02a
db $01, $01, $00, $01, $02, $01, $06, $03, $03, $02, $01, $01, $04, $04
BigKeyStatus: ;27f038 (status 2 indicate BnC guard)
dw $0002, $0002, $0001, $0001, $0000, $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001, $0001
DungeonReminderTable: ;27f054
dw $2D50, $2D50, $2D51, $2D52, $2D54, $2D56, $2D55, $2D5A, $2D57, $2D59, $2D53, $2D58, $2D5B, $2D5C
;27f070
+30 -2
View File
@@ -38,7 +38,7 @@ org $029396 ; <- 11384 - Bank02.asm : 3641 (LDA $01C322, X)
jsl StraightStairLayerFix jsl StraightStairLayerFix
; Graphics fix ; Graphics fix
org $02895d org $02895d ; Bank 02 line 1812 (JSL Dungeon_LoadRoom : JSL Dungeon_InitStarTileChr : JSL $00D6F9 : INC $B0)
Splicer: Splicer:
jsl GfxFixer jsl GfxFixer
lda $b1 : beq .done lda $b1 : beq .done
@@ -46,10 +46,14 @@ rts
nop #5 nop #5
.done .done
org $00fda4 org $00d377 ;Bank 00 line 3185
DecompDungAnimatedTiles:
org $00fda4 ;Bank 00 line 8882
Dungeon_InitStarTileCh: Dungeon_InitStarTileCh:
org $00d6ae ;(PC: 56ae) org $00d6ae ;(PC: 56ae)
LoadTransAuxGfx: LoadTransAuxGfx:
org $00d739 ;
LoadTransAuxGfx_Alt:
org $00df5a ;(PC: 5f5a) org $00df5a ;(PC: 5f5a)
PrepTransAuxGfx: PrepTransAuxGfx:
org $0ffd65 ;(PC: 07fd65) org $0ffd65 ;(PC: 07fd65)
@@ -86,7 +90,31 @@ org $2081f2
jsl MirrorCheckOverride2 jsl MirrorCheckOverride2
org $20825c org $20825c
jsl MirrorCheckOverride2 jsl MirrorCheckOverride2
org $07a955 ; <- Bank07.asm : around 6564 (JP is a bit different) (STZ $05FC : STZ $05FD)
jsl BlockEraseFix
nop #2
org $02b82a
jsl FixShopCode
org $1ddeea ; <- Bank1D.asm : 286 (JSL Sprite_LoadProperties)
jsl VitreousKeyReset
org $1ed024 ; f5024 sprite_guruguru_bar.asm : 27 (LDA $040C : CMP.b #$12 : INY #2
jsl GuruguruFix : bra .next
nop #3
.next
; also rando's hooks.asm line 1360
org $a0ee11 ; <- 6FC4C - headsup_display.asm : 836 (LDA $7EF36E : AND.w #$00FF : ADD.w #$0007 : AND.w #$FFF8 : TAX)
jsl DrHudOverride
org $098638 ; rando's hooks.asm line 2192
jsl CountChestKeys
org $06D192 ; rando's hooks.asm line 457
jsl CountAbsorbedKeys
; rando's hooks.asm line 1020
org $05FC7E ; <- 2FC7E - sprite_dash_item.asm : 118 (LDA $7EF36F : INC A : STA $7EF36F)
jsl CountBonkItem
; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire ; These two, if enabled together, have implications for vanilla BK doors in IP/Hera/Mire
; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes ; IPBJ is common enough to consider not doing this. Mire is not a concern for vanilla - maybe glitched modes
+14 -1
View File
@@ -1,11 +1,24 @@
GfxFixer: GfxFixer:
{ {
lda $b1 : bne .stage2 lda DRMode : bne +
jsl LoadRoomHook ;this is the code we overwrote
jsl Dungeon_InitStarTileCh
jsl LoadTransAuxGfx_Alt
inc $b0
rtl
+ lda $b1 : bne .stage2
jsl LoadRoomHook ; this is the rando version - let's only call this guy once - may fix star tiles and slower loads jsl LoadRoomHook ; this is the rando version - let's only call this guy once - may fix star tiles and slower loads
jsl Dungeon_InitStarTileCh jsl Dungeon_InitStarTileCh
jsl LoadTransAuxGfx jsl LoadTransAuxGfx
jsl Dungeon_LoadCustomTileAttr jsl Dungeon_LoadCustomTileAttr
jsl PrepTransAuxGfx jsl PrepTransAuxGfx
lda DRMode : cmp #$02 : bne + ; only do this in crossed mode
ldx $a0 : lda TilesetTable, x
cmp $0aa1 : beq + ; already eq no need to decomp
sta $0aa1
tax : lda $02802e, x : tay
jsl DecompDungAnimatedTiles
+
lda #$09 : sta $17 : sta $0710 lda #$09 : sta $17 : sta $0710
jsl Palette_SpriteAux3 jsl Palette_SpriteAux3
jsl Palette_SpriteAux2 jsl Palette_SpriteAux2
+97
View File
@@ -0,0 +1,97 @@
DrHudOverride:
{
jsl.l NewDrawHud
jsr HudAdditions
rtl
}
HudAdditions:
{
ldx $040c : cpx #$ff : bne + : rts : +
lda DRMode : bne + : rts : +
phb : phk : plb
lda $7ef364 : and.l $0098c0, x : beq +
lda CompassBossIndicator, x : and #$00ff : cmp $a0 : bne +
lda $1a : and #$0010 : beq +
lda #$345e : sta $7ec790 : bra .next
+ lda #$207f : sta $7ec790
.next lda DRMode : and #$0002 : bne + : plb : rts : +
lda $7ef36d : and #$00ff : beq +
lda DungeonReminderTable, x : bra .reminder
+ lda #$207f
.reminder sta $7ec702
+ lda DRFlags : and #$0004 : beq .restore
lda $7ef368 : and.l $0098c0, x : beq .restore
lda #$2811 : sta $7ec740
lda $7ef366 : and.l $0098c0, x : bne .check
lda BigKeyStatus, x : bne + ; change this, if bk status changes to one byte
lda #$2574 : bra ++
+ cmp #$0002 : bne +
lda #$2420 : bra ++
+ lda #$207f : bra ++
.check lda #$2826
++ sta $7ec742
txa : lsr : tax
lda $7ef4e0, x : jsr ConvertToDisplay : sta $7ec7a2
lda #$2830 : sta $7ec7a4
lda ChestKeys, x : jsr ConvertToDisplay : sta $7ec7a6
lda #$2871 : sta $7ec780
lda TotalKeys, x
sep #$20 : !sub $7ef4b0, x : rep #$20
jsr ConvertToDisplay : sta $7ec782
.restore
plb : rts
}
ConvertToDisplay:
and #$00ff : cmp #$000a : !blt +
!add #$2553 : rts
+ !add #$2490 : rts
CountChestKeys:
jsl ItemDowngradeFix
jsr CountChest
rtl
CountChest:
lda !MULTIWORLD_ITEM_PLAYER_ID : bne .end
cpy #$24 : beq +
cpy #$a0 : !blt .end
cpy #$ae : !bge .end
pha : phx
tya : and #$0f : bne ++
inc a
++ tax : bra .count
+ pha : phx
lda $040c : lsr : tax
.count
lda $7ef4b0, x : inc : sta $7ef4b0, x
lda $7ef4e0, x : inc : sta $7ef4e0, x
.restore plx : pla
.end rts
CountAbsorbedKeys:
jsl IncrementSmallKeysNoPrimary : phx
lda $040c : cmp #$ff : beq +
lsr : tax
lda $7ef4b0, x : inc : sta $7ef4b0, x
+ plx : rtl
CountBonkItem:
jsl GiveBonkItem
lda $a0 ; check room ID - only bonk keys in 2 rooms so we're just checking the lower byte
cmp #115 : bne + ; Desert Bonk Key
lda.l BonkKey_Desert
bra ++
+ : cmp #140 : bne + ; GTower Bonk Key
lda.l BonkKey_GTower
bra ++
+ lda.b #$24 ; default to small key
++
phy : tay : jsr CountChest : ply
rtl
+2 -1
View File
@@ -12,8 +12,9 @@
CheckIfDoorsOpen: { CheckIfDoorsOpen: {
jsr TrapDoorFixer ; see normal.asm jsr TrapDoorFixer ; see normal.asm
; note we are 16bit mode right now ; note we are 16bit mode right now
lda DRMode : beq +
lda $040c : cmp #$00ff : bne .gtg lda $040c : cmp #$00ff : bne .gtg
lda $a0 : dec : tax : and #$000f ; hijacked code + lda $a0 : dec : tax : and #$000f ; hijacked code
sec : rtl ; set carry to indicate normal behavior sec : rtl ; set carry to indicate normal behavior
.gtg .gtg
+23
View File
@@ -51,3 +51,26 @@ MirrorCheckOverride:
MirrorCheckOverride2: MirrorCheckOverride2:
lda $7ef353 : and #$02 : rtl lda $7ef353 : and #$02 : rtl
BlockEraseFix:
lda $7ef353 : and #$02 : beq +
stz $05fc : stz $05fd
+ rtl
FixShopCode:
cpx #$300 : !bge +
sta $7ef000, x
+ rtl
VitreousKeyReset:
lda DRMode : beq +
stz $0cba, x
+ jsl $0db818 ;restore old code
rtl
GuruguruFix:
lda $a0 : cmp #$df : !bge +
and #$0f : cmp #$0e : !blt +
iny #2
+ rtl
+11
View File
@@ -7,6 +7,7 @@ RecordStairType: {
} }
SpiralWarp: { SpiralWarp: {
lda DRMode : beq .abort ; abort if not DR
lda $040c : cmp.b #$ff : beq .abort ; abort if not in dungeon lda $040c : cmp.b #$ff : beq .abort ; abort if not in dungeon
lda $045e : cmp #$5e : beq .gtg ; abort if not spiral - intended room is in A! lda $045e : cmp #$5e : beq .gtg ; abort if not spiral - intended room is in A!
cmp #$5f : beq .gtg cmp #$5f : beq .gtg
@@ -66,6 +67,16 @@ SpiralWarp: {
ldy #$01 : jsr SetCamera ldy #$01 : jsr SetCamera
stz $045e ; clear the staircase flag stz $045e ; clear the staircase flag
; animated tiles fix
lda DRMode : cmp #$02 : bne + ; only do this in crossed mode
ldx $a0 : lda TilesetTable, x
cmp $0aa1 : beq + ; already eq no need to decomp
sta $0aa1
tax : lda $02802e, x : tay
jsl DecompDungAnimatedTiles
+
ply : plx : plb ; pull the stuff we pushed ply : plx : plb ; pull the stuff we pushed
lda $a2 : and #$0f ; this is the code we are hijacking lda $a2 : and #$0f ; this is the code we are hijacking
rtl rtl
+4 -1
View File
@@ -3,6 +3,9 @@ import os
import shutil import shutil
import sys import sys
# Spec file
SPEC_FILE = os.path.join("DungeonRandomizer.spec")
# Destination is current dir # Destination is current dir
DEST_DIRECTORY = '.' DEST_DIRECTORY = '.'
@@ -16,7 +19,7 @@ if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.
shutil.rmtree("build") shutil.rmtree("build")
# Run pyinstaller for DungeonRandomizer # Run pyinstaller for DungeonRandomizer
subprocess.run(" ".join(["pyinstaller DungeonRandomizer.spec ", subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ",
upx_string, upx_string,
"-y ", "-y ",
"--onefile ", "--onefile ",
+4 -1
View File
@@ -3,6 +3,9 @@ import os
import shutil import shutil
import sys import sys
# Spec file
SPEC_FILE = os.path.join("Gui.spec")
# Destination is current dir # Destination is current dir
DEST_DIRECTORY = '.' DEST_DIRECTORY = '.'
@@ -16,7 +19,7 @@ if os.path.isdir("build") and not sys.platform.find("mac") and not sys.platform.
shutil.rmtree("build") shutil.rmtree("build")
# Run pyinstaller for Gui # Run pyinstaller for Gui
subprocess.run(" ".join(["pyinstaller Gui.spec ", subprocess.run(" ".join([f"pyinstaller {SPEC_FILE} ",
upx_string, upx_string,
"-y ", "-y ",
"--onefile ", "--onefile ",
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
aioconsole==0.1.15
colorama==0.4.3
websockets==8.1
+15 -1
View File
@@ -1,9 +1,18 @@
{ {
"lang": {}, "lang": {},
"diags": {
"action": "store_true",
"type": "bool"
},
"create_spoiler": { "create_spoiler": {
"action": "store_true", "action": "store_true",
"type": "bool" "type": "bool"
}, },
"suppress_spoiler": {
"action": "store_false",
"dest": "create_spoiler",
"help": "suppress"
},
"logic": { "logic": {
"choices": [ "choices": [
"noglitches", "noglitches",
@@ -195,9 +204,14 @@
] ]
}, },
"hints": { "hints": {
"action": "store_true", "action": "store_false",
"type": "bool" "type": "bool"
}, },
"no_hints": {
"action": "store_true",
"dest": "hints",
"help": "suppress"
},
"heartbeep": { "heartbeep": {
"choices": [ "choices": [
"normal", "normal",
+1
View File
@@ -40,6 +40,7 @@
"made.rom": "Patched ROM: %s", "made.rom": "Patched ROM: %s",
"made.playthrough": "Printed Playthrough: %s", "made.playthrough": "Printed Playthrough: %s",
"made.spoiler": "Printed Spoiler: %s", "made.spoiler": "Printed Spoiler: %s",
"used.enemizer": "Enemized: %s",
"done": "Done. Enjoy.", "done": "Done. Enjoy.",
"total.time": "Total Time: %s", "total.time": "Total Time: %s",
"finished.run": "Finished run", "finished.run": "Finished run",
+1 -1
View File
@@ -154,7 +154,7 @@
"randomizer.gameoptions.sprite.unchanged": "(unchanged)", "randomizer.gameoptions.sprite.unchanged": "(unchanged)",
"randomizer.generation.spoiler": "Create Spoiler Log", "randomizer.generation.createspoiler": "Create Spoiler Log",
"randomizer.generation.createrom": "Create Patched ROM", "randomizer.generation.createrom": "Create Patched ROM",
"randomizer.generation.calcplaythrough": "Calculate Playthrough", "randomizer.generation.calcplaythrough": "Calculate Playthrough",
"randomizer.generation.usestartinventory": "Use Starting Inventory", "randomizer.generation.usestartinventory": "Use Starting Inventory",
@@ -1,6 +1,6 @@
{ {
"checkboxes": { "checkboxes": {
"spoiler": { "type": "checkbox" }, "createspoiler": { "type": "checkbox" },
"createrom": { "type": "checkbox" }, "createrom": { "type": "checkbox" },
"calcplaythrough": { "type": "checkbox" }, "calcplaythrough": { "type": "checkbox" },
"usestartinventory": { "type": "checkbox" }, "usestartinventory": { "type": "checkbox" },
+1 -1
View File
@@ -23,7 +23,7 @@ def prepare_env():
# get app version # get app version
APP_VERSION = "" APP_VERSION = ""
APP_VERSION_FILE = "./resources/app/meta/manifests/app_version.txt" APP_VERSION_FILE = os.path.join(".","resources","app","meta","manifests","app_version.txt")
if os.path.isfile(APP_VERSION_FILE): if os.path.isfile(APP_VERSION_FILE):
with open(APP_VERSION_FILE,"r") as f: with open(APP_VERSION_FILE,"r") as f:
APP_VERSION = f.readlines()[0].strip() APP_VERSION = f.readlines()[0].strip()
+6 -6
View File
@@ -6,7 +6,7 @@ from shutil import unpack_archive
# only do stuff if we don't have a UPX folder # only do stuff if we don't have a UPX folder
if not os.path.isdir("./upx"): if not os.path.isdir(os.path.join(".","upx")):
# get env vars # get env vars
env = common.prepare_env() env = common.prepare_env()
# set up download url # set up download url
@@ -25,7 +25,7 @@ if not os.path.isdir("./upx"):
print("Getting UPX: " + UPX_FILE) print("Getting UPX: " + UPX_FILE)
with open("./" + UPX_FILE,"wb") as upx: with open(os.path.join(".",UPX_FILE),"wb") as upx:
UPX_REQ = urllib.request.Request( UPX_REQ = urllib.request.Request(
UPX_URL, UPX_URL,
data=None data=None
@@ -34,9 +34,9 @@ if not os.path.isdir("./upx"):
UPX_DATA = UPX_REQ.read() UPX_DATA = UPX_REQ.read()
upx.write(UPX_DATA) upx.write(UPX_DATA)
unpack_archive(UPX_FILE,"./") unpack_archive(UPX_FILE,os.path.join("."))
os.rename("./" + UPX_SLUG,"./upx") os.rename(os.path.join(".",UPX_SLUG),os.path.join(".","upx"))
os.remove("./" + UPX_FILE) os.remove(os.path.join(".",UPX_FILE))
print("UPX should " + ("not " if not os.path.isdir("./upx") else "") + "be available.") print("UPX should " + ("not " if not os.path.isdir(os.path.join(".","upx")) else "") + "be available.")
+12 -3
View File
@@ -1,19 +1,28 @@
import subprocess # do stuff at the shell level import subprocess # do stuff at the shell level
import os
def git_clean(): def git_clean(clean_ignored=True, clean_user=False):
excludes = [ excludes = [
".vscode", # vscode IDE files ".vscode", # vscode IDE files
".idea", # idea IDE files ".idea", # idea IDE files
"*.json", # keep JSON files for that one time I just nuked all that I was working on, oops "*.json", # keep JSON files for that one time I just nuked all that I was working on, oops
"*app*version.*" # keep appversion files "*app*version.*", # keep appversion files
"EnemizerCLI" # keep EnemizerCLI files
] ]
if not clean_user:
excludes.append(os.path.join("resources","user*")) # keep user resources
excludes = ['--exclude={0}'.format(exclude) for exclude in excludes] excludes = ['--exclude={0}'.format(exclude) for exclude in excludes]
# d: directories, f: files, x: ignored files
switches = "df" + ("x" if clean_ignored else "")
# clean the git slate # clean the git slate
subprocess.check_call([ subprocess.check_call([
"git", # run a git command "git", # run a git command
"clean", # clean command "clean", # clean command
"-dfx", # d: directories, f: files, x: ignored files "-" + switches,
*excludes]) *excludes])
if __name__ == "__main__": if __name__ == "__main__":
+3
View File
@@ -0,0 +1,3 @@
from git_clean import git_clean
git_clean(clean_user=True)
+13 -12
View File
@@ -32,12 +32,13 @@ class SpriteSelector(object):
webbrowser.open("http://alttpr.com/sprite_preview") webbrowser.open("http://alttpr.com/sprite_preview")
def open_unofficial_sprite_dir(_evt): def open_unofficial_sprite_dir(_evt):
if not os.path.isdir(self.unofficial_sprite_dir):
os.makedirs(self.unofficial_sprite_dir)
open_file(self.unofficial_sprite_dir) open_file(self.unofficial_sprite_dir)
# Open SpriteSomething directory for Link sprites # Open SpriteSomething directory for Link sprites
def open_spritesomething_listing(_evt): def open_spritesomething_listing(_evt):
webbrowser.open("https://artheau.github.io/SpriteSomething/?mode=zelda3/link") webbrowser.open("https://artheau.github.io/SpriteSomething/resources/app/snes/zelda3/link/sprites.html")
# webbrowser.open("https://artheau.github.io/SpriteSomething/resources/app/snes/zelda3/link/sprites.html")
official_frametitle = Frame(self.window) official_frametitle = Frame(self.window)
official_title_text = Label(official_frametitle, text="Official Sprites") official_title_text = Label(official_frametitle, text="Official Sprites")
@@ -57,8 +58,8 @@ class SpriteSelector(object):
spritesomething_title_link.pack(side=LEFT) spritesomething_title_link.pack(side=LEFT)
spritesomething_title_link.bind("<Button-1>", open_spritesomething_listing) spritesomething_title_link.bind("<Button-1>", open_spritesomething_listing)
self.icon_section(official_frametitle, self.official_sprite_dir+'/*', 'Official sprites not found. Click "Update official sprites" to download them.') self.icon_section(official_frametitle, os.path.join(self.official_sprite_dir,"*"), 'Official sprites not found. Click "Update official sprites" to download them.')
self.icon_section(unofficial_frametitle, self.unofficial_sprite_dir+'/*', 'Put sprites in the unofficial sprites folder (see open link above) to have them appear here.') self.icon_section(unofficial_frametitle, os.path.join(self.unofficial_sprite_dir,"*"), 'Put sprites in the unofficial sprites folder (see open link above) to have them appear here.')
frame = Frame(self.window) frame = Frame(self.window)
frame.pack(side=BOTTOM, fill=X, pady=5) frame.pack(side=BOTTOM, fill=X, pady=5)
@@ -150,10 +151,10 @@ class SpriteSelector(object):
try: try:
task.update_status("Determining needed sprites") task.update_status("Determining needed sprites")
current_sprites = [os.path.basename(file) for file in glob(self.official_sprite_dir+'/*')] current_sprites = [os.path.basename(file) for file in glob(os.path.join(self.official_sprite_dir,"*"))]
official_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr] official_sprites = [(sprite['file'], os.path.basename(urlparse(sprite['file']).path)) for sprite in sprites_arr]
needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in official_sprites if filename not in current_sprites] needed_sprites = [(sprite_url, filename) for (sprite_url, filename) in official_sprites if filename not in current_sprites]
bundled_sprites = [os.path.basename(file) for file in glob(self.local_official_sprite_dir+'/*')] bundled_sprites = [os.path.basename(file) for file in glob(os.path.join(self.unofficial_sprite_dir,"*"))]
# todo: eventually use the above list to avoid downloading any sprites that we already have cached in the bundle. # todo: eventually use the above list to avoid downloading any sprites that we already have cached in the bundle.
official_filenames = [filename for (_, filename) in official_sprites] official_filenames = [filename for (_, filename) in official_sprites]
@@ -230,23 +231,23 @@ class SpriteSelector(object):
@property @property
def official_sprite_dir(self): def official_sprite_dir(self):
if is_bundled(): # if is_bundled():
return output_path("sprites/official") # return output_path(os.path.join("sprites","official"))
return self.local_official_sprite_dir return self.local_official_sprite_dir
@property @property
def local_official_sprite_dir(self): def local_official_sprite_dir(self):
return local_path("data/sprites/official") return local_path(os.path.join("data","sprites","official"))
@property @property
def unofficial_sprite_dir(self): def unofficial_sprite_dir(self):
if is_bundled(): # if is_bundled():
return output_path("sprites/unofficial") # return output_path(os.path.join("sprites","unofficial"))
return self.local_unofficial_sprite_dir return self.local_unofficial_sprite_dir
@property @property
def local_unofficial_sprite_dir(self): def local_unofficial_sprite_dir(self):
return local_path("data/sprites/unofficial") return local_path(os.path.join("data","sprites","unofficial"))
def get_image_for_sprite(sprite): def get_image_for_sprite(sprite):
+1 -1
View File
@@ -101,7 +101,7 @@ SETTINGSTOPROCESS = {
"uwpalettes": "uw_palettes" "uwpalettes": "uw_palettes"
}, },
"generation": { "generation": {
"spoiler": "create_spoiler", "createspoiler": "create_spoiler",
"createrom": "create_rom", "createrom": "create_rom",
"calcplaythrough": "calc_playthrough", "calcplaythrough": "calc_playthrough",
"usestartinventory": "usestartinventory", "usestartinventory": "usestartinventory",
+47
View File
@@ -0,0 +1,47 @@
import platform, sys, os, subprocess
import pkg_resources
from datetime import datetime
def diagpad(str):
return str.ljust(len("ALttP Door Randomizer Version") + 5,'.')
def output(APP_VERSION):
lines = [
"ALttP Door Randomizer Diagnostics",
"=================================",
diagpad("UTC Time") + str(datetime.utcnow())[:19],
diagpad("ALttP Door Randomizer Version") + APP_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 = [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])
return lines
if __name__ == "__main__":
raise AssertionError(f"Called main() on utility library {__file__}")
+1 -1
View File
@@ -104,7 +104,7 @@ def adjust_page(top, parent, settings):
arg = options[option] arg = options[option]
setattr(guiargs, arg, self.widgets[option].storageVar.get()) setattr(guiargs, arg, self.widgets[option].storageVar.get())
guiargs.rom = self.romVar2.get() guiargs.rom = self.romVar2.get()
guiargs.baserom = top.pages["randomizer"].pages["generation"].romVar.get() guiargs.baserom = top.pages["randomizer"].pages["generation"].widgets["rom"].storageVar.get()
guiargs.sprite = self.sprite guiargs.sprite = self.sprite
try: try:
adjust(args=guiargs) adjust(args=guiargs)
+2
View File
@@ -115,6 +115,7 @@ def bottom_frame(self, parent, args=None):
made = {} made = {}
for k in [ "rom", "playthrough", "spoiler" ]: for k in [ "rom", "playthrough", "spoiler" ]:
made[k] = parent.fish.translate("cli","cli","made." + k) made[k] = parent.fish.translate("cli","cli","made." + k)
made["enemizer"] = parent.fish.translate("cli","cli","used.enemizer")
for k in made: for k in made:
v = made[k] v = made[k]
pattern = "([\w]+)(:)([\s]+)(.*)" pattern = "([\w]+)(:)([\s]+)(.*)"
@@ -123,6 +124,7 @@ def bottom_frame(self, parent, args=None):
successMsg += (made["rom"] % (YES if (guiargs.create_rom) else NO)) + "\n" successMsg += (made["rom"] % (YES if (guiargs.create_rom) else NO)) + "\n"
successMsg += (made["playthrough"] % (YES if (guiargs.calc_playthrough) else NO)) + "\n" successMsg += (made["playthrough"] % (YES if (guiargs.calc_playthrough) else NO)) + "\n"
successMsg += (made["spoiler"] % (YES if (not guiargs.jsonout and guiargs.create_spoiler) else NO)) + "\n" successMsg += (made["spoiler"] % (YES if (not guiargs.jsonout and guiargs.create_spoiler) else NO)) + "\n"
successMsg += (made["enemizer"] % (YES if needEnemizer else NO)) + "\n"
# FIXME: English # FIXME: English
successMsg += ("Seed%s: %s" % ('s' if len(seeds) > 1 else "", ','.join(str(x) for x in seeds))) successMsg += ("Seed%s: %s" % ('s' if len(seeds) > 1 else "", ','.join(str(x) for x in seeds)))