Merged in DR v1.4.1.11

This commit is contained in:
codemann8
2024-04-25 15:06:59 -05:00
20 changed files with 327 additions and 213 deletions

View File

@@ -20,7 +20,7 @@ from source.dungeon.RoomObject import RoomObject
class World(object):
def __init__(self, players, owShuffle, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints):
timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints, spoiler_mode):
self.players = players
self.teams = 1
self.owShuffle = owShuffle.copy()
@@ -78,6 +78,7 @@ class World(object):
self.prizes = {}
self.dynamic_regions = []
self.dynamic_locations = []
self.spoiler_mode = spoiler_mode
self.spoiler = Spoiler(self)
self.lamps_needed_for_dark_rooms = 1
self.owedges = []
@@ -106,6 +107,7 @@ class World(object):
self.data_tables = {}
self.damage_table = {}
for player in range(1, players + 1):
def set_player_attr(attr, val):
self.__dict__.setdefault(attr, {})[player] = val
@@ -2869,6 +2871,17 @@ class Spoiler(object):
self.metadata = {}
self.shops = []
self.bosses = OrderedDict()
if world.spoiler_mode == 'settings':
self.settings = {'settings'}
elif world.spoiler_mode == 'semi':
self.settings = {'settings', 'entrances', 'requirements', 'prizes'}
elif world.spoiler_mode == 'full':
self.settings = {'all'}
elif world.spoiler_mode == 'debug':
self.settings = {'all', 'debug'}
else:
self.settings = {}
self.suppress_spoiler_locations = ['Big Bomb', 'Frog', 'Dark Blacksmith Ruins', 'Middle Aged Man', 'Lost Old Man', 'Old Man Drop Off']
@@ -3012,27 +3025,29 @@ class Spoiler(object):
self.bottles[f'Waterfall Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][0]
self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1]
def include_item(item):
return 'all' in self.settings or ('items' in self.settings and not item.crystal) or ('prizes' in self.settings and item.crystal)
self.locations = OrderedDict()
listed_locations = set()
lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and not loc.skip]
lw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.LightWorld and not loc.skip and include_item(loc.item)]
self.locations['Light World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in lw_locations])
listed_locations.update(lw_locations)
dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and not loc.skip]
dw_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.DarkWorld and not loc.skip and include_item(loc.item)]
self.locations['Dark World'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dw_locations])
listed_locations.update(dw_locations)
cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave and not loc.skip]
cave_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.type == RegionType.Cave and not loc.skip and include_item(loc.item)]
self.locations['Caves'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in cave_locations])
listed_locations.update(cave_locations)
for dungeon in self.world.dungeons:
dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item and not loc.skip]
dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item and not loc.skip and include_item(loc.item)]
self.locations[str(dungeon)] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations])
listed_locations.update(dungeon_locations)
other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and not loc.skip]
other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and not loc.skip and include_item(loc.item)]
if other_locations:
self.locations['Other Locations'] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in other_locations])
listed_locations.update(other_locations)
@@ -3132,14 +3147,17 @@ class Spoiler(object):
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
if self.metadata['user_notes']:
outfile.write('User Notes:'.ljust(line_width) + '%s\n' % self.metadata['user_notes'])
if 'all' in self.settings or 'settings' in self.settings:
outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm)
outfile.write('Players:'.ljust(line_width) + '%d\n' % self.world.players)
outfile.write('Teams:'.ljust(line_width) + '%d\n' % self.world.teams)
for player in range(1, self.world.players + 1):
if self.world.players > 1:
outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player)))
if 'all' in self.settings or 'settings' in self.settings:
outfile.write('Settings Code:'.ljust(line_width) + '%s\n' % self.metadata["code"][player])
outfile.write('\n')
if 'all' in self.settings or 'settings' in self.settings:
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player])
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
@@ -3247,6 +3265,7 @@ class Spoiler(object):
self.parse_data()
with open(filename, 'a') as outfile:
line_width = 35
if 'all' in self.settings or 'requirements' in self.settings:
outfile.write('\n\nRequirements:\n\n')
for dungeon, medallion in self.medallions.items():
outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion)
@@ -3257,11 +3276,13 @@ class Spoiler(object):
if self.world.crystals_ganon_orig[player] == 'random':
outfile.write(str('Crystals Required for Ganon' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player])))
if 'all' in self.settings or 'misc' in self.settings:
outfile.write('\n\nBottle Refills:\n\n')
for fairy, bottle in self.bottles.items():
outfile.write(f'{fairy}: {bottle}\n')
if self.maps:
if 'all' in self.settings or 'flute' in self.settings:
# flute shuffle
for player in range(1, self.world.players + 1):
if ('flute', player) in self.maps:
@@ -3273,6 +3294,7 @@ class Spoiler(object):
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
outfile.write(self.maps[('flute', player)]['text'])
if 'all' in self.settings or 'overworld' in self.settings:
# overworld tile flips
for player in range(1, self.world.players + 1):
if ('swaps', player) in self.maps:
@@ -3295,22 +3317,22 @@ class Spoiler(object):
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
outfile.write(self.maps[('groups', player)]['text'])
if self.overworlds:
if self.overworlds and ('all' in self.settings or 'overworld' in self.settings):
outfile.write('\n\nOverworld Edges:\n\n')
# overworld transitions
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()]))
if self.whirlpools:
if self.whirlpools and ('all' in self.settings or 'overworld' in self.settings):
outfile.write('\n\nWhirlpools:\n\n')
# whirlpools
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","whirlpools",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","whirlpools",entry['exit'])) for entry in self.whirlpools.values()]))
if self.entrances:
if self.entrances and ('all' in self.settings or 'entrances' in self.settings):
# entrances: To/From overworld; Checking w/ & w/out "Exit" and translating accordingly
outfile.write('\n\nEntrances:\n\n')
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta", "entrances", entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta", "entrances", entry['exit'])) for entry in self.entrances.values()]))
if self.doors:
if self.doors and ('all' in self.settings or 'doors' in self.settings):
outfile.write('\n\nDoors:\n\n')
outfile.write('\n'.join(
['%s%s %s %s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '',
@@ -3319,28 +3341,32 @@ class Spoiler(object):
self.world.fish.translate("meta", "doors", entry['exit']),
'({0})'.format(entry['dname']) if self.world.doorShuffle[entry['player']] != 'basic' else '') for
entry in self.doors.values()]))
if self.lobbies:
if self.lobbies and ('all' in self.settings or 'doors' in self.settings):
outfile.write('\n\nDungeon Lobbies:\n\n')
outfile.write('\n'.join(
[f"{'Player {0}: '.format(entry['player']) if self.world.players > 1 else ''}{entry['lobby_name']}: {entry['door_name']}"
for
entry in self.lobbies.values()]))
if self.doorTypes:
if self.doorTypes and ('all' in self.settings or 'doors' in self.settings):
# doorNames: For some reason these come in combined, somehow need to split on the thing to translate
# doorTypes: Small Key, Bombable, Bonkable
outfile.write('\n\nDoor Types:\n\n')
outfile.write('\n'.join(['%s%s %s' % ('Player {0}: '.format(entry['player']) if self.world.players > 1 else '', self.world.fish.translate("meta", "doors", entry['doorNames']), self.world.fish.translate("meta", "doorTypes", entry['type'])) for entry in self.doorTypes.values()]))
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
# items: Item names
outfile.write('\n\nLocations:\n\n')
outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta", "locations", location), self.world.fish.translate("meta", "items", item)) for grouping in self.locations.values() for (location, item) in grouping.items()]))
outfile.write('\n'.join(['%s: %s' % (self.world.fish.translate("meta", "locations", location), self.world.fish.translate("meta", "items", item))
for grouping in self.locations.values() for (location, item) in grouping.items()]))
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
# items: Item names
if 'all' in self.settings or 'shops' in self.settings:
outfile.write('\n\nShops:\n\n')
outfile.write('\n'.join("{} [{}]\n {}".format(self.world.fish.translate("meta", "locations", shop['location']), shop['type'], "\n ".join(self.world.fish.translate("meta", "items", item) for item in [shop.get('item_0', None), shop.get('item_1', None), shop.get('item_2', None)] if item)) for shop in self.shops))
if 'all' in self.settings or 'bosses' in self.settings:
for player in range(1, self.world.players + 1):
if self.world.boss_shuffle[player] != 'none':
bossmap = self.bosses[str(player)] if self.world.players > 1 else self.bosses

3
CLI.py
View File

@@ -267,10 +267,9 @@ def parse_settings():
"msu_resume": False,
"collection_rate": False,
# Spoiler defaults to TRUE
'spoiler': 'full',
# Playthrough defaults to TRUE
# ROM defaults to TRUE
"create_spoiler": True,
"calc_playthrough": True,
"create_rom": True,
"bps": False,

View File

@@ -1115,7 +1115,7 @@ def set_prize_drops(world, player):
for player, drop_config in drops.items():
for pack_num in range(1, 8):
if f'Pack {pack_num}' in drop_config:
for prize, idx in enumerate(drop_config[f'Pack {pack_num}']):
for idx, prize in enumerate(drop_config[f'Pack {pack_num}']):
chosen = random.choice(uniform_prizes) if prize == 'Random' else possible_prizes[prize]
prizes[(pack_num-1)*8 + idx] = chosen
for tree_pull_tier in range(1, 4):

2
Gui.py
View File

@@ -44,7 +44,7 @@ def save_settings(gui, args, filename):
if not os.path.exists(settings_path):
os.makedirs(settings_path)
output_args = {}
settings = ["create_rom", "suppress_rom", "bps", "create_spoiler", "suppress_spoiler",
settings = ["create_rom", "suppress_rom", "bps", "spoiler",
"calc_playthrough", "skip_playthrough", "print_custom_yaml",
"settingsonload", "rom", "outputpath"]
if filename == "settings.json":

21
Main.py
View File

@@ -41,7 +41,7 @@ from source.enemizer.DamageTables import DamageTable
from source.enemizer.Enemizer import randomize_enemies
from source.rom.DataTables import init_data_tables
version_number = '1.4.1.10'
version_number = '1.4.1.11'
version_branch = '-u'
__version__ = f'{version_number}{version_branch}'
@@ -87,7 +87,6 @@ def main(args, seed=None, fish=None):
for i in zip(args.logic.values(), args.door_shuffle.values()):
if i[0] == 'hybridglitches' and i[1] != 'vanilla':
raise RuntimeError(BabelFish().translate("cli","cli","hybridglitches.door.shuffle"))
if seed is None:
random.seed(None)
world.seed = random.randint(0, 999999999)
@@ -160,7 +159,7 @@ def main(args, seed=None, fish=None):
world.settings = CustomSettings()
world.settings.create_from_world(world, args)
if args.create_spoiler and not args.jsonout:
if world.spoiler_mode != 'none' and not args.jsonout:
logger.info(world.fish.translate("cli", "cli", "create.meta"))
world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if args.mystery and not (args.suppress_meta or args.create_spoiler):
@@ -365,12 +364,12 @@ def main(args, seed=None, fish=None):
if args.mystery and not (args.suppress_meta or args.create_spoiler):
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_meta.txt'))
elif args.create_spoiler and not args.jsonout:
elif world.spoiler_mode != 'none' and not args.jsonout:
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if args.create_spoiler and not args.jsonout:
if world.spoiler_mode != 'none' and not args.jsonout:
logger.info(world.fish.translate("cli", "cli", "patching.spoiler"))
world.spoiler.to_file(output_path(f'{outfilebase}_Spoiler.txt'))
if args.loglevel == 'debug':
if 'debug' in world.spoiler.settings:
world.spoiler.extras(output_path(f'{outfilebase}_Spoiler.txt'))
if not args.skip_playthrough:
@@ -379,7 +378,7 @@ def main(args, seed=None, fish=None):
if args.jsonout:
print(json.dumps({**jsonout, 'spoiler': world.spoiler.to_json()}))
elif args.create_spoiler:
elif world.spoiler_mode != 'none':
logger.info(world.fish.translate("cli","cli","patching.spoiler"))
if args.jsonout:
with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile:
@@ -394,7 +393,7 @@ def main(args, seed=None, fish=None):
logger.info("")
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.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 world.spoiler_mode != 'none') else NO))
logger.info(world.fish.translate("cli","cli","seed") + ": %s", world.seed)
logger.info(world.fish.translate("cli","cli","total.time"), time.perf_counter() - start)
@@ -452,7 +451,7 @@ def init_world(args, fish):
world = World(args.multi, args.ow_shuffle, args.ow_crossed, args.ow_mixed, args.shuffle, args.door_shuffle, args.logic, args.mode, args.swords,
args.difficulty, args.item_functionality, args.timer, args.progressive, args.goal, args.algorithm,
args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints)
args.accessibility, args.shuffleganon, args.custom, args.customitemarray, args.hints, args.spoiler)
world.customizer = customized
world.boots_hint = args.boots_hint.copy()
@@ -530,7 +529,7 @@ def copy_world(world):
# ToDo: Not good yet
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints)
world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode)
ret.teams = world.teams
ret.player_names = copy.deepcopy(world.player_names)
ret.remote_items = world.remote_items.copy()
@@ -725,7 +724,7 @@ def copy_world_premature(world, player):
# ToDo: Not good yet
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
world.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints)
world.accessibility, world.shuffle_ganon, world.custom, world.customitemarray, world.hints, world.spoiler_mode)
ret.teams = world.teams
ret.player_names = copy.deepcopy(world.player_names)
ret.remote_items = world.remote_items.copy()

View File

@@ -28,7 +28,7 @@ def main():
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255))
parser.add_argument('--names', default='')
parser.add_argument('--teams', default=1, type=lambda value: max(int(value), 1))
parser.add_argument('--create_spoiler', action='store_true')
parser.add_argument('--spoiler', default='none', choices=['none', 'settings', 'semi', 'full', 'debug'])
parser.add_argument('--no_race', action='store_true')
parser.add_argument('--suppress_rom', action='store_true')
parser.add_argument('--suppress_meta', action='store_true')
@@ -64,7 +64,7 @@ def main():
erargs = parse_cli(['--multi', str(args.multi)])
erargs.seed = seed
erargs.names = args.names
erargs.create_spoiler = args.create_spoiler
erargs.spoiler = args.spoiler
erargs.suppress_rom = args.suppress_rom
erargs.suppress_meta = args.suppress_meta
erargs.bps = args.bps

View File

@@ -141,6 +141,16 @@ These are now independent of retro mode and have three options: None, Random, an
# Patch Notes
* 1.4.1.11u
* New Feature: Several spoiler levels added: None, Settings-only, Semi, Full, Debug
* Semi includes only entrances, prizes, and medallions (potential new spoiler mode being worked on, definition may change)
* Entrance: Lite/Lean support enemy drop shuffle
* Standard: Re-added tutorial guard near large rock
* Enemizer
* Fixed the overwriting of bonk fairies
* Fixed broken graphics on hyrule castle
* Enemy bans
* Customizer: Fixed bug with customizing prize packs
* 1.4.1.10u
* Vanilla key logic: Fix for vanilla layout Misery Mire which allows more complex key logic. Locations blocked by crystal switch access are only locked by 2 keys thanks to that being the minimum in Mire to reach one of two crystal switches.
* Autotracking: Fix for chest turn counter with chest containing multiworld items (Thanks Hiimcody)

View File

@@ -347,11 +347,11 @@ def update_deprecated_args(args):
# Don't do: Yes
# Do: No
if "suppress_spoiler" in argVars:
args.create_spoiler = not args.suppress_spoiler in truthy
args.spoiler = 'none'
# Don't do: No
# Do: Yes
if "create_spoiler" in argVars:
args.suppress_spoiler = not args.create_spoiler in truthy
args.spoiler = 'full'
# ROM defaults to TRUE
# Don't do: Yes

View File

@@ -4,14 +4,14 @@
"action": "store_true",
"type": "bool"
},
"create_spoiler": {
"action": "store_false",
"dest": "suppress_spoiler",
"type": "bool",
"help": "suppress"
},
"suppress_spoiler": {
"action": "store_true"
"spoiler": {
"choices": [
"none",
"settings",
"semi",
"full",
"debug"
]
},
"mystery": {
"action": "store_true",

View File

@@ -61,7 +61,15 @@
},
"help": {
"lang": [ "App Language, if available, defaults to English" ],
"create_spoiler": [ "Output a Spoiler File" ],
"spoiler": [
"Spoiler File Options. (default: %(default)s)",
"None: No Spoiler",
"Meta: Meta information only about game. Intended for mystery settings",
"Settings: Only settings information",
"Semi: ",
"Full: Full spoiler generated",
"Debug: Includes debug information"
],
"bps": [ "Output BPS patches instead of ROMs"],
"logic": [
"Select Enforcement of Item Requirements. (default: %(default)s)",

View File

@@ -236,7 +236,12 @@
"randomizer.generation.bps": "Create BPS Patches",
"randomizer.generation.createspoiler": "Create Spoiler Log",
"randomizer.generation.spoiler": "Create Spoiler Log",
"randomizer.generation.spoiler.none": "None",
"randomizer.generation.spoiler.settings": "Settings Only",
"randomizer.generation.spoiler.semi": "Semi (Entrances and Prizes)",
"randomizer.generation.spoiler.full": "Full",
"randomizer.generation.spoiler.debug": "Debug",
"randomizer.generation.createrom": "Create Patched ROM",
"randomizer.generation.calcplaythrough": "Calculate Playthrough",
"randomizer.generation.print_custom_yaml": "Print Customizer File",

View File

@@ -2,7 +2,16 @@
"checkboxes": {
"createrom": { "type": "checkbox" },
"bps": { "type": "checkbox" },
"createspoiler": { "type": "checkbox" },
"spoiler": {
"type": "selectbox",
"options": [
"none",
"settings",
"semi",
"full",
"debug"
]
},
"calcplaythrough": { "type": "checkbox" },
"print_custom_yaml": { "type": "checkbox" }
}

View File

@@ -146,7 +146,7 @@ SETTINGSTOPROCESS = {
},
"generation": {
"bps": "bps",
"createspoiler": "create_spoiler",
"spoiler": "spoiler",
"createrom": "create_rom",
"calcplaythrough": "calc_playthrough",
"print_custom_yaml": "print_custom_yaml",

View File

@@ -260,7 +260,7 @@ def get_randomize_able_sprites_ow(area_id, data_tables):
req = data_tables.sprite_requirements[key]
if isinstance(req, dict):
continue
if not req.static and req.can_randomize:
if not req.static and req.can_randomize and not sprite.static:
sprite_table[idx] = sprite
return sprite_table

View File

@@ -29,6 +29,7 @@ def init_vanilla_sprites_ow():
create_sprite(0x21b, EnemySprite.GreenKnifeGuard, 0x20, 0x1A, '', 0x09CB51)
create_sprite(0x21b, EnemySprite.TutorialGuard, 0x2D, 0x25, '', 0x09CB54)
create_sprite(0x21b, EnemySprite.TutorialGuard, 0x20, 0x29, '', 0x09CB57)
create_sprite(0x21b, EnemySprite.TutorialGuard, 0x3C, 0x2A, '', 0x09CB5B)
create_sprite(0x21d, EnemySprite.Apple, 0x0B, 0x06, '', 0x09CB5B, bonk=True)
create_sprite(0x22b, EnemySprite.TutorialGuard, 0x09, 0x1E, '', 0x09CB5F)
create_sprite(0x22b, EnemySprite.TutorialGuard, 0x0B, 0x1E, '', 0x09CB62)

View File

@@ -687,7 +687,11 @@ def setup_required_overworld_groups(sheets):
sheets[4].add_sprite_to_sheet([None, None, None, None], {0xF, 0x9F}) # Waterfall of wishing (pre/post-Aga)
sheets[3].add_sprite_to_sheet([None, None, None, 14], {0x14, 0xA4}) # Graveyard (pre/post-Aga)
sheets[1].add_sprite_to_sheet([None, None, 76, 0x3F], {0x1B, 0xAB}) # Hyrule Castle (pre/post-Aga)
sheets[2].add_sprite_to_sheet([None, None, None, 0x3F], {}) # Hyrule Castle - rain state
## group 0 set to 0x48 for tutortial guards
## group 1 & 2 set for green knife guards (and probably normal green guard)
## group 3 set for lightning gate
sheets[2].add_sprite_to_sheet([0x48, 0x49, 0x13, 0x3F], {}) # Hyrule Castle - rain state
# Smithy/Race/Kak (pre/post-Aga)
sheets[6].add_sprite_to_sheet([0x4F, 0x49, 0x4A, 0x50], {0x18, 0x22, 0x28, 0xA8, 0xB2, 0xB8})
sheets[8].add_sprite_to_sheet([None, None, 18, None], {0x30, 0xC0}) # Desert (pre/post-Aga)

View File

@@ -325,6 +325,7 @@ UwGeneralDeny:
- [ 0x00c2, 5, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots
- [ 0x00c5, 6, [ "RollerVerticalUp", "RollerVerticalDown", "RollerHorizontalLeft", "RollerHorizontalRight", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Turtle Rock - Catwalk - Mini Helmasaur"
- [ 0x00c5, 7, [ "Statue" ] ] #"Turtle Rock - Catwalk - Laser Eye (Left) 4"
- [0x00c6, 5, ["Bumper"]]
- [ 0x00cb, 0, [ "Wizzrobe", "Statue" ] ] # Wizzrobes can't spawn on pots
- [ 0x00cb, 3, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 1"
- [ 0x00cb, 5, [ "RollerVerticalUp", "RollerVerticalDown", "Beamos", "AntiFairyCircle", "BigSpike", "SpikeBlock", "Bumper" ] ] #"Thieves' Town - Grand Room NW - Zol 2"
@@ -419,10 +420,12 @@ OwGeneralDeny:
- [0x03, 10, ["Gibo"]] # OldMan eating Gibo
- [0x05, 11, ["Bumper", "AntiFairyCircle"]] # Blocks path to portal
- [0x1e, 3, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle"]] # forbid a beamos here
- [0x35, 8, ["RollerVerticalUp", "RollerVerticalDown"]] # blocks the dock
- [0x40, 0, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]]
- [0x40, 7, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]]
- [0x40, 13, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]]
- [0x40, 14, ["Beamos", "Bumper", "BigSpike", "AntiFairyCircle", "Thief"]]
- [0x40, 16, ["RollerVerticalUp", "RollerVerticalDown"]] # Ropa near back hole is really large as a roller
- [0x55, 6, ["BigSpike"]]
- [0x57, 5, ["RollerVerticalUp", "RollerVerticalDown"]]
- [0x5e, 0, ["Gibo"]] # kiki eating Gibo

View File

@@ -133,7 +133,6 @@ def bottom_frame(self, parent, args=None):
made[k] = m.group(1) + m.group(2) + ' ' + m.group(4)
successMsg += (made["rom"] % (YES if (guiargs.create_rom) 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["enemizer"] % (YES if needEnemizer else NO)) + "\n"
# FIXME: English
successMsg += ("Seed%s: %s" % ('s' if len(seeds) > 1 else "", ','.join(str(x) for x in seeds)))

View File

@@ -1340,18 +1340,24 @@ def do_limited_shuffle_exclude_drops(pool_def, avail, lw=True):
def do_vanilla_connect(pool_def, avail):
if pool_def['condition'] == 'shopsanity':
if 'shopsanity' in pool_def['condition']:
if avail.world.shopsanity[avail.player]:
return
elif pool_def['condition'] == 'pottery': # this condition involves whether caves with pots are shuffled or not
if 'pottery' in pool_def['condition']: # this condition involves whether caves with pots are shuffled or not
if avail.world.pottery[avail.player] not in ['none', 'keys', 'dungeon']:
return
elif pool_def['condition'] == 'takeany':
if 'takeany' in pool_def['condition']:
if avail.world.take_any[avail.player] == 'fixed':
return
elif pool_def['condition'] == 'bonk':
if 'bonk' in pool_def['condition']:
if avail.world.shuffle_bonk_drops[avail.player]:
return
if 'dropshuffle' in pool_def['condition']:
if avail.world.dropshuffle[avail.player] not in ['none', 'keys']:
return
if 'enemy_drop' in pool_def['condition']:
if avail.world.dropshuffle[avail.player] not in ['none', 'keys'] or avail.world.enemy_shuffle[avail.player] != 'none':
return
defaults = {**default_connections, **(inverted_default_connections if avail.inverted != avail.world.is_tile_swapped(0x1b, avail.player) else open_default_connections)}
for entrance in pool_def['entrances']:
if entrance in avail.entrances:
@@ -1871,35 +1877,56 @@ modes = {
'special': 'vanilla',
'condition': '',
'entrances': ['Mire Fairy', 'Archery Game', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint',
'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Fairy', 'Dark Lake Hylia Shop',
'East Dark World Hint', 'Kakariko Gamble Game', 'Long Fairy Cave',
'Bush Covered House', 'Fortune Teller (Light)', 'Lost Woods Gamble',
'Lake Hylia Fortune Teller', 'Lake Hylia Fairy', 'Bonk Fairy (Light)'],
'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Fairy', 'East Dark World Hint',
'Kakariko Gamble Game', 'Bush Covered House', 'Fortune Teller (Light)',
'Lost Woods Gamble', 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy'],
},
'fixed_shops': {
'special': 'vanilla',
'condition': 'shopsanity',
'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop',
'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade',
'Lake Hylia Shop'],
'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop',
'Red Shield Shop', 'Kakariko Shop', 'Lake Hylia Shop', 'Dark Lake Hylia Shop'],
},
'fixed_takeanys': {
'special': 'vanilla',
'condition': 'takeany',
'entrances': ['Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy',
'Dark Lake Hylia Ledge Fairy', 'Bonk Fairy (Dark)'],
'entrances': ['Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Dark Lake Hylia Ledge Fairy'],
},
'fixed_takeanys_enemy_drops_fairies': {
'special': 'vanilla',
'condition': ['takeany', 'enemy_drop'],
'entrances': ['Bonk Fairy (Dark)'],
},
'fixed_pottery': {
'special': 'vanilla',
'condition': 'pottery',
'entrances': ['Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)',
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
'Mire Hint']
'20 Rupee Cave', '50 Rupee Cave', 'Palace of Darkness Hint',
'Dark Lake Hylia Ledge Spike Cave', 'Mire Hint']
},
'fixed_enemy_drops_fairies': {
'special': 'vanilla',
'condition': 'enemy_drop',
'entrances': ['Long Fairy Cave', 'Bonk Fairy (Light)']
},
'fixed_pots_n_bones_fairies': {
'special': 'vanilla',
'condition': ['pottery', 'enemy_drop'],
'entrances': ['Hookshot Fairy']
},
'fixed_pots_n_bones': {
'special': 'vanilla',
'condition': ['pottery', 'dropshuffle'],
'entrances': ['Light World Bomb Hut']
},
'fixed_shop_n_bones': {
'special': 'vanilla',
'condition': ['shopsanity', 'enemy_drop'],
'entrances': ['Capacity Upgrade']
},
'fixed_bonk': {
'special': 'vanilla',
'condition': 'bonk',
'condition': ['enemy_drop', 'bonk'],
'entrances': ['Good Bee Cave']
},
'item_caves': { # shuffles shops/pottery if they weren't fixed in the last steps
@@ -1908,15 +1935,15 @@ modes = {
'Ice Rod Cave', 'Dam', 'Bonk Rock Cave', 'Library', 'Potion Shop', 'Mini Moldorm Cave',
'Checkerboard Cave', 'Graveyard Cave', 'Cave 45', 'Sick Kids House', 'Blacksmiths Hut',
'Sahasrahlas Hut', 'Aginahs Cave', 'Chicken House', 'Kings Grave', 'Blinds Hideout',
'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Good Bee Cave',
'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Dark Lake Hylia Shop',
'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop',
'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Lake Hylia Shop',
'Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)',
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
'Mire Hint', 'Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy',
'Dark Lake Hylia Ledge Fairy', 'Bonk Fairy (Dark)',
'Links House', 'Tavern North']
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Desert Fairy',
'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Dark Lake Hylia Ledge Fairy',
'Bonk Fairy (Dark)', 'Good Bee Cave', 'Long Fairy Cave', 'Bonk Fairy (Light)',
'Mire Hint', 'Links House', 'Tavern North']
},
'old_man_cave': { # have to do old man cave first so lw dungeon don't use up everything
'special': 'old_man_cave_east',
@@ -1962,35 +1989,56 @@ modes = {
'special': 'vanilla',
'condition': '',
'entrances': ['Mire Fairy', 'Archery Game', 'Fortune Teller (Dark)', 'Dark Sanctuary Hint',
'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Fairy', 'Dark Lake Hylia Shop',
'East Dark World Hint', 'Kakariko Gamble Game', 'Long Fairy Cave',
'Bush Covered House', 'Fortune Teller (Light)', 'Lost Woods Gamble',
'Lake Hylia Fortune Teller', 'Lake Hylia Fairy', 'Bonk Fairy (Light)'],
'Dark Lake Hylia Ledge Hint', 'Dark Lake Hylia Fairy', 'East Dark World Hint',
'Kakariko Gamble Game', 'Bush Covered House', 'Fortune Teller (Light)',
'Lost Woods Gamble', 'Lake Hylia Fortune Teller', 'Lake Hylia Fairy'],
},
'fixed_shops': {
'special': 'vanilla',
'condition': 'shopsanity',
'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop',
'Dark World Shop', 'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade',
'Lake Hylia Shop'],
'entrances': ['Dark Death Mountain Shop', 'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop',
'Red Shield Shop', 'Kakariko Shop', 'Lake Hylia Shop', 'Dark Lake Hylia Shop'],
},
'fixed_takeanys': {
'special': 'vanilla',
'condition': 'takeany',
'entrances': ['Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy',
'Dark Lake Hylia Ledge Fairy', 'Bonk Fairy (Dark)'],
'entrances': ['Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Dark Lake Hylia Ledge Fairy'],
},
'fixed_takeanys_enemy_drops_fairies': {
'special': 'vanilla',
'condition': ['takeany', 'enemy_drop'],
'entrances': ['Bonk Fairy (Dark)'],
},
'fixed_pottery': {
'special': 'vanilla',
'condition': 'pottery',
'entrances': ['Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)',
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
'Mire Hint']
'20 Rupee Cave', '50 Rupee Cave', 'Palace of Darkness Hint',
'Dark Lake Hylia Ledge Spike Cave', 'Mire Hint']
},
'fixed_enemy_drops_fairies': {
'special': 'vanilla',
'condition': 'enemy_drop',
'entrances': ['Long Fairy Cave', 'Bonk Fairy (Light)']
},
'fixed_pots_n_bones_fairies': {
'special': 'vanilla',
'condition': ['pottery', 'enemy_drop'],
'entrances': ['Hookshot Fairy']
},
'fixed_pots_n_bones': {
'special': 'vanilla',
'condition': ['pottery', 'dropshuffle'],
'entrances': ['Light World Bomb Hut']
},
'fixed_shop_n_bones': {
'special': 'vanilla',
'condition': ['shopsanity', 'enemy_drop'],
'entrances': ['Capacity Upgrade']
},
'fixed_bonk': {
'special': 'vanilla',
'condition': 'bonk',
'condition': ['enemy_drop', 'bonk'],
'entrances': ['Good Bee Cave']
},
'item_caves': { # shuffles shops/pottery if they weren't fixed in the last steps
@@ -1999,15 +2047,15 @@ modes = {
'Ice Rod Cave', 'Dam', 'Bonk Rock Cave', 'Library', 'Potion Shop', 'Mini Moldorm Cave',
'Checkerboard Cave', 'Graveyard Cave', 'Cave 45', 'Sick Kids House', 'Blacksmiths Hut',
'Sahasrahlas Hut', 'Aginahs Cave', 'Chicken House', 'Kings Grave', 'Blinds Hideout',
'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Good Bee Cave',
'Waterfall of Wishing', 'Dark Death Mountain Shop', 'Dark Lake Hylia Shop',
'Dark Potion Shop', 'Dark Lumberjack Shop', 'Dark World Shop',
'Red Shield Shop', 'Kakariko Shop', 'Capacity Upgrade', 'Lake Hylia Shop',
'Lumberjack House', 'Snitch Lady (West)', 'Snitch Lady (East)', 'Tavern (Front)',
'Light World Bomb Hut', '20 Rupee Cave', '50 Rupee Cave', 'Hookshot Fairy',
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave',
'Mire Hint', 'Desert Fairy', 'Light Hype Fairy', 'Dark Death Mountain Fairy',
'Dark Lake Hylia Ledge Fairy', 'Bonk Fairy (Dark)',
'Links House', 'Tavern North'] # inverted links house gets substituted
'Palace of Darkness Hint', 'Dark Lake Hylia Ledge Spike Cave', 'Desert Fairy',
'Light Hype Fairy', 'Dark Death Mountain Fairy', 'Dark Lake Hylia Ledge Fairy',
'Bonk Fairy (Dark)', 'Good Bee Cave', 'Long Fairy Cave', 'Bonk Fairy (Light)',
'Mire Hint', 'Links House', 'Tavern North']
}
}
},

View File

@@ -94,6 +94,7 @@ class DataTables:
# _00FA81 is LW normal
# _00FAC1 is LW post-aga
# _00FB01 is DW
# _00FA41 is rain state
self.write_ow_sprite_data_to_rom(rom)
for sprite, stats in self.enemy_stats.items():
# write health to rom
@@ -134,9 +135,11 @@ class DataTables:
# calculate how big this table is going to be?
# bytes = sum(1+len(x)*3 for x in self.ow_enemy_table.values() if len(x) > 0)+1
# ending_byte = 0x09CB3B + bytes
max_per_state = {0: 0x40, 1: 0x90, 2: 0x8D} # dropped max on state 2 to steal space for a couple extra sprites (Murahdahla)
max_per_state = {0: 0x40, 1: 0x90, 2: 0x8B} # dropped max on state 2 to steal space for a couple extra sprites (Murahdahla, extra tutorial guard)
pointer_address = snes_to_pc(0x09C881)
data_pointer = snes_to_pc(0x09CB3B) # was originally 0x09CB41 - stealing space for a couple extra sprites (Murahdahla)
# currently borrowed 10 bytes, used 9 (2xMurah + TutorialGuard)
data_pointer = snes_to_pc(0x09CB38) # was originally 0x09CB41 - stealing space for a couple extra sprites (Murahdahla, extra tutorial guard)
empty_pointer = pc_to_snes(data_pointer) & 0xFFFF
rom.write_byte(data_pointer, 0xff)
cached_dark_world = {}