Merge in Unstable changes
This commit is contained in:
@@ -36,6 +36,7 @@ def main():
|
||||
parser.add_argument('--ow_palettes', default='default', choices=['default', 'random', 'blackout'])
|
||||
parser.add_argument('--uw_palettes', default='default', choices=['default', 'random', 'blackout'])
|
||||
parser.add_argument('--reduce_flashing', help='Reduce some in-game flashing.', action='store_true')
|
||||
parser.add_argument('--shuffle_sfx', help='Shuffles sound sfx', action='store_true')
|
||||
parser.add_argument('--sprite', help='''\
|
||||
Path to a sprite sheet to use for Link. Needs to be in
|
||||
binary format and have a length of 0x7000 (28672) bytes,
|
||||
|
||||
@@ -25,7 +25,7 @@ def adjust(args):
|
||||
args.sprite = None
|
||||
|
||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
|
||||
args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing)
|
||||
args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx)
|
||||
|
||||
output_path.cached_path = args.outputpath
|
||||
rom.write_to_file(output_path('%s.sfc' % outfilebase))
|
||||
|
||||
@@ -114,6 +114,7 @@ class World(object):
|
||||
set_player_attr('compassshuffle', False)
|
||||
set_player_attr('keyshuffle', False)
|
||||
set_player_attr('bigkeyshuffle', False)
|
||||
set_player_attr('bomblogic', False)
|
||||
set_player_attr('difficulty_requirements', None)
|
||||
set_player_attr('boss_shuffle', 'none')
|
||||
set_player_attr('enemy_shuffle', 'none')
|
||||
@@ -1031,8 +1032,7 @@ class CollectionState(object):
|
||||
|
||||
# In the future, this can be used to check if the player starts without bombs
|
||||
def can_use_bombs(self, player):
|
||||
StartingBombs = True
|
||||
return StartingBombs or self.has('Bomb Upgrade (+10)', player)
|
||||
return (not self.world.bomblogic[player] or self.has('Bomb Upgrade (+10)', player))
|
||||
|
||||
def can_hit_crystal(self, player):
|
||||
return (self.can_use_bombs(player)
|
||||
@@ -1064,6 +1064,7 @@ class CollectionState(object):
|
||||
def can_get_good_bee(self, player):
|
||||
cave = self.world.get_region('Good Bee Cave', player)
|
||||
return (
|
||||
self.can_use_bombs(player) and
|
||||
self.has_bottle(player) and
|
||||
self.has('Bug Catching Net', player) and
|
||||
(self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and
|
||||
@@ -2372,6 +2373,7 @@ class Spoiler(object):
|
||||
'logic': self.world.logic,
|
||||
'mode': self.world.mode,
|
||||
'retro': self.world.retro,
|
||||
'bomblogic': self.world.bomblogic,
|
||||
'weapons': self.world.swords,
|
||||
'goal': self.world.goal,
|
||||
'shuffle': self.world.shuffle,
|
||||
@@ -2470,6 +2472,7 @@ class Spoiler(object):
|
||||
outfile.write('Experimental: %s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
|
||||
outfile.write('Key Drops shuffled: %s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No'))
|
||||
outfile.write(f"Shopsanity: {'Yes' if self.metadata['shopsanity'][player] else 'No'}\n")
|
||||
outfile.write('Bomblogic: %s\n' % ('Yes' if self.metadata['bomblogic'][player] else 'No'))
|
||||
if self.doors:
|
||||
outfile.write('\n\nDoors:\n\n')
|
||||
outfile.write('\n'.join(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
import random
|
||||
import RaceRandom as random
|
||||
|
||||
from BaseClasses import Boss
|
||||
from Fill import FillError
|
||||
|
||||
5
CLI.py
5
CLI.py
@@ -96,13 +96,14 @@ def parse_cli(argv, no_defaults=False):
|
||||
for name in ['logic', 'mode', 'swords', 'goal', 'difficulty', 'item_functionality',
|
||||
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||
'bomblogic',
|
||||
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
|
||||
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots',
|
||||
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
||||
'remote_items', 'shopsanity', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code',
|
||||
'reduce_flashing']:
|
||||
'reduce_flashing', 'shuffle_sfx']:
|
||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||
if player == 1:
|
||||
setattr(ret, name, {1: value})
|
||||
@@ -126,6 +127,7 @@ def parse_settings():
|
||||
settings = {
|
||||
"lang": "en",
|
||||
"retro": False,
|
||||
"bomblogic": False,
|
||||
"mode": "open",
|
||||
"logic": "noglitches",
|
||||
"goal": "ganon",
|
||||
@@ -190,6 +192,7 @@ def parse_settings():
|
||||
"ow_palettes": "default",
|
||||
"uw_palettes": "default",
|
||||
"reduce_flashing": False,
|
||||
"shuffle_sfx": False,
|
||||
|
||||
# Spoiler defaults to TRUE
|
||||
# Playthrough defaults to TRUE
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import random
|
||||
import RaceRandom as random
|
||||
from collections import defaultdict, deque
|
||||
import logging
|
||||
import time
|
||||
@@ -99,7 +99,7 @@ def link_doors_main(world, player):
|
||||
analyze_portals(world, player)
|
||||
for portal in world.dungeon_portals[player]:
|
||||
connect_portal(portal, world, player)
|
||||
|
||||
if not world.doorShuffle[player] == 'vanilla':
|
||||
fix_big_key_doors_with_ugly_smalls(world, player)
|
||||
if world.doorShuffle[player] == 'vanilla':
|
||||
for entrance, ext in open_edges:
|
||||
@@ -1658,7 +1658,7 @@ def change_door_to_small_key(d, world, player):
|
||||
def smooth_door_pairs(world, player):
|
||||
all_doors = [x for x in world.doors if x.player == player]
|
||||
skip = set()
|
||||
bd_candidates, dashable_counts, bombable_counts = defaultdict(list), defaultdict(int), defaultdict(int)
|
||||
bd_candidates = defaultdict(list)
|
||||
for door in all_doors:
|
||||
if door.type in [DoorType.Normal, DoorType.Interior] and door not in skip and not door.entranceFlag:
|
||||
partner = door.dest
|
||||
@@ -1684,30 +1684,19 @@ def smooth_door_pairs(world, player):
|
||||
remove_pair(door, world, player)
|
||||
if type_b == DoorKind.SmallKey:
|
||||
remove_pair(door, world, player)
|
||||
elif type_a in [DoorKind.Bombable, DoorKind.Dashable] or type_b in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||
if valid_pair:
|
||||
new_type = type_a
|
||||
if type_a != type_b:
|
||||
new_type = DoorKind.Dashable if type_a == DoorKind.Dashable or type_b == DoorKind.Dashable else DoorKind.Bombable
|
||||
if type_a != new_type:
|
||||
room_a.change(door.doorListPos, new_type)
|
||||
if type_b != new_type:
|
||||
room_b.change(partner.doorListPos, new_type)
|
||||
add_pair(door, partner, world, player)
|
||||
spoiler_type = 'Bomb Door' if new_type == DoorKind.Bombable else 'Dash Door'
|
||||
world.spoiler.set_door_type(door.name + ' <-> ' + partner.name, spoiler_type, player)
|
||||
counter = bombable_counts if new_type == DoorKind.Bombable else dashable_counts
|
||||
counter[door.entrance.parent_region.dungeon] += 1
|
||||
else:
|
||||
if valid_pair:
|
||||
bd_candidates[door.entrance.parent_region.dungeon].append(door)
|
||||
elif type_a in [DoorKind.Bombable, DoorKind.Dashable] or type_b in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||
if type_a in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||
room_a.change(door.doorListPos, DoorKind.Normal)
|
||||
remove_pair(door, world, player)
|
||||
elif type_b in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||
else:
|
||||
room_b.change(partner.doorListPos, DoorKind.Normal)
|
||||
remove_pair(partner, world, player)
|
||||
elif valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey:
|
||||
bd_candidates[door.entrance.parent_region.dungeon].append(door)
|
||||
shuffle_bombable_dashable(bd_candidates, bombable_counts, dashable_counts, world, player)
|
||||
shuffle_bombable_dashable(bd_candidates, world, player)
|
||||
world.paired_doors[player] = [x for x in world.paired_doors[player] if x.pair or x.original]
|
||||
|
||||
|
||||
@@ -1744,15 +1733,15 @@ def stateful_door(door, kind):
|
||||
return False
|
||||
|
||||
|
||||
def shuffle_bombable_dashable(bd_candidates, bombable_counts, dashable_counts, world, player):
|
||||
def shuffle_bombable_dashable(bd_candidates, world, player):
|
||||
if world.doorShuffle[player] == 'basic':
|
||||
for dungeon, candidates in bd_candidates.items():
|
||||
diff = bomb_dash_counts[dungeon.name][1] - dashable_counts[dungeon]
|
||||
diff = bomb_dash_counts[dungeon.name][1]
|
||||
if diff > 0:
|
||||
for chosen in random.sample(candidates, min(diff, len(candidates))):
|
||||
change_pair_type(chosen, DoorKind.Dashable, world, player)
|
||||
candidates.remove(chosen)
|
||||
diff = bomb_dash_counts[dungeon.name][0] - bombable_counts[dungeon]
|
||||
diff = bomb_dash_counts[dungeon.name][0]
|
||||
if diff > 0:
|
||||
for chosen in random.sample(candidates, min(diff, len(candidates))):
|
||||
change_pair_type(chosen, DoorKind.Bombable, world, player)
|
||||
@@ -1761,14 +1750,10 @@ def shuffle_bombable_dashable(bd_candidates, bombable_counts, dashable_counts, w
|
||||
remove_pair_type_if_present(excluded, world, player)
|
||||
elif world.doorShuffle[player] == 'crossed':
|
||||
all_candidates = sum(bd_candidates.values(), [])
|
||||
all_bomb_counts = sum(bombable_counts.values())
|
||||
all_dash_counts = sum(dashable_counts.values())
|
||||
if all_dash_counts < 8:
|
||||
for chosen in random.sample(all_candidates, min(8 - all_dash_counts, len(all_candidates))):
|
||||
for chosen in random.sample(all_candidates, min(8, len(all_candidates))):
|
||||
change_pair_type(chosen, DoorKind.Dashable, world, player)
|
||||
all_candidates.remove(chosen)
|
||||
if all_bomb_counts < 12:
|
||||
for chosen in random.sample(all_candidates, min(12 - all_bomb_counts, len(all_candidates))):
|
||||
for chosen in random.sample(all_candidates, min(12, len(all_candidates))):
|
||||
change_pair_type(chosen, DoorKind.Bombable, world, player)
|
||||
all_candidates.remove(chosen)
|
||||
for excluded in all_candidates:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import random
|
||||
import RaceRandom as random
|
||||
import collections
|
||||
import itertools
|
||||
from collections import defaultdict, deque
|
||||
|
||||
@@ -3,7 +3,7 @@ import argparse
|
||||
import copy
|
||||
import os
|
||||
import logging
|
||||
import random
|
||||
import RaceRandom as random
|
||||
import textwrap
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import random
|
||||
import RaceRandom as random
|
||||
|
||||
from BaseClasses import Dungeon
|
||||
from Bosses import BossFactory
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import random
|
||||
import RaceRandom as random
|
||||
|
||||
# ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave.
|
||||
from collections import defaultdict
|
||||
@@ -2373,7 +2373,7 @@ Cave_Exits_Base = [['Elder House Exit (East)', 'Elder House Exit (West)'],
|
||||
['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)'],
|
||||
['Fairy Ascension Cave Exit (Bottom)', 'Fairy Ascension Cave Exit (Top)'],
|
||||
['Bumper Cave Exit (Top)', 'Bumper Cave Exit (Bottom)'],
|
||||
['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']]
|
||||
['Hookshot Cave Back Exit', 'Hookshot Cave Front Exit']]
|
||||
|
||||
Cave_Exits_Base += [('Superbunny Cave Exit (Bottom)', 'Superbunny Cave Exit (Top)'),
|
||||
('Spiral Cave Exit (Top)', 'Spiral Cave Exit')]
|
||||
@@ -3115,6 +3115,10 @@ mandatory_connections = [('Links House S&Q', 'Links House'),
|
||||
('Dark Death Mountain Drop (West)', 'Dark Death Mountain (West Bottom)'),
|
||||
('East Death Mountain (Top) Mirror Spot', 'East Death Mountain (Top)'),
|
||||
('Superbunny Cave Climb', 'Superbunny Cave (Top)'),
|
||||
('Hookshot Cave Front to Middle', 'Hookshot Cave (Middle)'),
|
||||
('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'),
|
||||
('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'),
|
||||
('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'),
|
||||
('Turtle Rock Teleporter', 'Turtle Rock (Top)'),
|
||||
('Turtle Rock Drop', 'Dark Death Mountain (Top)'),
|
||||
('Floating Island Drop', 'Dark Death Mountain (Top)'),
|
||||
@@ -3233,6 +3237,10 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'),
|
||||
('Turtle Rock Tail Drop', 'Turtle Rock (Top)'),
|
||||
('Turtle Rock Drop', 'Dark Death Mountain'),
|
||||
('Superbunny Cave Climb', 'Superbunny Cave (Top)'),
|
||||
('Hookshot Cave Front to Middle', 'Hookshot Cave (Middle)'),
|
||||
('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'),
|
||||
('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'),
|
||||
('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'),
|
||||
('Desert Ledge Drop', 'Light World'),
|
||||
('Floating Island Drop', 'Dark Death Mountain'),
|
||||
('Dark Lake Hylia Central Island Teleporter', 'Lake Hylia Central Island'),
|
||||
@@ -3428,16 +3436,16 @@ default_connections = [('Links House', 'Links House'),
|
||||
('Dark Desert Hint', 'Dark Desert Hint'),
|
||||
('Dark Desert Fairy', 'Dark Desert Healer Fairy'),
|
||||
('Spike Cave', 'Spike Cave'),
|
||||
('Hookshot Cave', 'Hookshot Cave'),
|
||||
('Hookshot Cave', 'Hookshot Cave (Front)'),
|
||||
('Superbunny Cave (Top)', 'Superbunny Cave (Top)'),
|
||||
('Cave Shop (Dark Death Mountain)', 'Cave Shop (Dark Death Mountain)'),
|
||||
('Dark Death Mountain Fairy', 'Dark Death Mountain Healer Fairy'),
|
||||
('Superbunny Cave (Bottom)', 'Superbunny Cave (Bottom)'),
|
||||
('Superbunny Cave Exit (Top)', 'Dark Death Mountain (Top)'),
|
||||
('Superbunny Cave Exit (Bottom)', 'Dark Death Mountain (East Bottom)'),
|
||||
('Hookshot Cave Exit (South)', 'Dark Death Mountain (Top)'),
|
||||
('Hookshot Cave Exit (North)', 'Death Mountain Floating Island (Dark World)'),
|
||||
('Hookshot Cave Back Entrance', 'Hookshot Cave'),
|
||||
('Hookshot Cave Front Exit', 'Dark Death Mountain (Top)'),
|
||||
('Hookshot Cave Back Exit', 'Death Mountain Floating Island (Dark World)'),
|
||||
('Hookshot Cave Back Entrance', 'Hookshot Cave (Back)'),
|
||||
('Mimic Cave', 'Mimic Cave'),
|
||||
|
||||
('Pyramid Hole', 'Pyramid'),
|
||||
@@ -3562,13 +3570,13 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'
|
||||
('Dark Desert Hint', 'Dark Desert Hint'),
|
||||
('Dark Desert Fairy', 'Dark Desert Healer Fairy'),
|
||||
('Spike Cave', 'Spike Cave'),
|
||||
('Hookshot Cave', 'Hookshot Cave'),
|
||||
('Hookshot Cave', 'Hookshot Cave (Front)'),
|
||||
('Superbunny Cave (Top)', 'Superbunny Cave (Top)'),
|
||||
('Cave Shop (Dark Death Mountain)', 'Cave Shop (Dark Death Mountain)'),
|
||||
('Superbunny Cave (Bottom)', 'Superbunny Cave (Bottom)'),
|
||||
('Superbunny Cave Exit (Bottom)', 'Dark Death Mountain (East Bottom)'),
|
||||
('Hookshot Cave Exit (North)', 'Death Mountain Floating Island (Dark World)'),
|
||||
('Hookshot Cave Back Entrance', 'Hookshot Cave'),
|
||||
('Hookshot Cave Back Exit', 'Death Mountain Floating Island (Dark World)'),
|
||||
('Hookshot Cave Back Entrance', 'Hookshot Cave (Back)'),
|
||||
('Mimic Cave', 'Mimic Cave'),
|
||||
('Inverted Pyramid Hole', 'Pyramid'),
|
||||
('Inverted Links House', 'Inverted Links House'),
|
||||
@@ -3589,7 +3597,7 @@ inverted_default_connections = [('Waterfall of Wishing', 'Waterfall of Wishing'
|
||||
('Death Mountain Return Cave (East)', 'Death Mountain Return Cave'),
|
||||
('Death Mountain Return Cave Exit (West)', 'Death Mountain'),
|
||||
('Death Mountain Return Cave Exit (East)', 'Death Mountain'),
|
||||
('Hookshot Cave Exit (South)', 'Dark Death Mountain'),
|
||||
('Hookshot Cave Front Exit', 'Dark Death Mountain'),
|
||||
('Superbunny Cave Exit (Top)', 'Dark Death Mountain'),
|
||||
('Pyramid Exit', 'Light World'),
|
||||
('Inverted Pyramid Entrance', 'Bottom of Pyramid')]
|
||||
@@ -3937,8 +3945,8 @@ exit_ids = {'Links House Exit': (0x01, 0x00),
|
||||
'Bumper Cave Exit (Bottom)': (0x16, 0x17),
|
||||
'Superbunny Cave Exit (Top)': (0x14, 0x15),
|
||||
'Superbunny Cave Exit (Bottom)': (0x13, 0x14),
|
||||
'Hookshot Cave Exit (South)': (0x3A, 0x3B),
|
||||
'Hookshot Cave Exit (North)': (0x3B, 0x3C),
|
||||
'Hookshot Cave Front Exit': (0x3A, 0x3B),
|
||||
'Hookshot Cave Back Exit': (0x3B, 0x3C),
|
||||
'Ganons Tower Exit': (0x37, 0x38),
|
||||
'Inverted Ganons Tower Exit': (0x37, 0x38),
|
||||
'Pyramid Exit': (0x36, 0x37),
|
||||
|
||||
2
Fill.py
2
Fill.py
@@ -1,4 +1,4 @@
|
||||
import random
|
||||
import RaceRandom as random
|
||||
import logging
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
|
||||
9
Gui.py
9
Gui.py
@@ -24,6 +24,13 @@ from source.classes.BabelFish import BabelFish
|
||||
from source.classes.Empty import Empty
|
||||
|
||||
|
||||
def check_python_version(fish):
|
||||
import sys
|
||||
version = sys.version_info
|
||||
if version.major < 3 or version.minor < 7:
|
||||
messagebox.showinfo("Door Shuffle " + ESVersion, fish.translate("cli","cli","old.python.version") % sys.version)
|
||||
|
||||
|
||||
def guiMain(args=None):
|
||||
# Save settings to file
|
||||
def save_settings(args):
|
||||
@@ -188,6 +195,8 @@ def guiMain(args=None):
|
||||
# load adjust settings into options
|
||||
loadadjustargs(self, self.settings)
|
||||
|
||||
check_python_version(self.fish)
|
||||
|
||||
# run main window
|
||||
mainWindow.mainloop()
|
||||
|
||||
|
||||
@@ -199,8 +199,11 @@ def create_inverted_regions(world, player):
|
||||
create_cave_region(player, 'Superbunny Cave (Top)', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)']),
|
||||
create_cave_region(player, 'Superbunny Cave (Bottom)', 'a connector', None, ['Superbunny Cave Climb', 'Superbunny Cave Exit (Bottom)']),
|
||||
create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']),
|
||||
create_cave_region(player, 'Hookshot Cave', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'],
|
||||
['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']),
|
||||
create_cave_region(player, 'Hookshot Cave (Front)', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'],
|
||||
['Hookshot Cave Front to Middle', 'Hookshot Cave Front Exit']),
|
||||
create_cave_region(player, 'Hookshot Cave (Back)', 'a connector', None, ['Hookshot Cave Back to Middle', 'Hookshot Cave Back Exit']),
|
||||
create_cave_region(player, 'Hookshot Cave (Middle)', 'a connector', None, ['Hookshot Cave Middle to Back', 'Hookshot Cave Middle to Front']),
|
||||
|
||||
create_dw_region(player, 'Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance']),
|
||||
create_cave_region(player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']),
|
||||
|
||||
|
||||
43
ItemList.py
43
ItemList.py
@@ -1,7 +1,7 @@
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
import math
|
||||
import random
|
||||
import RaceRandom as random
|
||||
|
||||
from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState
|
||||
from Bosses import place_bosses
|
||||
@@ -37,7 +37,7 @@ Difficulty = namedtuple('Difficulty',
|
||||
['baseitems', 'bottles', 'bottle_count', 'same_bottle', 'progressiveshield',
|
||||
'basicshield', 'progressivearmor', 'basicarmor', 'swordless',
|
||||
'progressivesword', 'basicsword', 'basicbow', 'timedohko', 'timedother',
|
||||
'retro',
|
||||
'retro', 'bomblogic',
|
||||
'extras', 'progressive_sword_limit', 'progressive_shield_limit',
|
||||
'progressive_armor_limit', 'progressive_bottle_limit',
|
||||
'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit'])
|
||||
@@ -61,6 +61,7 @@ difficulties = {
|
||||
timedohko = ['Green Clock'] * 25,
|
||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
retro = ['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10,
|
||||
bomblogic = ['Bomb Upgrade (+10)'] * 2,
|
||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit = 4,
|
||||
progressive_shield_limit = 3,
|
||||
@@ -86,6 +87,7 @@ difficulties = {
|
||||
timedohko = ['Green Clock'] * 25,
|
||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15,
|
||||
bomblogic = ['Bomb Upgrade (+10)'] * 2,
|
||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit = 3,
|
||||
progressive_shield_limit = 2,
|
||||
@@ -111,6 +113,7 @@ difficulties = {
|
||||
timedohko = ['Green Clock'] * 20 + ['Red Clock'] * 5,
|
||||
timedother = ['Green Clock'] * 20 + ['Blue Clock'] * 10 + ['Red Clock'] * 10,
|
||||
retro = ['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 15,
|
||||
bomblogic = ['Bomb Upgrade (+10)'] * 2,
|
||||
extras = [normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit = 2,
|
||||
progressive_shield_limit = 1,
|
||||
@@ -251,10 +254,10 @@ def generate_itempool(world, player):
|
||||
|
||||
# set up item pool
|
||||
if world.custom:
|
||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.customitemarray)
|
||||
(pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon, lamps_needed_for_dark_rooms) = make_custom_item_pool(world.progressive, world.shuffle[player], world.difficulty[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bomblogic[player], world.customitemarray)
|
||||
world.rupoor_cost = min(world.customitemarray[player]["rupoorcost"], 9999)
|
||||
else:
|
||||
(pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.doorShuffle[player], world.logic[player])
|
||||
(pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms) = get_pool_core(world.progressive, world.shuffle[player], world.difficulty[player], world.treasure_hunt_total[player], world.timer, world.goal[player], world.mode[player], world.swords[player], world.retro[player], world.bomblogic[player], world.doorShuffle[player], world.logic[player])
|
||||
|
||||
if player in world.pool_adjustment.keys():
|
||||
amt = world.pool_adjustment[player]
|
||||
@@ -284,7 +287,7 @@ def generate_itempool(world, player):
|
||||
if item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']:
|
||||
if item not in possible_weapons:
|
||||
possible_weapons.append(item)
|
||||
if item in ['Bombs (10)']:
|
||||
if not world.bomblogic[player] and item in ['Bombs (10)']:
|
||||
if item not in possible_weapons and world.doorShuffle[player] != 'crossed':
|
||||
possible_weapons.append(item)
|
||||
starting_weapon = random.choice(possible_weapons)
|
||||
@@ -315,6 +318,11 @@ def generate_itempool(world, player):
|
||||
p_item = next(item for item in items if item.name == potion and item.player == player)
|
||||
p_item.priority = True # don't beemize one of each potion
|
||||
|
||||
if world.bomblogic[player]:
|
||||
for item in items:
|
||||
if item.name == 'Bomb Upgrade (+10)' and item.player == player:
|
||||
item.advancement = True
|
||||
|
||||
world.lamps_needed_for_dark_rooms = lamps_needed_for_dark_rooms
|
||||
|
||||
if clock_mode is not None:
|
||||
@@ -520,6 +528,15 @@ def set_up_shops(world, player):
|
||||
rss.locked = True
|
||||
cap_shop = world.get_region('Capacity Upgrade', player).shop
|
||||
cap_shop.inventory[1] = None # remove arrow capacity upgrades in retro
|
||||
if world.bomblogic[player]:
|
||||
if world.shopsanity[player]:
|
||||
removals = [item for item in world.itempool if item.name == 'Bomb Upgrade (+5)' and item.player == player]
|
||||
for remove in removals:
|
||||
world.itempool.remove(remove)
|
||||
world.itempool.append(ItemFactory('Rupees (50)', player)) # replace the bomb upgrade
|
||||
else:
|
||||
cap_shop = world.get_region('Capacity Upgrade', player).shop
|
||||
cap_shop.inventory[0] = cap_shop.inventory[1] # remove bomb capacity upgrades in bomblogic
|
||||
|
||||
|
||||
def customize_shops(world, player):
|
||||
@@ -561,7 +578,7 @@ def customize_shops(world, player):
|
||||
shop.shopkeeper_config = shopkeeper
|
||||
# handle capacity upgrades - randomly choose a bomb bunch or arrow bunch to become capacity upgrades
|
||||
if world.difficulty[player] == 'normal':
|
||||
if not found_bomb_upgrade and len(possible_replacements) > 0:
|
||||
if not found_bomb_upgrade and len(possible_replacements) > 0 and not world.bomblogic[player]:
|
||||
choices = []
|
||||
for shop, idx, loc, item in possible_replacements:
|
||||
if item.name in ['Bombs (3)', 'Bombs (10)']:
|
||||
@@ -709,7 +726,7 @@ rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)'
|
||||
'Rupees (100)': 100, 'Rupees (300)': 300}
|
||||
|
||||
|
||||
def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, door_shuffle, logic):
|
||||
def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bomblogic, door_shuffle, logic):
|
||||
pool = []
|
||||
placed_items = {}
|
||||
precollected_items = []
|
||||
@@ -756,6 +773,11 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
|
||||
diff = difficulties[difficulty]
|
||||
pool.extend(diff.baseitems)
|
||||
|
||||
if bomblogic:
|
||||
pool = [item.replace('Bomb Upgrade (+5)','Rupees (5)') for item in pool]
|
||||
pool = [item.replace('Bomb Upgrade (+10)','Rupees (5)') for item in pool]
|
||||
pool.extend(diff.bomblogic)
|
||||
|
||||
# expert+ difficulties produce the same contents for
|
||||
# all bottles, since only one bottle is available
|
||||
if diff.same_bottle:
|
||||
@@ -850,7 +872,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
|
||||
pool.extend(['Small Key (Universal)'])
|
||||
return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms)
|
||||
|
||||
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, customitemarray):
|
||||
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bomblogic, customitemarray):
|
||||
if isinstance(customitemarray,dict) and 1 in customitemarray:
|
||||
customitemarray = customitemarray[1]
|
||||
pool = []
|
||||
@@ -966,8 +988,9 @@ def test():
|
||||
for shuffle in ['full', 'insanity_legacy']:
|
||||
for logic in ['noglitches', 'minorglitches', 'owglitches', 'nologic']:
|
||||
for retro in [True, False]:
|
||||
for bomblogic in [True, False]:
|
||||
for door_shuffle in ['basic', 'crossed', 'vanilla']:
|
||||
out = get_pool_core(progressive, shuffle, difficulty, 30, timer, goal, mode, swords, retro, door_shuffle, logic)
|
||||
out = get_pool_core(progressive, shuffle, difficulty, 30, timer, goal, mode, swords, retro, bomblogic, door_shuffle, logic)
|
||||
count = len(out[0]) + len(out[1])
|
||||
|
||||
correct_count = total_items_to_place
|
||||
@@ -977,7 +1000,7 @@ def test():
|
||||
if retro:
|
||||
correct_count += 28
|
||||
try:
|
||||
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro))
|
||||
assert count == correct_count, "expected {0} items but found {1} items for {2}".format(correct_count, count, (progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bomblogic))
|
||||
except AssertionError as e:
|
||||
print(e)
|
||||
|
||||
|
||||
32
Main.py
32
Main.py
@@ -4,7 +4,8 @@ from itertools import zip_longest
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import RaceRandom as random
|
||||
import string
|
||||
import time
|
||||
import zlib
|
||||
|
||||
@@ -27,22 +28,32 @@ from Fill import sell_potions, sell_keys, balance_multiworld_progression, balanc
|
||||
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
|
||||
from Utils import output_path, parse_player_names
|
||||
|
||||
__version__ = '0.4.1.0-u'
|
||||
__version__ = '0.5.1.0-u'
|
||||
|
||||
from source.classes.BabelFish import BabelFish
|
||||
|
||||
|
||||
class EnemizerError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
def check_python_version():
|
||||
import sys
|
||||
version = sys.version_info
|
||||
if version.major < 3 or version.minor < 7:
|
||||
logging.warning(BabelFish().translate("cli","cli","old.python.version"), sys.version)
|
||||
|
||||
|
||||
def main(args, seed=None, fish=None):
|
||||
check_python_version()
|
||||
if args.outputpath:
|
||||
os.makedirs(args.outputpath, exist_ok=True)
|
||||
output_path.cached_path = args.outputpath
|
||||
|
||||
start = time.perf_counter()
|
||||
|
||||
# if args.securerandom:
|
||||
# random.use_secure()
|
||||
if args.securerandom:
|
||||
random.use_secure()
|
||||
|
||||
# initialize the world
|
||||
if args.code:
|
||||
@@ -61,13 +72,14 @@ def main(args, seed=None, fish=None):
|
||||
random.seed(world.seed)
|
||||
|
||||
if args.securerandom:
|
||||
world.seed = None
|
||||
world.seed = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(9))
|
||||
|
||||
world.remote_items = args.remote_items.copy()
|
||||
world.mapshuffle = args.mapshuffle.copy()
|
||||
world.compassshuffle = args.compassshuffle.copy()
|
||||
world.keyshuffle = args.keyshuffle.copy()
|
||||
world.bigkeyshuffle = args.bigkeyshuffle.copy()
|
||||
world.bomblogic = args.bomblogic.copy()
|
||||
world.crystals_needed_for_ganon = {player: random.randint(0, 7) if args.crystals_ganon[player] == 'random' else int(args.crystals_ganon[player]) for player in range(1, world.players + 1)}
|
||||
world.crystals_needed_for_gt = {player: random.randint(0, 7) if args.crystals_gt[player] == 'random' else int(args.crystals_gt[player]) for player in range(1, world.players + 1)}
|
||||
world.crystals_ganon_orig = args.crystals_ganon.copy()
|
||||
@@ -257,11 +269,11 @@ def main(args, seed=None, fish=None):
|
||||
rom = JsonRom() if args.jsonout or use_enemizer else LocalRom(args.rom)
|
||||
|
||||
if use_enemizer and (args.enemizercli or not args.jsonout):
|
||||
base_patch = LocalRom(args.rom) # update base2current.json (side effect)
|
||||
local_rom = LocalRom(args.rom) # update base2current.json (side effect)
|
||||
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)
|
||||
if os.path.exists(args.enemizercli):
|
||||
patch_enemizer(world, player, rom, args.rom, args.enemizercli, sprite_random_on_hit)
|
||||
patch_enemizer(world, player, rom, local_rom, args.enemizercli, sprite_random_on_hit)
|
||||
enemized = True
|
||||
if not args.jsonout:
|
||||
rom = LocalRom.fromJsonRom(rom, args.rom, 0x400000)
|
||||
@@ -281,7 +293,8 @@ def main(args, seed=None, fish=None):
|
||||
|
||||
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
|
||||
args.fastmenu[player], args.disablemusic[player], args.sprite[player],
|
||||
args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player])
|
||||
args.ow_palettes[player], args.uw_palettes[player], args.reduce_flashing[player],
|
||||
args.shuffle_sfx[player])
|
||||
|
||||
if args.jsonout:
|
||||
jsonout[f'patch_t{team}_p{player}'] = rom.patches
|
||||
@@ -331,7 +344,7 @@ def main(args, seed=None, fish=None):
|
||||
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","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") + ": %s", world.seed)
|
||||
logger.info(world.fish.translate("cli","cli","total.time"), time.perf_counter() - start)
|
||||
|
||||
# print_wiki_doors_by_room(dungeon_regions,world,1)
|
||||
@@ -371,6 +384,7 @@ def copy_world(world):
|
||||
ret.compassshuffle = world.compassshuffle.copy()
|
||||
ret.keyshuffle = world.keyshuffle.copy()
|
||||
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
|
||||
ret.bomblogic = world.bomblogic.copy()
|
||||
ret.crystals_needed_for_ganon = world.crystals_needed_for_ganon.copy()
|
||||
ret.crystals_needed_for_gt = world.crystals_needed_for_gt.copy()
|
||||
ret.crystals_ganon_orig = world.crystals_ganon_orig.copy()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import argparse
|
||||
import logging
|
||||
import random
|
||||
import RaceRandom as random
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import yaml
|
||||
@@ -176,6 +176,8 @@ def roll_settings(weights):
|
||||
ret.retro = True
|
||||
ret.retro = get_choice('retro') == 'on' # this overrides world_state if used
|
||||
|
||||
ret.bomblogic = get_choice('bomblogic') == 'on'
|
||||
|
||||
ret.hints = get_choice('hints') == 'on'
|
||||
|
||||
ret.swords = {'randomized': 'random',
|
||||
@@ -226,11 +228,13 @@ def roll_settings(weights):
|
||||
ret.sprite = get_choice('sprite', romweights)
|
||||
ret.disablemusic = get_choice('disablemusic', romweights) == 'on'
|
||||
ret.quickswap = get_choice('quickswap', romweights) == 'on'
|
||||
ret.reduce_flashing = get_choice('reduce_flashing', romweights) == 'on'
|
||||
ret.fastmenu = get_choice('menuspeed', romweights)
|
||||
ret.heartcolor = get_choice('heartcolor', romweights)
|
||||
ret.heartbeep = get_choice('heartbeep', romweights)
|
||||
ret.ow_palettes = get_choice('ow_palettes', romweights)
|
||||
ret.uw_palettes = get_choice('uw_palettes', romweights)
|
||||
ret.uw_palettes = get_choice('shuffle_sfx', romweights) == 'on'
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import argparse
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import RaceRandom as random
|
||||
import time
|
||||
import sys
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ vanilla_pots = {
|
||||
43: [Pot(16, 5, PotItem.Heart, 'PoD Sexy Statue'), Pot(44, 5, PotItem.Switch, 'PoD Sexy Statue'), Pot(16, 6, PotItem.Heart, 'PoD Sexy Statue'), Pot(44, 6, PotItem.Bomb, 'PoD Sexy Statue'), Pot(16, 7, PotItem.Heart, 'PoD Sexy Statue'),
|
||||
Pot(44, 7, PotItem.Bomb, 'PoD Sexy Statue'), Pot(146, 21, PotItem.Bomb, 'PoD Map Balcony'), Pot(170, 21, PotItem.FiveArrows, 'PoD Map Balcony'), Pot(146, 22, PotItem.Bomb, 'PoD Map Balcony'),
|
||||
Pot(170, 22, PotItem.FiveArrows, 'PoD Map Balcony')],
|
||||
44: [Pot(108, 24, PotItem.Heart, 'Hookshot Cave'), Pot(112, 24, PotItem.Heart, 'Hookshot Cave')],
|
||||
44: [Pot(108, 24, PotItem.Heart, 'Hookshot Cave (Middle)'), Pot(112, 24, PotItem.Heart, 'Hookshot Cave (Middle)')],
|
||||
47: [Pot(28, 7, PotItem.Heart, 'Kakariko Well (top)'), Pot(32, 7, PotItem.Heart, 'Kakariko Well (top)'), Pot(28, 9, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(32, 9, PotItem.FiveRupees, 'Kakariko Well (top)'),
|
||||
Pot(172, 19, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(180, 19, PotItem.FiveRupees, 'Kakariko Well (top)'), Pot(104, 27, PotItem.Heart, 'Kakariko Well (bottom)'), Pot(104, 28, PotItem.Heart, 'Kakariko Well (bottom)')],
|
||||
49: [Pot(92, 28, PotItem.Bomb, 'Hera Beetles'), Pot(96, 28, PotItem.Nothing, 'Hera Beetles')],
|
||||
@@ -66,8 +66,8 @@ vanilla_pots = {
|
||||
55: [Pot(60, 6, PotItem.Key, 'Swamp Trench 1 Alcove'), Pot(48, 20, PotItem.Nothing, 'Swamp Trench 1 Key Ledge')],
|
||||
56: [Pot(164, 12, PotItem.Bomb, 'Swamp Pot Row'), Pot(164, 13, PotItem.FiveRupees, 'Swamp Pot Row'), Pot(164, 18, PotItem.Bomb, 'Swamp Pot Row'), Pot(164, 19, PotItem.Key, 'Swamp Pot Row')],
|
||||
57: [Pot(12, 20, PotItem.Heart, 'Skull Spike Corner'), Pot(48, 28, PotItem.FiveArrows, 'Skull Spike Corner'), Pot(100, 22, PotItem.SmallMagic, 'Skull Final Drop'), Pot(100, 26, PotItem.FiveArrows, 'Skull Final Drop')],
|
||||
60: [Pot(24, 8, PotItem.SmallMagic, 'Hookshot Cave'), Pot(64, 12, PotItem.FiveRupees, 'Hookshot Cave'), Pot(20, 14, PotItem.OneRupee, 'Hookshot Cave'), Pot(20, 19, PotItem.Nothing, 'Hookshot Cave'),
|
||||
Pot(68, 18, PotItem.FiveRupees, 'Hookshot Cave'), Pot(96, 19, PotItem.Heart, 'Hookshot Cave'), Pot(64, 20, PotItem.FiveRupees, 'Hookshot Cave'), Pot(64, 26, PotItem.FiveRupees, 'Hookshot Cave')],
|
||||
60: [Pot(24, 8, PotItem.SmallMagic, 'Hookshot Cave (Front)'), Pot(64, 12, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(20, 14, PotItem.OneRupee, 'Hookshot Cave (Front)'), Pot(20, 19, PotItem.Nothing, 'Hookshot Cave (Front)'),
|
||||
Pot(68, 18, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(96, 19, PotItem.Heart, 'Hookshot Cave (Front)'), Pot(64, 20, PotItem.FiveRupees, 'Hookshot Cave (Front)'), Pot(64, 26, PotItem.FiveRupees, 'Hookshot Cave (Front)')],
|
||||
61: [Pot(76, 12, PotItem.Bomb, 'GT Mini Helmasaur Room'), Pot(112, 12, PotItem.Bomb, 'GT Mini Helmasaur Room'), Pot(24, 22, PotItem.Heart, 'GT Crystal Inner Circle'), Pot(40, 22, PotItem.FiveArrows, 'GT Crystal Inner Circle'),
|
||||
Pot(32, 24, PotItem.Heart, 'GT Crystal Inner Circle'), Pot(20, 26, PotItem.FiveRupees, 'GT Crystal Inner Circle'), Pot(36, 26, PotItem.BigMagic, 'GT Crystal Inner Circle')],
|
||||
62: [Pot(96, 6, PotItem.Bomb, 'Ice Stalfos Hint'), Pot(100, 6, PotItem.SmallMagic, 'Ice Stalfos Hint'), Pot(88, 10, PotItem.Heart, 'Ice Stalfos Hint'), Pot(92, 10, PotItem.SmallMagic, 'Ice Stalfos Hint')],
|
||||
@@ -275,7 +275,7 @@ vanilla_pots = {
|
||||
|
||||
|
||||
def shuffle_pots(world, player):
|
||||
import random
|
||||
import RaceRandom as random
|
||||
|
||||
new_pot_contents = {}
|
||||
|
||||
|
||||
126
RELEASENOTES.md
126
RELEASENOTES.md
@@ -1,108 +1,29 @@
|
||||
# New Features
|
||||
|
||||
## Maiden Hint for Theives Town Attic
|
||||
## Shuffle SFX
|
||||
|
||||
In crossed dungeon mode, if you bring the maiden to the boss room when the attic is not bombed (and thus no light in the room), she mentions the dungeon where you can find the cracked floor.
|
||||
Shuffles a large portion of the sounds effects. Can be used with the adjuster.
|
||||
|
||||
## Shuffle Links House
|
||||
CLI: ```--shuffle_sfx```
|
||||
|
||||
Links house can now be shuffled in different ER settings. It will be limited to the Light World (or Dark World in inverted) if Crossed or Insanity shuffle is not one. It it also limited if door shuffle settings allow the Sanctuary to be in the dark world. (This is prevent having no Light World spawn points in Open modes) This setting is ignored by standard mode. THe CLI parameter is --shufflelinks
|
||||
## Bomb Logic
|
||||
|
||||
## OWG Glitch Logic
|
||||
When enabling this option, you do not start with bomb capacity but rather you must find 1 of 2 bomb bags. (They are represented by the +10 capacity item.) Bomb capacity upgrades are otherwise unavailable.
|
||||
|
||||
Thanks to qadan, cheuer, & compiling
|
||||
CLI: ```--bomblogic```
|
||||
|
||||
## Pseudo Boots
|
||||
|
||||
Thanks to Bonta. You can now start with pseudo boots that let you move fast, but have no other logical uses (bonking open things, hovering, etc)
|
||||
|
||||
## Pendant/Crystal Indicator
|
||||
|
||||
For accessibility, you now get a C or P indicator to the left of the magic bar on the HUD when instead a Crystal or Pendant. Requires ownership of the map of that dungeon for display. Thanks to kan.
|
||||
|
||||
# Bug Fixes and Notes.
|
||||
|
||||
* 0.4.0.10
|
||||
* Renamed to pseudoboots
|
||||
* Some release note updates
|
||||
* 0.4.0.9
|
||||
* Fixes for stats and P/C indicator (thanks Kara)
|
||||
* Swamp lobby fixes (thanks Catobat)
|
||||
* Fix for --hints flag on CLI
|
||||
* 0.4.0.8
|
||||
* Ganon jokes added for when silvers aren't available
|
||||
* Some text updated (Blind jokes, uncle text)
|
||||
* Fixed some enemizer Mystery settings
|
||||
* Added a setting that's random enemy shuffle without Unkillable Thieves possible
|
||||
* Fixed shop spoiler when money balancing/multiworld balancing
|
||||
* Fixed a problem with insanity
|
||||
* Fixed an issue with the credit stats specific to DR (e.g. collection rate total)
|
||||
* More helpful error message when bps is missing?
|
||||
* Minor generation issues involving enemizer and the link sprite
|
||||
* Baserom updates (from Bonta, kan, qwertymodo, ardnaxelark)
|
||||
* Boss icon on dungeon map (if you have a compass)
|
||||
* Progressive bow sprite replacement
|
||||
* Quickswap - consecutive special swaps
|
||||
* Bonk Counter
|
||||
* One mind
|
||||
* MSU fix
|
||||
* Chest turn tracking (not yet in credits)
|
||||
* Damaged and magic stats in credits (gt bk removed)
|
||||
* Fix for infinite bombs
|
||||
* Pseudo boots option
|
||||
* Always allowed medallions for swordless (no option yet)
|
||||
* 0.4.0.7
|
||||
* Reduce flashing option added
|
||||
* Sprite author credit added
|
||||
* Ranged Crystal switch rules tweaked
|
||||
* Baserom update: includes Credits Speedup, reduced flashing option, msu resume (but turned off by default)
|
||||
* Create link sprite's zspr from local ROM and no longer attempts to download it from website
|
||||
* Some minor bug fixes
|
||||
* 0.4.0.6
|
||||
* Hints now default to off
|
||||
* The maiden gives you a hint to the attic if you bring her to the unlit boss room
|
||||
* Beemizer support and fix for shopsanity
|
||||
* Capacity upgrades removed in hard/expert item difficulties
|
||||
* Swamp Hub added to lobby shuffle with ugly cave entrance.
|
||||
* TR Lava Escape added to lobby shuffle.
|
||||
* Hyrule Main Lobby and Sanctuary can now have a more visible outside exit, and rugs modified to be fully clipped.
|
||||
* 0.4.0.5
|
||||
* Insanity - less restrictions on exiting (all modes)
|
||||
* Fix for simple bosses shuffle
|
||||
* Fix for boss shuffle from Mystery.py
|
||||
* Minor msu fade out bug (thanks codemann8)
|
||||
* Other bug fixes (thanks Catobat)
|
||||
* 0.4.0.4
|
||||
* Added --shufflelinks option
|
||||
* Moved spawning as a bunny indoors to experimental
|
||||
* Baserom bug fixes
|
||||
* 0.4.0.3
|
||||
* Fixed a bug where Sanctuary could be chosen as a lobby for a DW dungeon in non-crossed ER modes
|
||||
* 0.4.0.2
|
||||
* Fixed a bug where Defeat Ganon is not possible
|
||||
* Fixed the item counter total
|
||||
* Fixed the bunny state when starting out in Sanc in a dark world dungeon
|
||||
* 0.4.0.1
|
||||
* Moved stonewall pre-opening to not happen in experimental
|
||||
* Updated baserom
|
||||
* Boss RNG perseved between files
|
||||
* Vanilla prize pack fix
|
||||
* Starting equipment fix
|
||||
* Post-Aga world state option
|
||||
* Code optimzation
|
||||
* Bottle quickswap via double shoulder
|
||||
* Credits update
|
||||
* Accessibility option
|
||||
* Sewer map/compass fix
|
||||
* Fixed a standard bug where the exits to the ledge would be unavailable if the pyramid was pre-opened
|
||||
* DR ASM optimization
|
||||
* Removed Archery Game from Take-Any caves in inverted
|
||||
* Fixed a problem with new YAML parser
|
||||
* 0.4.0.0
|
||||
* Mystery yaml parser updated to a package maintained version (Thanks StructuralMike)
|
||||
* Bomb-logic and extend crystal switch logic (Thanks StructuralMike)
|
||||
* Fixed logic for moved locations in playthrough (Thanks compiling)
|
||||
* OWG Glitch logic added
|
||||
* 0.5.0.2
|
||||
* --shuffle_sfx option added
|
||||
* 0.5.0.1
|
||||
* --bomblogic option added
|
||||
* 0.5.0.0
|
||||
* Handles headered roms for enemizer (Thanks compiling)
|
||||
* Warning added for earlier version of python (Thanks compiling)
|
||||
* Minor logic issue for defeating Aga in standard (Thanks compiling)
|
||||
* Fix for boss music in non-DR modes (Thanks codemann8)
|
||||
|
||||
# Known Issues
|
||||
|
||||
@@ -111,20 +32,3 @@ For accessibility, you now get a C or P indicator to the left of the magic bar o
|
||||
* Forfeit in Multiworld not granting all shop items
|
||||
* Potential keylocks in multi-entrance dungeons
|
||||
* Incorrect vanilla key logic for Mire
|
||||
|
||||
## Other Notes
|
||||
|
||||
### Triforce Hunt Options
|
||||
|
||||
Thanks to deathFouton!
|
||||
|
||||
--triforce_pool and --triforce_goal added to the CLI.
|
||||
|
||||
Also, to the Mystery.py he added the following options:
|
||||
* triforce_goal_min
|
||||
* triforce_goal_max
|
||||
* triforce_pool_min
|
||||
* triforce_pool_max
|
||||
* triforce_min_difference
|
||||
|
||||
See the example yaml file for demonstrated usage.
|
||||
@@ -190,8 +190,11 @@ def create_regions(world, player):
|
||||
create_cave_region(player, 'Superbunny Cave (Top)', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)']),
|
||||
create_cave_region(player, 'Superbunny Cave (Bottom)', 'a connector', None, ['Superbunny Cave Climb', 'Superbunny Cave Exit (Bottom)']),
|
||||
create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']),
|
||||
create_cave_region(player, 'Hookshot Cave', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'],
|
||||
['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']),
|
||||
create_cave_region(player, 'Hookshot Cave (Front)', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left'],
|
||||
['Hookshot Cave Front to Middle', 'Hookshot Cave Front Exit']),
|
||||
create_cave_region(player, 'Hookshot Cave (Back)', 'a connector', None, ['Hookshot Cave Back to Middle', 'Hookshot Cave Back Exit']),
|
||||
create_cave_region(player, 'Hookshot Cave (Middle)', 'a connector', None, ['Hookshot Cave Middle to Back', 'Hookshot Cave Middle to Front']),
|
||||
|
||||
create_dw_region(player, 'Death Mountain Floating Island (Dark World)', None, ['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot']),
|
||||
create_lw_region(player, 'Death Mountain Floating Island (Light World)', ['Floating Island']),
|
||||
create_dw_region(player, 'Turtle Rock (Top)', None, ['Turtle Rock Drop']),
|
||||
|
||||
58
Rom.py
58
Rom.py
@@ -5,7 +5,7 @@ import json
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import RaceRandom as random
|
||||
import struct
|
||||
import sys
|
||||
import subprocess
|
||||
@@ -28,9 +28,11 @@ from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_
|
||||
from Items import ItemFactory
|
||||
from EntranceShuffle import door_addresses, exit_ids
|
||||
|
||||
from source.classes.SFX import randomize_sfx
|
||||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = 'df3386b7a48d79950a1432b8bbaafde1'
|
||||
RANDOMIZERBASEHASH = '988f1546b14d8f2e6ee30b9de44882da'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
@@ -86,10 +88,12 @@ class LocalRom(object):
|
||||
self.name = name
|
||||
self.hash = hash
|
||||
self.orig_buffer = None
|
||||
self.file = file
|
||||
self.has_smc_header = False
|
||||
if not os.path.isfile(file):
|
||||
raise RuntimeError("Could not find valid local base rom for patching at expected path %s." % file)
|
||||
with open(file, 'rb') as stream:
|
||||
self.buffer = read_rom(stream)
|
||||
self.buffer, self.has_smc_header = read_rom(stream)
|
||||
if patch:
|
||||
self.patch_base_rom()
|
||||
self.orig_buffer = self.buffer.copy()
|
||||
@@ -187,12 +191,21 @@ def write_int32s(rom, startaddress, values):
|
||||
def read_rom(stream):
|
||||
"Reads rom into bytearray and strips off any smc header"
|
||||
buffer = bytearray(stream.read())
|
||||
has_smc_header = False
|
||||
if len(buffer)%0x400 == 0x200:
|
||||
buffer = buffer[0x200:]
|
||||
return buffer
|
||||
has_smc_header = True
|
||||
return buffer, has_smc_header
|
||||
|
||||
def patch_enemizer(world, player, rom, baserom_path, enemizercli, random_sprite_on_hit):
|
||||
baserom_path = os.path.abspath(baserom_path)
|
||||
def patch_enemizer(world, player, rom, local_rom, enemizercli, random_sprite_on_hit):
|
||||
baserom_path = os.path.abspath(local_rom.file)
|
||||
unheadered_path = None
|
||||
if local_rom.has_smc_header:
|
||||
headered_path = baserom_path
|
||||
unheadered_path = baserom_path = os.path.abspath(output_path('unheadered_rom.sfc'))
|
||||
with open(headered_path, 'rb') as headered:
|
||||
with open(baserom_path, 'wb') as unheadered:
|
||||
unheadered.write(headered.read()[0x200:])
|
||||
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")
|
||||
randopatch_path = os.path.abspath(output_path('enemizer_randopatch.json'))
|
||||
@@ -336,6 +349,12 @@ def patch_enemizer(world, player, rom, baserom_path, enemizercli, random_sprite_
|
||||
rom.write_bytes(0x307000 + (i * 0x8000), sprite.palette)
|
||||
rom.write_bytes(0x307078 + (i * 0x8000), sprite.glove_palette)
|
||||
|
||||
if local_rom.has_smc_header:
|
||||
try:
|
||||
os.remove(unheadered_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
try:
|
||||
os.remove(randopatch_path)
|
||||
except OSError:
|
||||
@@ -807,17 +826,17 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
|
||||
write_int16(rom, 0x187010, credits_total) # dynamic credits
|
||||
if credits_total != 216:
|
||||
# collection rate address:
|
||||
cr_address = 0x2391F2
|
||||
# collection rate address (hi):
|
||||
cr_address = 0x238057
|
||||
cr_pc = cr_address - 0x120000 # convert to pc
|
||||
mid_top, mid_bot = credits_digit((credits_total // 10) % 10)
|
||||
last_top, last_bot = credits_digit(credits_total % 10)
|
||||
# top half
|
||||
rom.write_byte(cr_pc+0x1c, mid_top)
|
||||
rom.write_byte(cr_pc+0x1d, last_top)
|
||||
rom.write_byte(cr_pc+0x1, mid_top)
|
||||
rom.write_byte(cr_pc+0x2, last_top)
|
||||
# bottom half
|
||||
rom.write_byte(cr_pc+0x3a, mid_bot)
|
||||
rom.write_byte(cr_pc+0x3b, last_bot)
|
||||
rom.write_byte(cr_pc+0x1f, mid_bot)
|
||||
rom.write_byte(cr_pc+0x20, last_bot)
|
||||
|
||||
# patch medallion requirements
|
||||
if world.required_medallions[player][0] == 'Bombos':
|
||||
@@ -1032,7 +1051,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
rom.write_bytes(0x184000, [
|
||||
# original_item, limit, replacement_item, filler
|
||||
0x12, 0x01, 0x35, 0xFF, # lamp -> 5 rupees
|
||||
0x51, 0x06, 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade
|
||||
0x51, 0x00 if world.bomblogic[player] else 0x06, 0x31 if world.bomblogic[player] else 0x52, 0xFF, # 6 +5 bomb upgrades -> +10 bomb upgrade. If bomblogic -> turns into Bombs (10)
|
||||
0x53, 0x06, 0x54, 0xFF, # 6 +5 arrow upgrades -> +10 arrow upgrade
|
||||
0x58, 0x01, 0x36 if world.retro[player] else 0x43, 0xFF, # silver arrows -> single arrow (red 20 in retro mode)
|
||||
0x3E, difficulty.boss_heart_container_limit, 0x47, 0xff, # boss heart -> green 20
|
||||
@@ -1169,6 +1188,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
equip[0x36C] = 0x18
|
||||
equip[0x36D] = 0x18
|
||||
equip[0x379] = 0x68
|
||||
if world.bomblogic[player]:
|
||||
starting_max_bombs = 0
|
||||
else:
|
||||
starting_max_bombs = 10
|
||||
starting_max_arrows = 30
|
||||
|
||||
@@ -1461,7 +1483,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
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)
|
||||
bow_max, bow_small = 70, 10
|
||||
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 ['Bomb Upgrade (+10)' if world.bomblogic[player] else 'Bombs (10)']:
|
||||
rom.write_byte(0x18004E, 2) # Escape Fill (bombs)
|
||||
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)
|
||||
@@ -1529,8 +1551,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||
# set rom name
|
||||
# 21 bytes
|
||||
from Main import __version__
|
||||
seedstring = f'{world.seed:09}' if isinstance(world.seed, int) else world.seed
|
||||
# todo: change to DR when Enemizer is okay with DR
|
||||
rom.name = bytearray(f'ER{__version__.split("-")[0].replace(".","")[0:3]}_{team+1}_{player}_{world.seed:09}\0', 'utf8')[:21]
|
||||
rom.name = bytearray(f'ER{__version__.split("-")[0].replace(".","")[0:3]}_{team+1}_{player}_{seedstring}\0', 'utf8')[:21]
|
||||
rom.name.extend([0] * (21 - len(rom.name)))
|
||||
rom.write_bytes(0x7FC0, rom.name)
|
||||
|
||||
@@ -1624,7 +1647,7 @@ def hud_format_text(text):
|
||||
|
||||
|
||||
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite,
|
||||
ow_palettes, uw_palettes, reduce_flashing):
|
||||
ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx):
|
||||
|
||||
if not os.path.exists("data/sprites/official/001.link.1.zspr") and rom.orig_buffer:
|
||||
dump_zspr(rom.orig_buffer[0x80000:0x87000], rom.orig_buffer[0xdd308:0xdd380],
|
||||
@@ -1727,6 +1750,9 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
|
||||
elif uw_palettes == 'blackout':
|
||||
blackout_uw_palettes(rom)
|
||||
|
||||
if shuffle_sfx:
|
||||
randomize_sfx(rom)
|
||||
|
||||
if isinstance(rom, LocalRom):
|
||||
rom.write_crc()
|
||||
|
||||
|
||||
12
Rules.py
12
Rules.py
@@ -561,7 +561,8 @@ def global_rules(world, player):
|
||||
def bomb_rules(world, player):
|
||||
bonkable_doors = ['Two Brothers House Exit (West)', 'Two Brothers House Exit (East)'] # Technically this is incorrectly defined, but functionally the same as what is intended.
|
||||
bombable_doors = ['Ice Rod Cave', 'Light World Bomb Hut', 'Light World Death Mountain Shop', 'Mini Moldorm Cave',
|
||||
'Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)', 'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery']
|
||||
'Hookshot Cave Back to Middle', 'Hookshot Cave Front to Middle', 'Hookshot Cave Middle to Front','Hookshot Cave Middle to Back',
|
||||
'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery']
|
||||
for entrance in bonkable_doors:
|
||||
add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player))
|
||||
for entrance in bombable_doors:
|
||||
@@ -576,9 +577,10 @@ def bomb_rules(world, player):
|
||||
for location in bombable_items:
|
||||
add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player))
|
||||
|
||||
cave_kill_locations = ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy']
|
||||
cave_kill_locations = ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right', 'Mini Moldorm Cave - Generous Guy', 'Spiral Cave']
|
||||
for location in cave_kill_locations:
|
||||
add_rule(world.get_location(location, player), lambda state: state.can_kill_most_things(player) or state.can_use_bombs(player))
|
||||
add_rule(world.get_entrance('Spiral Cave (top to bottom)', player), lambda state: state.can_kill_most_things(player) or state.can_use_bombs(player))
|
||||
|
||||
paradox_switch_chests = ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle']
|
||||
for location in paradox_switch_chests:
|
||||
@@ -1149,7 +1151,7 @@ def standard_rules(world, player):
|
||||
set_rule(entrance, lambda state: state.has('Zelda Delivered', player))
|
||||
set_rule(world.get_entrance('Sanctuary Exit', player), lambda state: state.has('Zelda Delivered', player))
|
||||
# zelda should be saved before agahnim is in play
|
||||
set_rule(world.get_location('Agahnim 1', player), lambda state: state.has('Zelda Delivered', player))
|
||||
add_rule(world.get_location('Agahnim 1', player), lambda state: state.has('Zelda Delivered', player))
|
||||
|
||||
# too restrictive for crossed?
|
||||
def uncle_item_rule(item):
|
||||
@@ -1160,7 +1162,7 @@ def standard_rules(world, player):
|
||||
|
||||
def bomb_escape_rule():
|
||||
loc = world.get_location("Link's Uncle", player)
|
||||
return loc.item and loc.item.name == 'Bombs (10)'
|
||||
return loc.item and loc.item.name in ['Bomb Upgrade (+10)' if world.bomblogic[player] else 'Bombs (10)']
|
||||
|
||||
def standard_escape_rule(state):
|
||||
return state.can_kill_most_things(player) or bomb_escape_rule()
|
||||
@@ -1671,7 +1673,7 @@ def set_bunny_rules(world, player, inverted):
|
||||
|
||||
# regions for the exits of multi-entrace caves/drops that bunny cannot pass
|
||||
# Note spiral cave may be technically passible, but it would be too absurd to require since OHKO mode is a thing.
|
||||
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave',
|
||||
bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave (Middle)',
|
||||
'Pyramid', 'Spiral Cave (Top)', 'Fairy Ascension Cave (Drop)']
|
||||
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
|
||||
'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid',
|
||||
|
||||
@@ -580,8 +580,8 @@ dw $00bc, $00a2, $00a3, $00c2, $001a, $0049, $0014, $008c
|
||||
; Ice Many Pots, Swamp Waterfall, GT Gauntlet 3, Eastern Push Block, Eastern Courtyard, Eastern Map Valley
|
||||
; Eastern Cannonball, HC East Hall
|
||||
dw $009f, $0066, $005d, $00a8, $00a9, $00aa, $00b9, $0052
|
||||
; HC West Hall, TR Dash Bridge, TR Hub, Pod Arena, GT Petting Zoo
|
||||
dw $0050, $00c5, $00c6, $0009, $0003, $002a, $007d
|
||||
; HC West Hall, TR Dash Bridge, TR Hub, Pod Arena, GT Petting Zoo, Ice Spike Cross
|
||||
dw $0050, $00c5, $00c6, $0009, $0003, $002a, $007d, $005e
|
||||
dw $ffff
|
||||
|
||||
; dungeon tables
|
||||
|
||||
@@ -179,6 +179,17 @@ JSL BlindsAtticHint : NOP #2
|
||||
org $1cfd69
|
||||
Main_ShowTextMessage:
|
||||
|
||||
; Conditionally disable UW music changes in Door Rando
|
||||
org $028ADB ; <- Bank02.asm:2088-2095 (LDX.b #$14 : LDA $A0 ...)
|
||||
JSL.l Underworld_DoorDown_Entry : CPX #$10
|
||||
db $B0, $21 ; BCS $028B04
|
||||
BRA + : NOP #6 : +
|
||||
|
||||
org $02C3F2 ; <- Bank02.asm:10521 Unused call
|
||||
Underworld_DoorDown_Call:
|
||||
org $02C3F3
|
||||
dw $8AD9 ; address of Bank02.asm:2085
|
||||
|
||||
; 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
|
||||
; Hera BK door back can be seen with Pot clipping - likely useful for no logic seeds
|
||||
|
||||
@@ -398,7 +398,7 @@ StraightStairsTrapDoor:
|
||||
.animateTraps
|
||||
lda #$05 : sta $11
|
||||
inc $0468 : stz $068e : stz $0690
|
||||
++ rtl
|
||||
++ JSL Underworld_DoorDown_Call : rtl
|
||||
+ JML Dungeon_ApproachFixedColor ; what we wrote over
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -132,3 +132,6 @@
|
||||
half: 0
|
||||
quarter: 1
|
||||
off: 0
|
||||
shuffle_sfx:
|
||||
on: 1
|
||||
off: 1
|
||||
|
||||
@@ -199,6 +199,10 @@
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"shuffle_sfx": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"mapshuffle": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
@@ -220,6 +224,10 @@
|
||||
"type": "bool",
|
||||
"help": "suppress"
|
||||
},
|
||||
"bomblogic": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
},
|
||||
"retro": {
|
||||
"action": "store_true",
|
||||
"type": "bool"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"cli": {
|
||||
"app.title": "ALttP Tür Randomisier Version %s - Nummer: %d, Code: %s",
|
||||
"app.title": "ALttP Tür Randomisier Version %s - Nummer: %s, Code: %s",
|
||||
"shuffling.world": "Welt wird durchmischt.",
|
||||
"generating.itempool": "Generier Gegenstandsbasis.",
|
||||
"calc.access.rules": "Berechne Zugriffsregeln.",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"cli": {
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"app.title": "ALttP Door Randomizer Version %s : --seed %d --code %s",
|
||||
"app.title": "ALttP Door Randomizer Version %s : --seed %s --code %s",
|
||||
"version": "Version",
|
||||
"seed": "Seed",
|
||||
"player": "Player",
|
||||
@@ -52,7 +52,8 @@
|
||||
"enemizer.nothing.applied": "No Enemizer options will be applied until this is resolved.",
|
||||
"building.collection.spheres": "Building up collection spheres",
|
||||
"building.calculating.spheres": "Calculated sphere %i, containing %i of %i progress items.",
|
||||
"building.final.spheres": "Calculated final sphere %i, containing %i of %i progress items."
|
||||
"building.final.spheres": "Calculated final sphere %i, containing %i of %i progress items.",
|
||||
"old.python.version": "Door Rando may have issues with python versions earlier than 3.7. Detected version: %s"
|
||||
},
|
||||
"help": {
|
||||
"lang": [ "App Language, if available, defaults to English" ],
|
||||
@@ -263,6 +264,7 @@
|
||||
"and a few other little things make this more like Zelda-1. (default: %(default)s)"
|
||||
],
|
||||
"pseudoboots": [ " Players starts with pseudo boots that allow dashing but no item checks (default: %(default)s"],
|
||||
"bomblogic": ["Start with 0 bomb capacity. Two capacity upgrades (+10) are added to the pool (default: %(default)s)" ],
|
||||
"startinventory": [ "Specifies a list of items that will be in your starting inventory (separated by commas). (default: %(default)s)" ],
|
||||
"usestartinventory": [ "Toggle usage of Starting Inventory." ],
|
||||
"custom": [ "Not supported." ],
|
||||
@@ -295,6 +297,7 @@
|
||||
"sprite that will be extracted."
|
||||
],
|
||||
"reduce_flashing": [ "Reduce some in-game flashing (default: %(default)s)" ],
|
||||
"shuffle_sfx": [ "Shuffle sounds effects (default: %(default)s)" ],
|
||||
"create_rom": [ "Create an output rom file. (default: %(default)s)" ],
|
||||
"gui": [ "Launch the GUI. (default: %(default)s)" ],
|
||||
"jsonout": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"cli": {
|
||||
"app.title": "ALttP Puerta Aleatorizador Versión %s - Número: %d, Código: %s",
|
||||
"app.title": "ALttP Puerta Aleatorizador Versión %s - Número: %s, Código: %s",
|
||||
"player": "Jugador",
|
||||
"shuffling.world": "Barajando el Mundo",
|
||||
"shuffling.dungeons": "Barajando Mazmorras",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"checkboxes": {
|
||||
"nobgm": { "type": "checkbox" },
|
||||
"quickswap": { "type": "checkbox" },
|
||||
"reduce_flashing": {"type": "checkbox"}
|
||||
"reduce_flashing": {"type": "checkbox"},
|
||||
"shuffle_sfx": {"type": "checkbox"}
|
||||
},
|
||||
"leftAdjustFrame": {
|
||||
"heartcolor": {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"adjust.nobgm": "Disable Music & MSU-1",
|
||||
"adjust.quickswap": "L/R Quickswapping",
|
||||
"adjust.reduce_flashing": "Reduce Flashing",
|
||||
"adjust.shuffle_sfx": "Shuffle Sound Effects",
|
||||
|
||||
"adjust.heartcolor": "Heart Color",
|
||||
"adjust.heartcolor.red": "Red",
|
||||
@@ -134,6 +135,7 @@
|
||||
"randomizer.gameoptions.nobgm": "Disable Music & MSU-1",
|
||||
"randomizer.gameoptions.quickswap": "L/R Quickswapping",
|
||||
"randomizer.gameoptions.reduce_flashing": "Reduce Flashing",
|
||||
"randomizer.gameoptions.shuffle_sfx": "Shuffle Sound Effects",
|
||||
|
||||
"randomizer.gameoptions.heartcolor": "Heart Color",
|
||||
"randomizer.gameoptions.heartcolor.red": "Red",
|
||||
@@ -190,6 +192,7 @@
|
||||
"randomizer.item.hints": "Include Helpful Hints",
|
||||
"randomizer.item.retro": "Retro mode (universal keys)",
|
||||
"randomizer.item.pseudoboots": "Start with Pseudo Boots",
|
||||
"randomizer.item.bomblogic": "Bomblogic",
|
||||
|
||||
"randomizer.item.worldstate": "World State",
|
||||
"randomizer.item.worldstate.standard": "Standard",
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"checkboxes": {
|
||||
"nobgm": { "type": "checkbox" },
|
||||
"quickswap": { "type": "checkbox" },
|
||||
"reduce_flashing": {"type": "checkbox"}
|
||||
"reduce_flashing": {"type": "checkbox"},
|
||||
"shuffle_sfx": {"type": "checkbox"}
|
||||
},
|
||||
"leftRomOptionsFrame": {
|
||||
"heartcolor": {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"checkboxes": {
|
||||
"retro": { "type": "checkbox" },
|
||||
"bomblogic": { "type": "checkbox" },
|
||||
"shopsanity": { "type": "checkbox" },
|
||||
"hints": {
|
||||
"type": "checkbox"
|
||||
|
||||
191
source/classes/SFX.py
Normal file
191
source/classes/SFX.py
Normal file
@@ -0,0 +1,191 @@
|
||||
import random
|
||||
from Utils import int16_as_bytes
|
||||
|
||||
|
||||
class SFX(object):
|
||||
|
||||
def __init__(self, name, sfx_set, orig_id, addr, chain, accomp=False):
|
||||
self.name = name
|
||||
self.sfx_set = sfx_set
|
||||
self.orig_id = orig_id
|
||||
self.addr = addr
|
||||
self.chain = chain
|
||||
self.accomp = accomp
|
||||
|
||||
self.target_set = None
|
||||
self.target_id = None
|
||||
self.target_chain = None
|
||||
|
||||
|
||||
def init_sfx_data():
|
||||
sfx_pool = [SFX('Slash1', 0x02, 0x01, 0x2614, []), SFX('Slash2', 0x02, 0x02, 0x2625, []),
|
||||
SFX('Slash3', 0x02, 0x03, 0x2634, []), SFX('Slash4', 0x02, 0x04, 0x2643, []),
|
||||
SFX('Wall clink', 0x02, 0x05, 0x25DD, []), SFX('Bombable door clink', 0x02, 0x06, 0x25D7, []),
|
||||
SFX('Fwoosh shooting', 0x02, 0x07, 0x25B7, []), SFX('Arrow hitting wall', 0x02, 0x08, 0x25E3, []),
|
||||
SFX('Boomerang whooshing', 0x02, 0x09, 0x25AD, []), SFX('Hookshot', 0x02, 0x0A, 0x25C7, []),
|
||||
SFX('Placing bomb', 0x02, 0x0B, 0x2478, []),
|
||||
SFX('Bomb exploding/Quake/Bombos/Exploding wall', 0x02, 0x0C, 0x269C, []),
|
||||
SFX('Powder', 0x02, 0x0D, 0x2414, [0x3f]), SFX('Fire rod shot', 0x02, 0x0E, 0x2404, []),
|
||||
SFX('Ice rod shot', 0x02, 0x0F, 0x24C3, []), SFX('Hammer use', 0x02, 0x10, 0x23FA, []),
|
||||
SFX('Hammering peg', 0x02, 0x11, 0x23F0, []), SFX('Digging', 0x02, 0x12, 0x23CD, []),
|
||||
SFX('Flute use', 0x02, 0x13, 0x23A0, [0x3e]), SFX('Cape on', 0x02, 0x14, 0x2380, []),
|
||||
SFX('Cape off/Wallmaster grab', 0x02, 0x15, 0x2390, []), SFX('Staircase', 0x02, 0x16, 0x232C, []),
|
||||
SFX('Staircase', 0x02, 0x17, 0x2344, []), SFX('Staircase', 0x02, 0x18, 0x2356, []),
|
||||
SFX('Staircase', 0x02, 0x19, 0x236E, []), SFX('Tall grass/Hammer hitting bush', 0x02, 0x1A, 0x2316, []),
|
||||
SFX('Mire shallow water', 0x02, 0x1B, 0x2307, []), SFX('Shallow water', 0x02, 0x1C, 0x2301, []),
|
||||
SFX('Lifting object', 0x02, 0x1D, 0x22BB, []), SFX('Cutting grass', 0x02, 0x1E, 0x2577, []),
|
||||
SFX('Item breaking', 0x02, 0x1F, 0x22E9, []), SFX('Item falling in pit', 0x02, 0x20, 0x22DA, []),
|
||||
SFX('Bomb hitting ground/General bang', 0x02, 0x21, 0x22CF, []),
|
||||
SFX('Pushing object/Armos bounce', 0x02, 0x22, 0x2107, []), SFX('Boots dust', 0x02, 0x23, 0x22B1, []),
|
||||
SFX('Splashing', 0x02, 0x24, 0x22A5, [0x3d]), SFX('Mire shallow water again?', 0x02, 0x25, 0x2296, []),
|
||||
SFX('Link taking damage', 0x02, 0x26, 0x2844, []), SFX('Fainting', 0x02, 0x27, 0x2252, []),
|
||||
SFX('Item splash', 0x02, 0x28, 0x2287, []), SFX('Rupee refill', 0x02, 0x29, 0x243F, [0x3b]),
|
||||
SFX('Fire rod shot hitting wall/Bombos spell', 0x02, 0x2A, 0x2033, []),
|
||||
SFX('Heart beep/Text box', 0x02, 0x2B, 0x1FF2, []), SFX('Sword up', 0x02, 0x2C, 0x1FD9, [0x3a]),
|
||||
SFX('Magic drain', 0x02, 0x2D, 0x20A6, []), SFX('GT opening', 0x02, 0x2E, 0x1FCA, [0x39]),
|
||||
SFX('GT opening/Water drain', 0x02, 0x2F, 0x1F47, [0x38]), SFX('Cucco', 0x02, 0x30, 0x1EF1, []),
|
||||
SFX('Fairy', 0x02, 0x31, 0x20CE, []), SFX('Bug net', 0x02, 0x32, 0x1D47, []),
|
||||
SFX('Teleport2', 0x02, 0x33, 0x1CDC, [], True), SFX('Teleport1', 0x02, 0x34, 0x1F6F, [0x33]),
|
||||
SFX('Quake/Vitreous/Zora king/Armos/Pyramid/Lanmo', 0x02, 0x35, 0x1C67, [0x36]),
|
||||
SFX('Mire entrance (extends above)', 0x02, 0x36, 0x1C64, [], True),
|
||||
SFX('Spin charged', 0x02, 0x37, 0x1A43, []), SFX('Water sound', 0x02, 0x38, 0x1F6F, [], True),
|
||||
SFX('GT opening thunder', 0x02, 0x39, 0x1F9C, [], True), SFX('Sword up', 0x02, 0x3A, 0x1FE7, [], True),
|
||||
SFX('Quiet rupees', 0x02, 0x3B, 0x2462, [], True), SFX('Error beep', 0x02, 0x3C, 0x1A37, []),
|
||||
SFX('Big splash', 0x02, 0x3D, 0x22AB, [], True), SFX('Flute again', 0x02, 0x3E, 0x23B5, [], True),
|
||||
SFX('Powder paired', 0x02, 0x3F, 0x2435, [], True),
|
||||
|
||||
SFX('Sword beam', 0x03, 0x01, 0x1A18, []),
|
||||
SFX('TR opening', 0x03, 0x02, 0x254E, []), SFX('Pyramid hole', 0x03, 0x03, 0x224A, []),
|
||||
SFX('Angry soldier', 0x03, 0x04, 0x220E, []), SFX('Lynel shot/Javelin toss', 0x03, 0x05, 0x25B7, []),
|
||||
SFX('BNC swing/Phantom ganon/Helma tail/Arrghus swoosh', 0x03, 0x06, 0x21F5, []),
|
||||
SFX('Cannon fire', 0x03, 0x07, 0x223D, []), SFX('Damage to enemy; $0BEX.4=1', 0x03, 0x08, 0x21E6, []),
|
||||
SFX('Enemy death', 0x03, 0x09, 0x21C1, []), SFX('Collecting rupee', 0x03, 0x0A, 0x21A9, []),
|
||||
SFX('Collecting heart', 0x03, 0x0B, 0x2198, []),
|
||||
SFX('Non-blank text character', 0x03, 0x0C, 0x218E, []),
|
||||
SFX('HUD heart (used explicitly by sanc heart?)', 0x03, 0x0D, 0x21B5, []),
|
||||
SFX('Opening chest', 0x03, 0x0E, 0x2182, []),
|
||||
SFX('♪Do do do doooooo♫', 0x03, 0x0F, 0x24B9, [0x3C, 0x3D, 0x3E, 0x3F]),
|
||||
SFX('Opening/Closing map (paired)', 0x03, 0x10, 0x216D, [0x3b]),
|
||||
SFX('Opening item menu/Bomb shop guy breathing', 0x03, 0x11, 0x214F, []),
|
||||
SFX('Closing item menu/Bomb shop guy breathing', 0x03, 0x12, 0x215E, []),
|
||||
SFX('Throwing object (sprites use it as well)/Stalfos jump', 0x03, 0x13, 0x213B, []),
|
||||
SFX('Key door/Trinecks/Dash key landing/Stalfos Knight collapse', 0x03, 0x14, 0x246C, []),
|
||||
SFX('Door closing/OW door opening/Chest opening (w/ $29 in $012E)', 0x03, 0x15, 0x212F, []),
|
||||
SFX('Armos Knight thud', 0x03, 0x16, 0x2123, []), SFX('Rat squeak', 0x03, 0x17, 0x25A6, []),
|
||||
SFX('Dragging/Mantle moving', 0x03, 0x18, 0x20DD, []),
|
||||
SFX('Fireball/Laser shot; Somehow used by Trinexx???', 0x03, 0x19, 0x250A, []),
|
||||
SFX('Chest reveal jingle ', 0x03, 0x1A, 0x1E8A, [0x38]),
|
||||
SFX('Puzzle jingle', 0x03, 0x1B, 0x20B6, [0x3a]), SFX('Damage to enemy', 0x03, 0x1C, 0x1A62, []),
|
||||
SFX('Potion refill/Magic drain', 0x03, 0x1D, 0x20A6, []),
|
||||
SFX('Flapping (Duck/Cucco swarm/Ganon bats/Keese/Raven/Vulture)', 0x03, 0x1E, 0x2091, []),
|
||||
SFX('Link falling', 0x03, 0x1F, 0x204B, []), SFX('Menu/Text cursor moved', 0x03, 0x20, 0x276C, []),
|
||||
SFX('Damage to boss', 0x03, 0x21, 0x27E2, []), SFX('Boss dying/Deleting file', 0x03, 0x22, 0x26CF, []),
|
||||
SFX('Spin attack/Medallion swoosh', 0x03, 0x23, 0x2001, [0x39]),
|
||||
SFX('OW map perspective change', 0x03, 0x24, 0x2043, []),
|
||||
SFX('Pressure switch', 0x03, 0x25, 0x1E9D, []),
|
||||
SFX('Lightning/Game over/Laser/Ganon bat/Trinexx lunge', 0x03, 0x26, 0x1E7B, []),
|
||||
SFX('Agahnim charge', 0x03, 0x27, 0x1E40, []), SFX('Agahnim/Ganon teleport', 0x03, 0x28, 0x26F7, []),
|
||||
SFX('Agahnim shot', 0x03, 0x29, 0x1E21, []),
|
||||
SFX('Somaria/Byrna/Ether spell/Helma fire ball', 0x03, 0x2A, 0x1E12, []),
|
||||
SFX('Electrocution', 0x03, 0x2B, 0x1DF3, []), SFX('Bees', 0x03, 0x2C, 0x1DC0, []),
|
||||
SFX('Milestone, also via text', 0x03, 0x2D, 0x1DA9, [0x37]),
|
||||
SFX('Collecting heart container', 0x03, 0x2E, 0x1D5D, [0x35, 0x34]),
|
||||
SFX('Collecting absorbable key', 0x03, 0x2F, 0x1D80, [0x33]),
|
||||
SFX('Byrna spark/Item plop/Magic bat zap/Blob emerge', 0x03, 0x30, 0x1B53, []),
|
||||
SFX('Sprite falling/Moldorm shuffle', 0x03, 0x31, 0x1ACA, []),
|
||||
SFX('Bumper boing/Somaria punt/Blob transmutation/Sprite boings', 0x03, 0x32, 0x1A78, []),
|
||||
SFX('Jingle (paired $2F→$33)', 0x03, 0x33, 0x1D93, [], True),
|
||||
SFX('Depressing jingle (paired $2E→$35→$34)', 0x03, 0x34, 0x1D66, [], True),
|
||||
SFX('Ugly jingle (paired $2E→$35→$34)', 0x03, 0x35, 0x1D73, [], True),
|
||||
SFX('Wizzrobe shot/Helma fireball split/Mothula beam/Blue balls', 0x03, 0x36, 0x1AA7, []),
|
||||
SFX('Dinky jingle (paired $2D→$37)', 0x03, 0x37, 0x1DB4, [], True),
|
||||
SFX('Apathetic jingle (paired $1A→$38)', 0x03, 0x38, 0x1E93, [], True),
|
||||
SFX('Quiet swish (paired $23→$39)', 0x03, 0x39, 0x2017, [], True),
|
||||
SFX('Defective jingle (paired $1B→$3A)', 0x03, 0x3A, 0x20C0, [], True),
|
||||
SFX('Petulant jingle (paired $10→$3B)', 0x03, 0x3B, 0x2176, [], True),
|
||||
SFX('Triumphant jingle (paired $0F→$3C→$3D→$3E→$3F)', 0x03, 0x3C, 0x248A, [], True),
|
||||
SFX('Less triumphant jingle ($0F→$3C→$3D→$3E→$3F)', 0x03, 0x3D, 0x2494, [], True),
|
||||
SFX('"You tried, I guess" jingle (paired $0F→$3C→$3D→$3E→$3F)', 0x03, 0x3E, 0x249E, [], True),
|
||||
SFX('"You didn\'t really try" jingle (paired $0F→$3C→$3D→$3E→$3F)', 0x03, 0x3F, 0x2480, [], True)]
|
||||
return sfx_pool
|
||||
|
||||
|
||||
def shuffle_sfx_data():
|
||||
sfx_pool = init_sfx_data()
|
||||
sfx_map = {2: {}, 3: {}}
|
||||
accompaniment_map = {2: set(), 3: set()}
|
||||
candidates = []
|
||||
for sfx in sfx_pool:
|
||||
sfx_map[sfx.sfx_set][sfx.orig_id] = sfx
|
||||
if not sfx.accomp:
|
||||
candidates.append((sfx.sfx_set, sfx.orig_id))
|
||||
else:
|
||||
accompaniment_map[sfx.sfx_set].add(sfx.orig_id)
|
||||
chained_sfx = [x for x in sfx_pool if len(x.chain) > 0]
|
||||
|
||||
random.shuffle(candidates)
|
||||
|
||||
# place chained sfx first
|
||||
random.shuffle(chained_sfx) # todo: sort largest to smallest
|
||||
chained_sfx = sorted(chained_sfx, key=lambda x: len(x.chain), reverse=True)
|
||||
for chained in chained_sfx:
|
||||
chosen_slot = next(x for x in candidates if len(accompaniment_map[x[0]]) - len(chained.chain) >= 0)
|
||||
if chosen_slot is None:
|
||||
raise Exception('Something went wrong with sfx chains')
|
||||
chosen_set, chosen_id = chosen_slot
|
||||
chained.target_set, chained.target_id = chosen_slot
|
||||
chained.target_chain = []
|
||||
for downstream in chained.chain:
|
||||
next_slot = accompaniment_map[chosen_set].pop()
|
||||
ds_acc = sfx_map[chained.sfx_set][downstream]
|
||||
ds_acc.target_set, ds_acc.target_id = chosen_set, next_slot
|
||||
chained.target_chain.append(next_slot)
|
||||
candidates.remove(chosen_slot)
|
||||
sfx_pool.remove(chained)
|
||||
|
||||
unchained_sfx = [x for x in sfx_pool if not x.accomp]
|
||||
# do the rest
|
||||
for sfx in unchained_sfx:
|
||||
chosen_slot = candidates.pop()
|
||||
sfx.target_set, sfx.target_id = chosen_slot
|
||||
|
||||
return sfx_map
|
||||
|
||||
|
||||
sfx_table = {
|
||||
2: 0x1a8c29,
|
||||
3: 0x1A8D25
|
||||
}
|
||||
|
||||
# 0x1a8c29
|
||||
# d8059
|
||||
|
||||
sfx_accompaniment_table = {
|
||||
2: 0x1A8CA7,
|
||||
3: 0x1A8DA3
|
||||
}
|
||||
|
||||
|
||||
def randomize_sfx(rom):
|
||||
sfx_map = shuffle_sfx_data()
|
||||
|
||||
for shuffled_sfx in sfx_map.values():
|
||||
for sfx in shuffled_sfx.values():
|
||||
base_address = sfx_table[sfx.target_set]
|
||||
rom.write_bytes(base_address + (sfx.target_id * 2) - 2, int16_as_bytes(sfx.addr))
|
||||
ac_base = sfx_accompaniment_table[sfx.target_set]
|
||||
last = sfx.target_id
|
||||
if sfx.target_chain:
|
||||
for chained in sfx.target_chain:
|
||||
rom.write_byte(ac_base + last - 1, chained)
|
||||
last = chained
|
||||
rom.write_byte(ac_base + last - 1, 0)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ SETTINGSTOPROCESS = {
|
||||
"item": {
|
||||
"hints": "hints",
|
||||
"retro": "retro",
|
||||
"bomblogic": "bomblogic",
|
||||
"shopsanity": "shopsanity",
|
||||
"pseudoboots": "pseudoboots",
|
||||
"worldstate": "mode",
|
||||
@@ -107,7 +108,8 @@ SETTINGSTOPROCESS = {
|
||||
"menuspeed": "fastmenu",
|
||||
"owpalettes": "ow_palettes",
|
||||
"uwpalettes": "uw_palettes",
|
||||
"reduce_flashing": "reduce_flashing"
|
||||
"reduce_flashing": "reduce_flashing",
|
||||
"shuffle_sfx": "shuffle_sfx",
|
||||
},
|
||||
"generation": {
|
||||
"createspoiler": "create_spoiler",
|
||||
|
||||
@@ -103,6 +103,7 @@ def adjust_page(top, parent, settings):
|
||||
"quickswap": "quickswap",
|
||||
"nobgm": "disablemusic",
|
||||
"reduce_flashing": "reduce_flashing",
|
||||
"shuffle_sfx": "shuffle_sfx",
|
||||
}
|
||||
guiargs = Namespace()
|
||||
for option in options:
|
||||
|
||||
Reference in New Issue
Block a user