Version bump 0.2.8.0
This commit is contained in:
@@ -37,6 +37,7 @@ def main():
|
|||||||
parser.add_argument('--uw_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('--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('--shuffle_sfx', help='Shuffles sound sfx', action='store_true')
|
||||||
|
parser.add_argument('--msu_resume', help='Enable MSU resume', action='store_true')
|
||||||
parser.add_argument('--sprite', help='''\
|
parser.add_argument('--sprite', help='''\
|
||||||
Path to a sprite sheet to use for Link. Needs to be in
|
Path to a sprite sheet to use for Link. Needs to be in
|
||||||
binary format and have a length of 0x7000 (28672) bytes,
|
binary format and have a length of 0x7000 (28672) bytes,
|
||||||
|
|||||||
+44
-1
@@ -2,8 +2,15 @@ import os
|
|||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
try:
|
||||||
|
import bps.apply
|
||||||
|
import bps.io
|
||||||
|
except ImportError:
|
||||||
|
raise Exception('Could not load BPS module')
|
||||||
|
|
||||||
from Utils import output_path
|
from Utils import output_path
|
||||||
from Rom import LocalRom, apply_rom_settings
|
from Rom import LocalRom, apply_rom_settings
|
||||||
|
from source.tools.BPS import bps_read_vlv
|
||||||
|
|
||||||
|
|
||||||
def adjust(args):
|
def adjust(args):
|
||||||
@@ -25,7 +32,8 @@ def adjust(args):
|
|||||||
args.sprite = None
|
args.sprite = None
|
||||||
|
|
||||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic,
|
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.shuffle_sfx)
|
args.sprite, args.ow_palettes, args.uw_palettes, args.reduce_flashing, args.shuffle_sfx,
|
||||||
|
args.msu_resume)
|
||||||
|
|
||||||
output_path.cached_path = args.outputpath
|
output_path.cached_path = args.outputpath
|
||||||
rom.write_to_file(output_path('%s.sfc' % outfilebase))
|
rom.write_to_file(output_path('%s.sfc' % outfilebase))
|
||||||
@@ -34,3 +42,38 @@ def adjust(args):
|
|||||||
logger.debug('Total Time: %s', time.process_time() - start)
|
logger.debug('Total Time: %s', time.process_time() - start)
|
||||||
|
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def patch(args):
|
||||||
|
start = time.process_time()
|
||||||
|
logger = logging.getLogger('')
|
||||||
|
logger.info('Patching ROM.')
|
||||||
|
|
||||||
|
outfile_base = os.path.basename(args.patch)[:-4]
|
||||||
|
|
||||||
|
rom = LocalRom(args.baserom, False)
|
||||||
|
if os.path.isfile(args.baserom):
|
||||||
|
rom.verify_base_rom()
|
||||||
|
orig_buffer = rom.buffer.copy()
|
||||||
|
with open(args.patch, 'rb') as stream:
|
||||||
|
stream.seek(4) # skip BPS1
|
||||||
|
bps_read_vlv(stream) # skip source size
|
||||||
|
target_length = bps_read_vlv(stream)
|
||||||
|
rom.buffer.extend(bytearray([0x00] * (target_length - len(rom.buffer))))
|
||||||
|
stream.seek(0)
|
||||||
|
bps.apply.apply_to_bytearrays(bps.io.read_bps(stream), orig_buffer, rom.buffer)
|
||||||
|
|
||||||
|
if not hasattr(args, "sprite"):
|
||||||
|
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.shuffle_sfx,
|
||||||
|
args.msu_resume)
|
||||||
|
|
||||||
|
output_path.cached_path = args.outputpath
|
||||||
|
rom.write_to_file(output_path('%s.sfc' % outfile_base))
|
||||||
|
|
||||||
|
logger.info('Done. Enjoy.')
|
||||||
|
logger.debug('Total Time: %s', time.process_time() - start)
|
||||||
|
|
||||||
|
return args
|
||||||
|
|||||||
+164
-55
@@ -15,6 +15,7 @@ from source.classes.BabelFish import BabelFish
|
|||||||
from Utils import int16_as_bytes
|
from Utils import int16_as_bytes
|
||||||
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
|
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
|
||||||
from RoomData import Room
|
from RoomData import Room
|
||||||
|
from source.dungeon.RoomObject import RoomObject
|
||||||
|
|
||||||
|
|
||||||
class World(object):
|
class World(object):
|
||||||
@@ -62,7 +63,6 @@ class World(object):
|
|||||||
self.lock_aga_door_in_escape = False
|
self.lock_aga_door_in_escape = False
|
||||||
self.save_and_quit_from_boss = True
|
self.save_and_quit_from_boss = True
|
||||||
self.accessibility = accessibility.copy()
|
self.accessibility = accessibility.copy()
|
||||||
self.initial_overworld_flags = {}
|
|
||||||
self.fix_skullwoods_exit = {}
|
self.fix_skullwoods_exit = {}
|
||||||
self.fix_palaceofdarkness_exit = {}
|
self.fix_palaceofdarkness_exit = {}
|
||||||
self.fix_trock_exit = {}
|
self.fix_trock_exit = {}
|
||||||
@@ -99,6 +99,7 @@ class World(object):
|
|||||||
self._portal_cache = {}
|
self._portal_cache = {}
|
||||||
self.sanc_portal = {}
|
self.sanc_portal = {}
|
||||||
self.fish = BabelFish()
|
self.fish = BabelFish()
|
||||||
|
self.pot_contents = {}
|
||||||
|
|
||||||
for player in range(1, players + 1):
|
for player in range(1, players + 1):
|
||||||
# If World State is Retro, set to Open and set Retro flag
|
# If World State is Retro, set to Open and set Retro flag
|
||||||
@@ -120,7 +121,6 @@ class World(object):
|
|||||||
set_player_attr('ganon_at_pyramid', True)
|
set_player_attr('ganon_at_pyramid', True)
|
||||||
set_player_attr('ganonstower_vanilla', True)
|
set_player_attr('ganonstower_vanilla', True)
|
||||||
set_player_attr('sewer_light_cone', self.mode[player] == 'standard')
|
set_player_attr('sewer_light_cone', self.mode[player] == 'standard')
|
||||||
set_player_attr('initial_overworld_flags', [0] * 0x80)
|
|
||||||
set_player_attr('fix_trock_doors', self.shuffle[player] != 'vanilla' or self.is_tile_swapped(0x05, player))
|
set_player_attr('fix_trock_doors', self.shuffle[player] != 'vanilla' or self.is_tile_swapped(0x05, player))
|
||||||
set_player_attr('fix_skullwoods_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'] or self.doorShuffle[player] not in ['vanilla'])
|
set_player_attr('fix_skullwoods_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'] or self.doorShuffle[player] not in ['vanilla'])
|
||||||
set_player_attr('fix_palaceofdarkness_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
|
set_player_attr('fix_palaceofdarkness_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
|
||||||
@@ -154,9 +154,11 @@ class World(object):
|
|||||||
set_player_attr('potshuffle', False)
|
set_player_attr('potshuffle', False)
|
||||||
set_player_attr('pot_contents', None)
|
set_player_attr('pot_contents', None)
|
||||||
set_player_attr('pseudoboots', False)
|
set_player_attr('pseudoboots', False)
|
||||||
|
set_player_attr('collection_rate', False)
|
||||||
|
set_player_attr('colorizepots', False)
|
||||||
|
set_player_attr('pot_pool', {})
|
||||||
|
|
||||||
set_player_attr('shopsanity', False)
|
set_player_attr('shopsanity', False)
|
||||||
set_player_attr('keydropshuffle', False)
|
|
||||||
set_player_attr('mixed_travel', 'prevent')
|
set_player_attr('mixed_travel', 'prevent')
|
||||||
set_player_attr('standardize_palettes', 'standardize')
|
set_player_attr('standardize_palettes', 'standardize')
|
||||||
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False})
|
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False})
|
||||||
@@ -456,6 +458,8 @@ class World(object):
|
|||||||
location.item = item
|
location.item = item
|
||||||
item.location = location
|
item.location = location
|
||||||
item.world = self
|
item.world = self
|
||||||
|
if location.player != item.player and location.type == LocationType.Pot:
|
||||||
|
self.pot_contents[location.player].multiworld_count += 1
|
||||||
if collect:
|
if collect:
|
||||||
self.state.collect(item, location.event, location)
|
self.state.collect(item, location.event, location)
|
||||||
|
|
||||||
@@ -1361,6 +1365,8 @@ class CollectionState(object):
|
|||||||
return self.has('Fire Rod', player) or self.has('Lamp', player)
|
return self.has('Fire Rod', player) or self.has('Lamp', player)
|
||||||
|
|
||||||
def can_flute(self, player):
|
def can_flute(self, player):
|
||||||
|
if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player):
|
||||||
|
return False
|
||||||
if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)):
|
if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)):
|
||||||
return True
|
return True
|
||||||
lw = self.world.get_region('Kakariko Area', player)
|
lw = self.world.get_region('Kakariko Area', player)
|
||||||
@@ -2632,10 +2638,19 @@ class Location(object):
|
|||||||
self.item_rule = lambda item: True
|
self.item_rule = lambda item: True
|
||||||
self.player = player
|
self.player = player
|
||||||
self.skip = False
|
self.skip = False
|
||||||
|
self.type = LocationType.Normal if not crystal else LocationType.Prize
|
||||||
|
self.pot = None
|
||||||
|
|
||||||
def can_fill(self, state, item, check_access=True):
|
def can_fill(self, state, item, check_access=True):
|
||||||
|
if not self.valid_multiworld(state, item):
|
||||||
|
return False
|
||||||
return self.always_allow(state, item) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state)))
|
return self.always_allow(state, item) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state)))
|
||||||
|
|
||||||
|
def valid_multiworld(self, state, item):
|
||||||
|
if self.type == LocationType.Pot and self.player != item.player:
|
||||||
|
return state.world.pot_contents[self.player].multiworld_count < 256
|
||||||
|
return True
|
||||||
|
|
||||||
def can_reach(self, state):
|
def can_reach(self, state):
|
||||||
return self.parent_region.can_reach(state) and self.access_rule(state)
|
return self.parent_region.can_reach(state) and self.access_rule(state)
|
||||||
|
|
||||||
@@ -2671,6 +2686,15 @@ class Location(object):
|
|||||||
return hash((self.name, self.player))
|
return hash((self.name, self.player))
|
||||||
|
|
||||||
|
|
||||||
|
class LocationType(FastEnum):
|
||||||
|
Normal = 0
|
||||||
|
Prize = 1
|
||||||
|
Logical = 2
|
||||||
|
Shop = 3
|
||||||
|
Pot = 4
|
||||||
|
Drop = 5
|
||||||
|
|
||||||
|
|
||||||
class Item(object):
|
class Item(object):
|
||||||
|
|
||||||
def __init__(self, name='', advancement=False, priority=False, type=None, code=None, price=999, pedestal_hint=None,
|
def __init__(self, name='', advancement=False, priority=False, type=None, code=None, price=999, pedestal_hint=None,
|
||||||
@@ -2907,17 +2931,19 @@ class Spoiler(object):
|
|||||||
'enemy_shuffle': self.world.enemy_shuffle,
|
'enemy_shuffle': self.world.enemy_shuffle,
|
||||||
'enemy_health': self.world.enemy_health,
|
'enemy_health': self.world.enemy_health,
|
||||||
'enemy_damage': self.world.enemy_damage,
|
'enemy_damage': self.world.enemy_damage,
|
||||||
'potshuffle': self.world.potshuffle,
|
|
||||||
'players': self.world.players,
|
'players': self.world.players,
|
||||||
'teams': self.world.teams,
|
'teams': self.world.teams,
|
||||||
'experimental': self.world.experimental,
|
'experimental': self.world.experimental,
|
||||||
'keydropshuffle': self.world.keydropshuffle,
|
'dropshuffle': self.world.dropshuffle,
|
||||||
|
'pottery': self.world.pottery,
|
||||||
|
'potshuffle': self.world.potshuffle,
|
||||||
'shopsanity': self.world.shopsanity,
|
'shopsanity': self.world.shopsanity,
|
||||||
'pseudoboots': self.world.pseudoboots,
|
'pseudoboots': self.world.pseudoboots,
|
||||||
'triforcegoal': self.world.treasure_hunt_count,
|
'triforcegoal': self.world.treasure_hunt_count,
|
||||||
'triforcepool': self.world.treasure_hunt_total,
|
'triforcepool': self.world.treasure_hunt_total,
|
||||||
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}
|
'code': {p: Settings.make_code(self.world, p) for p in range(1, self.world.players + 1)}
|
||||||
}
|
}
|
||||||
|
|
||||||
for p in range(1, self.world.players + 1):
|
for p in range(1, self.world.players + 1):
|
||||||
from ItemList import set_default_triforce
|
from ItemList import set_default_triforce
|
||||||
if self.world.custom and p in self.world.customitemarray:
|
if self.world.custom and p in self.world.customitemarray:
|
||||||
@@ -2962,11 +2988,11 @@ class Spoiler(object):
|
|||||||
listed_locations.update(cave_locations)
|
listed_locations.update(cave_locations)
|
||||||
|
|
||||||
for dungeon in self.world.dungeons:
|
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]
|
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]
|
||||||
self.locations[str(dungeon)] = OrderedDict([(location.gen_name(), str(location.item) if location.item is not None else 'Nothing') for location in dungeon_locations])
|
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)
|
listed_locations.update(dungeon_locations)
|
||||||
|
|
||||||
other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations]
|
other_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and not loc.skip]
|
||||||
if other_locations:
|
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])
|
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)
|
listed_locations.update(other_locations)
|
||||||
@@ -3041,6 +3067,18 @@ class Spoiler(object):
|
|||||||
|
|
||||||
return json.dumps(out)
|
return json.dumps(out)
|
||||||
|
|
||||||
|
def mystery_meta_to_file(self, filename):
|
||||||
|
self.parse_meta()
|
||||||
|
with open(filename, 'w') as outfile:
|
||||||
|
line_width = 35
|
||||||
|
outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed))
|
||||||
|
for k,v in self.metadata["versions"].items():
|
||||||
|
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
|
||||||
|
for player in range(1, self.world.players + 1):
|
||||||
|
if self.world.players > 1:
|
||||||
|
outfile.write('\nPlayer %d: %s\n' % (player, self.world.get_player_names(player)))
|
||||||
|
outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player])
|
||||||
|
|
||||||
def meta_to_file(self, filename):
|
def meta_to_file(self, filename):
|
||||||
def yn(flag):
|
def yn(flag):
|
||||||
return 'Yes' if flag else 'No'
|
return 'Yes' if flag else 'No'
|
||||||
@@ -3048,7 +3086,7 @@ class Spoiler(object):
|
|||||||
self.parse_meta()
|
self.parse_meta()
|
||||||
with open(filename, 'w') as outfile:
|
with open(filename, 'w') as outfile:
|
||||||
line_width = 35
|
line_width = 35
|
||||||
outfile.write('ALttP Entrance Randomizer - Seed: %s\n\n' % (self.world.seed))
|
outfile.write('ALttP Overworld Randomizer - Seed: %s\n\n' % (self.world.seed))
|
||||||
for k,v in self.metadata["versions"].items():
|
for k,v in self.metadata["versions"].items():
|
||||||
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
|
outfile.write((k + ' Version:').ljust(line_width) + '%s\n' % v)
|
||||||
outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm)
|
outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm)
|
||||||
@@ -3088,15 +3126,15 @@ class Spoiler(object):
|
|||||||
outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player]))
|
outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player]))
|
||||||
if self.metadata['shuffle'][player] != 'vanilla' or self.metadata['ow_mixed'][player]:
|
if self.metadata['shuffle'][player] != 'vanilla' or self.metadata['ow_mixed'][player]:
|
||||||
outfile.write('Overworld Map:'.ljust(line_width) + '%s\n' % self.metadata['overworld_map'][player])
|
outfile.write('Overworld Map:'.ljust(line_width) + '%s\n' % self.metadata['overworld_map'][player])
|
||||||
if self.metadata['goal'][player] != 'trinity':
|
|
||||||
outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % self.metadata['open_pyramid'][player])
|
outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % self.metadata['open_pyramid'][player])
|
||||||
outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player])
|
outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player])
|
||||||
if self.metadata['door_shuffle'][player] != 'vanilla':
|
if self.metadata['door_shuffle'][player] != 'vanilla':
|
||||||
outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player])
|
outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player])
|
||||||
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player]))
|
outfile.write('Experimental:'.ljust(line_width) + '%s\n' % yn(self.metadata['experimental'][player]))
|
||||||
outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player])
|
outfile.write('Dungeon Counters:'.ljust(line_width) + '%s\n' % self.metadata['dungeon_counters'][player])
|
||||||
outfile.write('Pot Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player]))
|
outfile.write('Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['dropshuffle'][player]))
|
||||||
outfile.write('Key Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['keydropshuffle'][player]))
|
outfile.write('Pottery Mode:'.ljust(line_width) + '%s\n' % self.metadata['pottery'][player])
|
||||||
|
outfile.write('Pot Shuffle (Legacy):'.ljust(line_width) + '%s\n' % yn(self.metadata['potshuffle'][player]))
|
||||||
outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player]))
|
outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['mapshuffle'][player]))
|
||||||
outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['compassshuffle'][player]))
|
outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['compassshuffle'][player]))
|
||||||
outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['keyshuffle'][player]))
|
outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['keyshuffle'][player]))
|
||||||
@@ -3111,19 +3149,37 @@ class Spoiler(object):
|
|||||||
outfile.write('Starting Inventory:'.ljust(line_width))
|
outfile.write('Starting Inventory:'.ljust(line_width))
|
||||||
outfile.write('\n'.ljust(line_width+1).join(self.startinventory) + '\n')
|
outfile.write('\n'.ljust(line_width+1).join(self.startinventory) + '\n')
|
||||||
|
|
||||||
|
def hashes_to_file(self, filename):
|
||||||
|
with open(filename, 'r') as infile:
|
||||||
|
contents = infile.readlines()
|
||||||
|
|
||||||
|
def insert(lines, i, value):
|
||||||
|
lines.insert(i, value)
|
||||||
|
i += 1
|
||||||
|
return i
|
||||||
|
|
||||||
|
idx = 2
|
||||||
|
if self.world.players > 1:
|
||||||
|
idx = insert(contents, idx, 'Hashes:')
|
||||||
|
for player in range(1, self.world.players + 1):
|
||||||
|
if self.world.players > 1:
|
||||||
|
idx = insert(contents, idx, f'\nPlayer {player}: {self.world.get_player_names(player)}\n')
|
||||||
|
if len(self.hashes) > 0:
|
||||||
|
for team in range(self.world.teams):
|
||||||
|
player_name = self.world.player_names[player][team]
|
||||||
|
label = f"Hash - {player_name} (Team {team+1}): " if self.world.teams > 1 else 'Hash: '
|
||||||
|
idx = insert(contents, idx, f'{label}{self.hashes[player, team]}\n')
|
||||||
|
if self.world.players > 1:
|
||||||
|
insert(contents, idx, '\n') # return value ignored here, if you want to add more lines
|
||||||
|
|
||||||
|
with open(filename, "w") as f:
|
||||||
|
contents = "".join(contents)
|
||||||
|
f.write(contents)
|
||||||
|
|
||||||
def to_file(self, filename):
|
def to_file(self, filename):
|
||||||
self.parse_data()
|
self.parse_data()
|
||||||
with open(filename, 'a') as outfile:
|
with open(filename, 'a') as outfile:
|
||||||
line_width = 35
|
line_width = 35
|
||||||
if self.world.players > 1:
|
|
||||||
outfile.write('\nHashes:')
|
|
||||||
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 len(self.hashes) > 0:
|
|
||||||
for team in range(self.world.teams):
|
|
||||||
outfile.write('%s%s\n' % (f"Hash - {self.world.player_names[player][team]} (Team {team+1}): " if self.world.teams > 1 else 'Hash: ', self.hashes[player, team]))
|
|
||||||
|
|
||||||
outfile.write('\n\nRequirements:\n\n')
|
outfile.write('\n\nRequirements:\n\n')
|
||||||
for dungeon, medallion in self.medallions.items():
|
for dungeon, medallion in self.medallions.items():
|
||||||
outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion)
|
outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion)
|
||||||
@@ -3220,7 +3276,7 @@ class Spoiler(object):
|
|||||||
outfile.write(f'\n\nBosses ({self.world.get_player_names(player)}):\n\n')
|
outfile.write(f'\n\nBosses ({self.world.get_player_names(player)}):\n\n')
|
||||||
outfile.write('\n'.join([f'{x}: {y}' for x, y in bossmap.items() if y not in ['Agahnim', 'Agahnim 2', 'Ganon']]))
|
outfile.write('\n'.join([f'{x}: {y}' for x, y in bossmap.items() if y not in ['Agahnim', 'Agahnim 2', 'Ganon']]))
|
||||||
|
|
||||||
def playthru_to_file(self, filename):
|
def playthrough_to_file(self, filename):
|
||||||
with open(filename, 'a') as outfile:
|
with open(filename, 'a') as outfile:
|
||||||
# locations: Change up location names; in the instance of a location with multiple sections, it'll try to translate the room name
|
# 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
|
# items: Item names
|
||||||
@@ -3318,15 +3374,40 @@ class PotFlags(FastEnum):
|
|||||||
Normal = 0x0
|
Normal = 0x0
|
||||||
NoSwitch = 0x1 # A switch should never go here
|
NoSwitch = 0x1 # A switch should never go here
|
||||||
SwitchLogicChange = 0x2 # A switch can go here, but requires a logic change
|
SwitchLogicChange = 0x2 # A switch can go here, but requires a logic change
|
||||||
|
Block = 0x4 # This is actually a block
|
||||||
|
LowerRegion = 0x8 # This is a pot in the lower region
|
||||||
|
|
||||||
|
|
||||||
class Pot(object):
|
class Pot(object):
|
||||||
def __init__(self, x, y, item, room, flags = PotFlags.Normal):
|
def __init__(self, x, y, item, room, flags=PotFlags.Normal, obj=None):
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
self.item = item
|
self.item = item
|
||||||
self.room = room
|
self.room = room
|
||||||
self.flags = flags
|
self.flags = flags
|
||||||
|
self.indicator = None # 0x80 for standing item, 0xC0 multiworld item
|
||||||
|
self.standing_item_code = None # standing item code if nay
|
||||||
|
self.obj_ref = obj
|
||||||
|
self.location = None # location back ref
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
obj_ref = RoomObject(self.obj_ref.address, self.obj_ref.data) if self.obj_ref else None
|
||||||
|
return Pot(self.x, self.y, self.item, self.room, self.flags, obj_ref)
|
||||||
|
|
||||||
|
def pot_data(self):
|
||||||
|
high_byte = self.y
|
||||||
|
if self.flags & PotFlags.LowerRegion:
|
||||||
|
high_byte |= 0x20
|
||||||
|
if self.indicator:
|
||||||
|
high_byte |= self.indicator
|
||||||
|
item = self.item if not self.indicator else self.standing_item_code
|
||||||
|
return [self.x, high_byte, item]
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return self.x == other.x and self.y == other.y and self.room == other.room
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self.x, self.y, self.room))
|
||||||
|
|
||||||
|
|
||||||
# byte 0: DDOO OEEE (DR, OR, ER)
|
# byte 0: DDOO OEEE (DR, OR, ER)
|
||||||
@@ -3344,33 +3425,43 @@ goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "cryst
|
|||||||
diff_mode = {"normal": 0, "hard": 1, "expert": 2}
|
diff_mode = {"normal": 0, "hard": 1, "expert": 2}
|
||||||
func_mode = {"normal": 0, "hard": 1, "expert": 2}
|
func_mode = {"normal": 0, "hard": 1, "expert": 2}
|
||||||
|
|
||||||
# byte 3: SKMM PIII (shop, keydrop, mixed, palettes, intensity)
|
# byte 3: S?MM PIII (shop, unused, mixed, palettes, intensity)
|
||||||
|
# keydrop now has it's own byte
|
||||||
mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
|
mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
|
||||||
# intensity is 3 bits (reserves 4-7 levels)
|
# intensity is 3 bits (reserves 4-7 levels)
|
||||||
|
|
||||||
# byte 4: CCCC CTTX (crystals gt, ctr2, experimental)
|
# new byte 4: ?DDD PPPP (unused, drop, pottery)
|
||||||
|
# dropshuffle reserves 2 bits, pottery needs 2 but reserves 2 for future modes)
|
||||||
|
pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7,
|
||||||
|
'clustered': 8, 'nonempty': 9}
|
||||||
|
|
||||||
|
# byte 5: CCCC CTTX (crystals gt, ctr2, experimental)
|
||||||
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
|
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
|
||||||
|
|
||||||
# byte 5: CCCC CPAA (crystals ganon, pyramid, access
|
# byte 6: CCCC CPAA (crystals ganon, pyramid, access
|
||||||
access_mode = {"items": 0, "locations": 1, "none": 2}
|
access_mode = {"items": 0, "locations": 1, "none": 2}
|
||||||
|
|
||||||
# byte 6: BSMC BBEE (big, small, maps, compass, bosses, enemies)
|
# byte 7: BSMC ??EE (big, small, maps, compass, bosses, enemies)
|
||||||
boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3}
|
enemy_mode = {"none": 0, "shuffled": 1, "chaos": 2, "random": 2, "legacy": 3}
|
||||||
enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2, "legacy": 3}
|
|
||||||
|
|
||||||
# byte 7: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links)
|
# byte 8: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links)
|
||||||
|
# potshuffle decprecated, now unused
|
||||||
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
|
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
|
||||||
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
|
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
|
||||||
|
|
||||||
# byte 8: RRAA A??? (restrict boss mode, algorithm, ? = unused)
|
# byte 9: RRAA ABBB (restrict boss mode, algorithm, boss shuffle)
|
||||||
rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2}
|
rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2}
|
||||||
# algorithm:
|
# algorithm:
|
||||||
algo_mode = {"balanced": 0, "equitable": 1, "vanilla_fill": 2, "dungeon_only": 3, "district": 4, 'major_only': 5}
|
algo_mode = {"balanced": 0, "equitable": 1, "vanilla_fill": 2, "dungeon_only": 3, "district": 4, 'major_only': 5}
|
||||||
|
boss_mode = {"none": 0, "simple": 1, "full": 2, "chaos": 3, 'random': 3, 'unique': 4}
|
||||||
|
|
||||||
# additions
|
# additions
|
||||||
# psuedoboots does not effect code
|
# psuedoboots does not effect code
|
||||||
# sfx_shuffle and other adjust items does not effect settings code
|
# sfx_shuffle and other adjust items does not effect settings code
|
||||||
|
|
||||||
|
# Bump this when making changes that are not backwards compatible (nearly all of them)
|
||||||
|
settings_version = 0
|
||||||
|
|
||||||
|
|
||||||
class Settings(object):
|
class Settings(object):
|
||||||
|
|
||||||
@@ -3385,10 +3476,12 @@ class Settings(object):
|
|||||||
(goal_mode[w.goal[p]] << 5) | (diff_mode[w.difficulty[p]] << 3)
|
(goal_mode[w.goal[p]] << 5) | (diff_mode[w.difficulty[p]] << 3)
|
||||||
| (func_mode[w.difficulty_adjustments[p]] << 1) | (1 if w.hints[p] else 0),
|
| (func_mode[w.difficulty_adjustments[p]] << 1) | (1 if w.hints[p] else 0),
|
||||||
|
|
||||||
(0x80 if w.shopsanity[p] else 0) | (0x40 if w.keydropshuffle[p] else 0)
|
(0x80 if w.shopsanity[p] else 0) | (mixed_travel_mode[w.mixed_travel[p]] << 4)
|
||||||
| (mixed_travel_mode[w.mixed_travel[p]] << 4) | (0x8 if w.standardize_palettes[p] == "original" else 0)
|
| (0x8 if w.standardize_palettes[p] == "original" else 0)
|
||||||
| (0 if w.intensity[p] == "random" else w.intensity[p]),
|
| (0 if w.intensity[p] == "random" else w.intensity[p]),
|
||||||
|
|
||||||
|
(0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[p]]),
|
||||||
|
|
||||||
((8 if w.crystals_gt_orig[p] == "random" else int(w.crystals_gt_orig[p])) << 3)
|
((8 if w.crystals_gt_orig[p] == "random" else int(w.crystals_gt_orig[p])) << 3)
|
||||||
| (counter_mode[w.dungeon_counters[p]] << 1) | (1 if w.experimental[p] else 0),
|
| (counter_mode[w.dungeon_counters[p]] << 1) | (1 if w.experimental[p] else 0),
|
||||||
|
|
||||||
@@ -3397,18 +3490,25 @@ class Settings(object):
|
|||||||
|
|
||||||
(0x80 if w.bigkeyshuffle[p] else 0) | (0x40 if w.keyshuffle[p] else 0)
|
(0x80 if w.bigkeyshuffle[p] else 0) | (0x40 if w.keyshuffle[p] else 0)
|
||||||
| (0x20 if w.mapshuffle[p] else 0) | (0x10 if w.compassshuffle[p] else 0)
|
| (0x20 if w.mapshuffle[p] else 0) | (0x10 if w.compassshuffle[p] else 0)
|
||||||
| (boss_mode[w.boss_shuffle[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]),
|
| (enemy_mode[w.enemy_shuffle[p]]),
|
||||||
|
|
||||||
(e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) | (0x4 if w.potshuffle[p] else 0)
|
(e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3) | (0x4 if w.potshuffle[p] else 0)
|
||||||
| (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0),
|
| (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0),
|
||||||
|
|
||||||
(rb_mode[w.restrict_boss_items[p]] << 6)])
|
(rb_mode[w.restrict_boss_items[p]] << 6) | (algo_mode[w.algorithm] << 3) | (boss_mode[w.boss_shuffle[p]]),
|
||||||
|
|
||||||
|
settings_version])
|
||||||
return base64.b64encode(code, "+-".encode()).decode()
|
return base64.b64encode(code, "+-".encode()).decode()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def adjust_args_from_code(code, player, args):
|
def adjust_args_from_code(code, player, args):
|
||||||
settings, p = base64.b64decode(code.encode(), "+-".encode()), player
|
settings, p = base64.b64decode(code.encode(), "+-".encode()), player
|
||||||
|
|
||||||
|
if len(settings) < 11:
|
||||||
|
raise Exception('Provided code is incompatible with this version')
|
||||||
|
if settings[10] != settings_version:
|
||||||
|
raise Exception('Provided code is incompatible with this version')
|
||||||
|
|
||||||
def r(d):
|
def r(d):
|
||||||
return {y: x for x, y in d.items()}
|
return {y: x for x, y in d.items()}
|
||||||
|
|
||||||
@@ -3421,36 +3521,45 @@ class Settings(object):
|
|||||||
args.difficulty[p] = r(diff_mode)[(settings[2] & 0x18) >> 3]
|
args.difficulty[p] = r(diff_mode)[(settings[2] & 0x18) >> 3]
|
||||||
args.item_functionality[p] = r(func_mode)[(settings[2] & 0x6) >> 1]
|
args.item_functionality[p] = r(func_mode)[(settings[2] & 0x6) >> 1]
|
||||||
args.goal[p] = r(goal_mode)[(settings[2] & 0xE0) >> 5]
|
args.goal[p] = r(goal_mode)[(settings[2] & 0xE0) >> 5]
|
||||||
args.accessibility[p] = r(access_mode)[settings[5] & 0x3]
|
args.accessibility[p] = r(access_mode)[settings[6] & 0x3]
|
||||||
args.retro[p] = True if settings[1] & 0x01 else False
|
args.retro[p] = True if settings[1] & 0x01 else False
|
||||||
args.hints[p] = True if settings[2] & 0x01 else False
|
args.hints[p] = True if settings[2] & 0x01 else False
|
||||||
args.retro[p] = True if settings[1] & 0x01 else False
|
|
||||||
args.shopsanity[p] = True if settings[3] & 0x80 else False
|
args.shopsanity[p] = True if settings[3] & 0x80 else False
|
||||||
args.keydropshuffle[p] = True if settings[3] & 0x40 else False
|
# args.keydropshuffle[p] = True if settings[3] & 0x40 else False
|
||||||
args.mixed_travel[p] = r(mixed_travel_mode)[(settings[3] & 0x30) >> 4]
|
args.mixed_travel[p] = r(mixed_travel_mode)[(settings[3] & 0x30) >> 4]
|
||||||
args.standardize_palettes[p] = "original" if settings[3] & 0x8 else "standardize"
|
args.standardize_palettes[p] = "original" if settings[3] & 0x8 else "standardize"
|
||||||
intensity = settings[3] & 0x7
|
intensity = settings[3] & 0x7
|
||||||
args.intensity[p] = "random" if intensity == 0 else intensity
|
args.intensity[p] = "random" if intensity == 0 else intensity
|
||||||
args.dungeon_counters[p] = r(counter_mode)[(settings[4] & 0x6) >> 1]
|
|
||||||
cgt = (settings[4] & 0xf8) >> 3
|
# args.shuffleswitches[p] = True if settings[4] & 0x80 else False
|
||||||
|
args.dropshuffle[p] = True if settings[4] & 0x10 else False
|
||||||
|
args.pottery[p] = r(pottery_mode)[settings[4] & 0x0F]
|
||||||
|
|
||||||
|
args.dungeon_counters[p] = r(counter_mode)[(settings[5] & 0x6) >> 1]
|
||||||
|
cgt = (settings[5] & 0xf8) >> 3
|
||||||
args.crystals_gt[p] = "random" if cgt == 8 else cgt
|
args.crystals_gt[p] = "random" if cgt == 8 else cgt
|
||||||
args.experimental[p] = True if settings[4] & 0x1 else False
|
args.experimental[p] = True if settings[5] & 0x1 else False
|
||||||
cgan = (settings[5] & 0xf8) >> 3
|
|
||||||
|
cgan = (settings[6] & 0xf8) >> 3
|
||||||
args.crystals_ganon[p] = "random" if cgan == 8 else cgan
|
args.crystals_ganon[p] = "random" if cgan == 8 else cgan
|
||||||
args.openpyramid[p] = True if settings[5] & 0x4 else False
|
args.openpyramid[p] = True if settings[6] & 0x4 else False
|
||||||
args.bigkeyshuffle[p] = True if settings[6] & 0x80 else False
|
|
||||||
args.keyshuffle[p] = True if settings[6] & 0x40 else False
|
args.bigkeyshuffle[p] = True if settings[7] & 0x80 else False
|
||||||
args.mapshuffle[p] = True if settings[6] & 0x20 else False
|
args.keyshuffle[p] = True if settings[7] & 0x40 else False
|
||||||
args.compassshuffle[p] = True if settings[6] & 0x10 else False
|
args.mapshuffle[p] = True if settings[7] & 0x20 else False
|
||||||
args.shufflebosses[p] = r(boss_mode)[(settings[6] & 0xc) >> 2]
|
args.compassshuffle[p] = True if settings[7] & 0x10 else False
|
||||||
args.shuffleenemies[p] = r(enemy_mode)[settings[6] & 0x3]
|
# args.shufflebosses[p] = r(boss_mode)[(settings[7] & 0xc) >> 2]
|
||||||
args.enemy_health[p] = r(e_health)[(settings[7] & 0xE0) >> 5]
|
args.shuffleenemies[p] = r(enemy_mode)[settings[7] & 0x3]
|
||||||
args.enemy_damage[p] = r(e_dmg)[(settings[7] & 0x18) >> 3]
|
|
||||||
args.shufflepots[p] = True if settings[7] & 0x4 else False
|
args.enemy_health[p] = r(e_health)[(settings[8] & 0xE0) >> 5]
|
||||||
args.bombbag[p] = True if settings[7] & 0x2 else False
|
args.enemy_damage[p] = r(e_dmg)[(settings[8] & 0x18) >> 3]
|
||||||
args.shufflelinks[p] = True if settings[7] & 0x1 else False
|
args.shufflepots[p] = True if settings[8] & 0x4 else False
|
||||||
if len(settings) > 8:
|
args.bombbag[p] = True if settings[8] & 0x2 else False
|
||||||
args.restrict_boss_items[p] = True if r(rb_mode)[(settings[8] & 0x80) >> 6] else False
|
args.shufflelinks[p] = True if settings[8] & 0x1 else False
|
||||||
|
if len(settings) > 9:
|
||||||
|
args.restrict_boss_items[p] = r(rb_mode)[(settings[9] & 0xC0) >> 6]
|
||||||
|
args.algorithm = r(algo_mode)[(settings[9] & 0x38) >> 3]
|
||||||
|
args.shufflebosses[p] = r(boss_mode)[(settings[9] & 0x07)]
|
||||||
|
|
||||||
|
|
||||||
class KeyRuleType(FastEnum):
|
class KeyRuleType(FastEnum):
|
||||||
|
|||||||
@@ -166,18 +166,19 @@ def place_bosses(world, player):
|
|||||||
all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons
|
all_bosses = sorted(boss_table.keys()) #s orted to be deterministic on older pythons
|
||||||
placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]
|
placeable_bosses = [boss for boss in all_bosses if boss not in ['Agahnim', 'Agahnim2', 'Ganon']]
|
||||||
|
|
||||||
if world.boss_shuffle[player] in ["simple", "full"]:
|
|
||||||
# temporary hack for swordless kholdstare:
|
# temporary hack for swordless kholdstare:
|
||||||
|
if world.boss_shuffle[player] in ["simple", "full", "unique"]:
|
||||||
if world.swords[player] == 'swordless':
|
if world.swords[player] == 'swordless':
|
||||||
world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player)
|
world.get_dungeon('Ice Palace', player).boss = BossFactory('Kholdstare', player)
|
||||||
logging.getLogger('').debug('Placing boss Kholdstare at Ice Palace')
|
logging.getLogger('').debug('Placing boss Kholdstare at Ice Palace')
|
||||||
boss_locations.remove(['Ice Palace', None])
|
boss_locations.remove(['Ice Palace', None])
|
||||||
placeable_bosses.remove('Kholdstare')
|
placeable_bosses.remove('Kholdstare')
|
||||||
|
|
||||||
|
if world.boss_shuffle[player] in ["simple", "full"]:
|
||||||
if world.boss_shuffle[player] == "simple": # vanilla bosses shuffled
|
if world.boss_shuffle[player] == "simple": # vanilla bosses shuffled
|
||||||
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
|
bosses = placeable_bosses + ['Armos Knights', 'Lanmolas', 'Moldorm']
|
||||||
else: # all bosses present, the three duplicates chosen at random
|
else: # all bosses present, the three duplicates chosen at random
|
||||||
bosses = all_bosses + random.sample(placeable_bosses, 3)
|
bosses = placeable_bosses + random.sample(placeable_bosses, 3)
|
||||||
|
|
||||||
logging.getLogger('').debug('Bosses chosen %s', bosses)
|
logging.getLogger('').debug('Bosses chosen %s', bosses)
|
||||||
|
|
||||||
@@ -189,12 +190,7 @@ def place_bosses(world, player):
|
|||||||
raise FillError('Could not place boss for location %s' % loc_text)
|
raise FillError('Could not place boss for location %s' % loc_text)
|
||||||
bosses.remove(boss)
|
bosses.remove(boss)
|
||||||
|
|
||||||
# GT Bosses can move dungeon - find the real dungeon to place them in
|
place_boss(boss, level, loc, loc_text, world, player)
|
||||||
if level:
|
|
||||||
loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0]
|
|
||||||
loc_text = loc + ' (' + level + ')'
|
|
||||||
logging.getLogger('').debug('Placing boss %s at %s', boss, loc_text)
|
|
||||||
world.get_dungeon(loc, player).bosses[level] = BossFactory(boss, player)
|
|
||||||
elif world.boss_shuffle[player] == "random": #all bosses chosen at random
|
elif world.boss_shuffle[player] == "random": #all bosses chosen at random
|
||||||
for [loc, level] in boss_locations:
|
for [loc, level] in boss_locations:
|
||||||
loc_text = loc + (' ('+level+')' if level else '')
|
loc_text = loc + (' ('+level+')' if level else '')
|
||||||
@@ -203,6 +199,25 @@ def place_bosses(world, player):
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
raise FillError('Could not place boss for location %s' % loc_text)
|
raise FillError('Could not place boss for location %s' % loc_text)
|
||||||
|
|
||||||
|
place_boss(boss, level, loc, loc_text, world, player)
|
||||||
|
elif world.boss_shuffle[player] == 'unique':
|
||||||
|
bosses = list(placeable_bosses)
|
||||||
|
|
||||||
|
for [loc, level] in boss_locations:
|
||||||
|
loc_text = loc + (' ('+level+')' if level else '')
|
||||||
|
try:
|
||||||
|
if level:
|
||||||
|
boss = random.choice([b for b in placeable_bosses if can_place_boss(world, player, b, loc, level)])
|
||||||
|
else:
|
||||||
|
boss = random.choice([b for b in bosses if can_place_boss(world, player, b, loc, level)])
|
||||||
|
bosses.remove(boss)
|
||||||
|
except IndexError:
|
||||||
|
raise FillError('Could not place boss for location %s' % loc_text)
|
||||||
|
|
||||||
|
place_boss(boss, level, loc, loc_text, world, player)
|
||||||
|
|
||||||
|
|
||||||
|
def place_boss(boss, level, loc, loc_text, world, player):
|
||||||
# GT Bosses can move dungeon - find the real dungeon to place them in
|
# GT Bosses can move dungeon - find the real dungeon to place them in
|
||||||
if level:
|
if level:
|
||||||
loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0]
|
loc = [x.name for x in world.dungeons if x.player == player and level in x.bosses.keys()][0]
|
||||||
|
|||||||
+30
-23
@@ -1,8 +1,15 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### 0.2.8.0
|
||||||
|
- ~Merged DR v1.0.1.0 - Pottery options, BPS support, MSU Resume, Collection Rate Counter~
|
||||||
|
- Various improvements to increase generation success rate and reduce generation time
|
||||||
|
- Fixed issue with playthru recognizing Aga accessibility
|
||||||
|
- Fixed issue with applying rules correctly to Murahdahla, fixing Murahdahla+Beatable issues
|
||||||
|
- Fixed issue with Flute+Rainstate, flute use is no longer in logic until Zelda is delivered
|
||||||
|
|
||||||
### 0.2.7.3
|
### 0.2.7.3
|
||||||
- Restructured OWR algorithm to include some additional scenarios not previously allowed
|
- Restructured OWR algorithm to include some additional scenarios not previously allowed
|
||||||
- Added new Inverted D-pad controls for Social Distorion (ie. Mirror Mode) support
|
- Added new Inverted D-pad controls for Social Distortion (ie. Mirror Mode) support
|
||||||
- Crossed OWR/Special OW Areas are now included in the spoiler log
|
- Crossed OWR/Special OW Areas are now included in the spoiler log
|
||||||
- Fixed default TF pieces with Trinity in Mystery
|
- Fixed default TF pieces with Trinity in Mystery
|
||||||
- Added bush crabs to rupee farm logic (only in non-enemizer)
|
- Added bush crabs to rupee farm logic (only in non-enemizer)
|
||||||
@@ -19,7 +26,7 @@
|
|||||||
- Added proper branch-specific versioning (ie. Dev branch has '-u' suffixing the version number while Release/Main branch does not)
|
- Added proper branch-specific versioning (ie. Dev branch has '-u' suffixing the version number while Release/Main branch does not)
|
||||||
|
|
||||||
### 0.2.7.0
|
### 0.2.7.0
|
||||||
- ~~Merged DR v1.0.0.3 - MANY changes, major things listed below~~
|
- ~Merged DR v1.0.0.3 - MANY changes, major things listed below~
|
||||||
- New Item Fills (Districts/Vanilla/Major Location/Dungeon)
|
- New Item Fills (Districts/Vanilla/Major Location/Dungeon)
|
||||||
- New OW Map Prize Indicators (In ER, map checks can spoil dungeon locations with a user setting)
|
- New OW Map Prize Indicators (In ER, map checks can spoil dungeon locations with a user setting)
|
||||||
- Forbidden Boss Items (Exclude certain dungeon items from dropping on bosses)
|
- Forbidden Boss Items (Exclude certain dungeon items from dropping on bosses)
|
||||||
@@ -70,7 +77,7 @@
|
|||||||
- Fixed issue with incorrect Mirror bonking
|
- Fixed issue with incorrect Mirror bonking
|
||||||
- Fixed issue with old man follower death to Pyramid
|
- Fixed issue with old man follower death to Pyramid
|
||||||
- Fixed Hera boss music not playing when boss not defeated
|
- Fixed Hera boss music not playing when boss not defeated
|
||||||
- ~~Merged DR v0.5.1.7 - TT boss trap door fix/Applied Glitched flag~~
|
- ~Merged DR v0.5.1.7 - TT boss trap door fix/Applied Glitched flag~
|
||||||
|
|
||||||
### 0.2.4.0
|
### 0.2.4.0
|
||||||
- Added Guaranteed OWR Reachability
|
- Added Guaranteed OWR Reachability
|
||||||
@@ -96,7 +103,7 @@
|
|||||||
- Fake flipper damage fix improved to skip the long delay after the scroll
|
- Fake flipper damage fix improved to skip the long delay after the scroll
|
||||||
- Fixed missing Blue Potion in Lake Shop in Inverted
|
- Fixed missing Blue Potion in Lake Shop in Inverted
|
||||||
- Added legacy OW Crossed option 'None (Allowed)' to support old behavior when invalid option was used in Mystery
|
- Added legacy OW Crossed option 'None (Allowed)' to support old behavior when invalid option was used in Mystery
|
||||||
- ~~Merged DR v0.5.1.6 - Money balancing fix/Boss logic fixes with Bombbag~~
|
- ~Merged DR v0.5.1.6 - Money balancing fix/Boss logic fixes with Bombbag~
|
||||||
|
|
||||||
### 0.2.3.3
|
### 0.2.3.3
|
||||||
- Added OW Layout validation that reduces the cases where some screens are unreachable
|
- Added OW Layout validation that reduces the cases where some screens are unreachable
|
||||||
@@ -138,7 +145,7 @@
|
|||||||
- Fixed music track change to Sanc music when Standard mode is delivering Zelda
|
- Fixed music track change to Sanc music when Standard mode is delivering Zelda
|
||||||
- Fixed SP flooding issue
|
- Fixed SP flooding issue
|
||||||
- Fixed issue with Shuffle Ganon in CLI/GUI
|
- Fixed issue with Shuffle Ganon in CLI/GUI
|
||||||
- ~~Merged DR v0.5.1.5 - Mystery subweights~~
|
- ~Merged DR v0.5.1.5 - Mystery subweights~
|
||||||
|
|
||||||
### 0.2.1.2
|
### 0.2.1.2
|
||||||
- Fixed issue with whirlpools not changing world when in Crossed OW
|
- Fixed issue with whirlpools not changing world when in Crossed OW
|
||||||
@@ -159,7 +166,7 @@
|
|||||||
- Smith deletion on S+Q only occurs if Blacksmith not reachable from starting locations
|
- Smith deletion on S+Q only occurs if Blacksmith not reachable from starting locations
|
||||||
- Spoiler log improvements to prevent spoiling in the beginning 'meta' section
|
- Spoiler log improvements to prevent spoiling in the beginning 'meta' section
|
||||||
- Various minor fixes and improvements
|
- Various minor fixes and improvements
|
||||||
- ~~Merged DR v0.5.1.4 - ROM bug fixes/keylogic improvements~~
|
- ~Merged DR v0.5.1.4 - ROM bug fixes/keylogic improvements~
|
||||||
|
|
||||||
### 0.1.9.4
|
### 0.1.9.4
|
||||||
- Hotfix for bad 0.1.9.3 version
|
- Hotfix for bad 0.1.9.3 version
|
||||||
@@ -172,11 +179,11 @@
|
|||||||
### 0.1.9.2
|
### 0.1.9.2
|
||||||
- Fixed spoiler log and mystery for new Crossed/Mixed structure
|
- Fixed spoiler log and mystery for new Crossed/Mixed structure
|
||||||
- Minor preparations and tweaks to ER framework (added global Entrance/Exit pool)
|
- Minor preparations and tweaks to ER framework (added global Entrance/Exit pool)
|
||||||
- ~~Merged DR v0.5.1.2 - Blind Prison shuffled outside TT/Keylogic Improvements~~
|
- ~Merged DR v0.5.1.2 - Blind Prison shuffled outside TT/Keylogic Improvements~
|
||||||
|
|
||||||
### 0.1.9.1
|
### 0.1.9.1
|
||||||
- Fixed logic issue with leaving IP entrance not requiring flippers
|
- Fixed logic issue with leaving IP entrance not requiring flippers
|
||||||
- ~~Merged DR v0.5.1.1 - Map Indicator Fix/Boss Shuffle Bias/Shop Hints~~
|
- ~Merged DR v0.5.1.1 - Map Indicator Fix/Boss Shuffle Bias/Shop Hints~
|
||||||
|
|
||||||
### 0.1.9.0
|
### 0.1.9.0
|
||||||
- Expanded Crossed OW to four separate options, see Readme for details
|
- Expanded Crossed OW to four separate options, see Readme for details
|
||||||
@@ -190,7 +197,7 @@
|
|||||||
- Fixed issues with Link/Bunny state in Crossed OW
|
- Fixed issues with Link/Bunny state in Crossed OW
|
||||||
- Fixed issue with Standard+Parallel not using vanilla connections for Escape
|
- Fixed issue with Standard+Parallel not using vanilla connections for Escape
|
||||||
- Fixed issue with Mystery for OW boolean options
|
- Fixed issue with Mystery for OW boolean options
|
||||||
- ~~Merged DR v0.5.1.0 - Major Keylogic Update~~
|
- ~Merged DR v0.5.1.0 - Major Keylogic Update~
|
||||||
|
|
||||||
### 0.1.8.1
|
### 0.1.8.1
|
||||||
- Fixed issue with activating flute in DW (OW Mixed)
|
- Fixed issue with activating flute in DW (OW Mixed)
|
||||||
@@ -205,12 +212,12 @@
|
|||||||
- Added OW Shuffle support for Plando module (needs user testing)
|
- Added OW Shuffle support for Plando module (needs user testing)
|
||||||
- Fixed issue with Sanc start at TR as bunny when it is LW
|
- Fixed issue with Sanc start at TR as bunny when it is LW
|
||||||
- Fixed issue with Pyramid Hole not getting shuffled
|
- Fixed issue with Pyramid Hole not getting shuffled
|
||||||
- ~~Merged DR v0.5.0.3 - Minor DR fixes~~
|
- ~Merged DR v0.5.0.3 - Minor DR fixes~
|
||||||
|
|
||||||
### 0.1.7.4
|
### 0.1.7.4
|
||||||
- Fixed issue with Mixed OW failing to generate when HC/Pyramid is swapped
|
- Fixed issue with Mixed OW failing to generate when HC/Pyramid is swapped
|
||||||
- Various fixes to improve generation rates for Mixed OW Shuffle
|
- Various fixes to improve generation rates for Mixed OW Shuffle
|
||||||
- ~~Merged DR v0.5.0.2 - Shuffle SFX~~
|
- ~Merged DR v0.5.0.2 - Shuffle SFX~
|
||||||
|
|
||||||
### 0.1.7.3
|
### 0.1.7.3
|
||||||
- Fixed minor issue with ambient SFX stopping and starting on OW screen load
|
- Fixed minor issue with ambient SFX stopping and starting on OW screen load
|
||||||
@@ -230,10 +237,10 @@
|
|||||||
|
|
||||||
### 0.1.7.0
|
### 0.1.7.0
|
||||||
- Expanded new DR bomb logic to all modes (bomb usage in logic only if there is an unlimited supply of bombs available)
|
- Expanded new DR bomb logic to all modes (bomb usage in logic only if there is an unlimited supply of bombs available)
|
||||||
- ~~Merged DR v0.5.0.1 - Bombbag mode / Enemizer fixes~~
|
- ~Merged DR v0.5.0.1 - Bombbag mode / Enemizer fixes~
|
||||||
|
|
||||||
### 0.1.6.9
|
### 0.1.6.9
|
||||||
- ~~Merged DR v0.4.0.12 - Secure random update / Credits fix~~
|
- ~Merged DR v0.4.0.12 - Secure random update / Credits fix~
|
||||||
|
|
||||||
### 0.1.6.8
|
### 0.1.6.8
|
||||||
- Implemented a smarter Balanced Flute Shuffle algorithm
|
- Implemented a smarter Balanced Flute Shuffle algorithm
|
||||||
@@ -247,14 +254,14 @@
|
|||||||
- Fixed Boss Music when boss room is entered thru straight stairs
|
- Fixed Boss Music when boss room is entered thru straight stairs
|
||||||
- Suppressed in-dungeon music changes when DR is enabled
|
- Suppressed in-dungeon music changes when DR is enabled
|
||||||
- Fixed issue with Pyramid Exit exiting to wrong location in ER
|
- Fixed issue with Pyramid Exit exiting to wrong location in ER
|
||||||
- ~~Merged DR v0.4.0.11 - Various DR changes~~
|
- ~Merged DR v0.4.0.11 - Various DR changes~
|
||||||
|
|
||||||
### 0.1.6.6
|
### 0.1.6.6
|
||||||
- ~~Merged DR v0.4.0.9 - P/C Indicator / Credits fix / CLI Hints Fix~~
|
- ~Merged DR v0.4.0.9 - P/C Indicator / Credits fix / CLI Hints Fix~
|
||||||
|
|
||||||
### 0.1.6.5
|
### 0.1.6.5
|
||||||
- Reduced chance of diagonal flute spot in Balanced
|
- Reduced chance of diagonal flute spot in Balanced
|
||||||
- ~~Merged DR v0.4.0.8 - Boss Indicator / Psuedo Boots / Quickswap Update / Credits Updates~~
|
- ~Merged DR v0.4.0.8 - Boss Indicator / Psuedo Boots / Quickswap Update / Credits Updates~
|
||||||
|
|
||||||
### 0.1.6.4
|
### 0.1.6.4
|
||||||
- Fixed Frogsmith and Stumpy and restored progression in these locations
|
- Fixed Frogsmith and Stumpy and restored progression in these locations
|
||||||
@@ -293,15 +300,15 @@
|
|||||||
### 0.1.5.0
|
### 0.1.5.0
|
||||||
- Added OW Tile Swap setting
|
- Added OW Tile Swap setting
|
||||||
- Fixed horizontal VRAM visual loading glitch on megatiles
|
- Fixed horizontal VRAM visual loading glitch on megatiles
|
||||||
- ~~Merged DR v0.4.0.7 - Fast Credits / Reduced Flashing / Sprite Author in Credits~~
|
- ~~Merged DR v0.4.0.7 - Fast Credits / Reduced Flashing / Sprite Author in Credits~~ Didn't fully merge
|
||||||
|
|
||||||
### 0.1.4.3
|
### 0.1.4.3
|
||||||
- Merged DR v0.4.0.6 - TT Maiden Attic Hint / DR Entrance Floor Mat Mods / Hard/Expert Item Pool Fix
|
- ~Merged DR v0.4.0.6 - TT Maiden Attic Hint / DR Entrance Floor Mat Mods / Hard/Expert Item Pool Fix~
|
||||||
|
|
||||||
### 0.1.4.2
|
### 0.1.4.2
|
||||||
- Modified various OW map terrain specific to OW Shuffle
|
- Modified various OW map terrain specific to OW Shuffle
|
||||||
- Changed World check to table-based vs OW ID-based (should have no effect with current modes)
|
- Changed World check to table-based vs OW ID-based (should have no effect with current modes)
|
||||||
- Merged DR v0.4.0.5 - Mystery Boss Shuffle Fix / Swordless+Hard Item Pool Fix / Insanity+Inverted ER Fixes
|
- ~Merged DR v0.4.0.5 - Mystery Boss Shuffle Fix / Swordless+Hard Item Pool Fix / Insanity+Inverted ER Fixes~
|
||||||
|
|
||||||
### 0.1.4.1
|
### 0.1.4.1
|
||||||
- Moved Inverted Pyramid Entrance to top of HC Ledge
|
- Moved Inverted Pyramid Entrance to top of HC Ledge
|
||||||
@@ -315,11 +322,11 @@
|
|||||||
- Various logic fixes and region prep for Inverted
|
- Various logic fixes and region prep for Inverted
|
||||||
- Fixed muted MSU-1 music in door rando when descending GT Climb stairs
|
- Fixed muted MSU-1 music in door rando when descending GT Climb stairs
|
||||||
- Fixed Standard + Vanilla (thanks compiling)
|
- Fixed Standard + Vanilla (thanks compiling)
|
||||||
- Merged DR v0.4.0.4 - Shuffle Link's House / Experimental Bunny Start / 10 Bomb Fix
|
- ~Merged DR v0.4.0.4 - Shuffle Link's House / Experimental Bunny Start / 10 Bomb Fix~
|
||||||
|
|
||||||
### 0.1.3.0
|
### 0.1.3.0
|
||||||
- Added OWG Logic for OW Shuffle
|
- Added OWG Logic for OW Shuffle
|
||||||
- Merged DR v0.4.0.2 - OWG Framework / YAML
|
- ~Merged DR v0.4.0.2 - OWG Framework / YAML~
|
||||||
|
|
||||||
### 0.1.2.2
|
### 0.1.2.2
|
||||||
- Re-purposed OW Shuffle setting to Layout Shuffle
|
- Re-purposed OW Shuffle setting to Layout Shuffle
|
||||||
@@ -328,7 +335,7 @@
|
|||||||
|
|
||||||
### 0.1.2.1
|
### 0.1.2.1
|
||||||
- Made possible fix for Standard
|
- Made possible fix for Standard
|
||||||
- Merged DR v0.3.1.10 - Fixed Standard generation
|
- ~Merged DR v0.3.1.10 - Fixed Standard generation~
|
||||||
|
|
||||||
### 0.1.2.0
|
### 0.1.2.0
|
||||||
- Added 'Parallel Worlds' toggle option
|
- Added 'Parallel Worlds' toggle option
|
||||||
@@ -338,7 +345,7 @@
|
|||||||
### 0.1.1.2
|
### 0.1.1.2
|
||||||
- If Link's current position fits within the incoming gap, Link will not get re-centered to the incoming gap
|
- If Link's current position fits within the incoming gap, Link will not get re-centered to the incoming gap
|
||||||
- Added Rule for Pearl required to drop down back of SW
|
- Added Rule for Pearl required to drop down back of SW
|
||||||
- Merged DR v0.3.1.8 - Improved Shopsanity pricing - Fixed Retro generation
|
- ~Merged DR v0.3.1.8 - Improved Shopsanity pricing - Fixed Retro generation~
|
||||||
|
|
||||||
### 0.1.1.1
|
### 0.1.1.1
|
||||||
- Fixed camera unlocking issue
|
- Fixed camera unlocking issue
|
||||||
|
|||||||
@@ -88,6 +88,10 @@ def parse_cli(argv, no_defaults=False):
|
|||||||
if ret.keysanity:
|
if ret.keysanity:
|
||||||
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4
|
ret.mapshuffle, ret.compassshuffle, ret.keyshuffle, ret.bigkeyshuffle = [True] * 4
|
||||||
|
|
||||||
|
if ret.keydropshuffle:
|
||||||
|
ret.dropshuffle = True
|
||||||
|
ret.pottery = 'keys' if ret.pottery == 'none' else ret.pottery
|
||||||
|
|
||||||
if multiargs.multi:
|
if multiargs.multi:
|
||||||
defaults = copy.deepcopy(ret)
|
defaults = copy.deepcopy(ret)
|
||||||
for player in range(1, multiargs.multi + 1):
|
for player in range(1, multiargs.multi + 1):
|
||||||
@@ -97,14 +101,15 @@ def parse_cli(argv, no_defaults=False):
|
|||||||
'ow_shuffle', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
|
'ow_shuffle', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
|
||||||
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
|
||||||
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
|
||||||
'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
|
'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
|
||||||
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
|
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max',
|
||||||
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots',
|
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'pseudoboots',
|
||||||
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
'retro', 'accessibility', 'hints', 'beemizer', 'experimental', 'dungeon_counters',
|
||||||
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
'shufflebosses', 'shuffleenemies', 'enemy_health', 'enemy_damage', 'shufflepots',
|
||||||
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor', 'heartbeep',
|
'ow_palettes', 'uw_palettes', 'sprite', 'disablemusic', 'quickswap', 'fastmenu', 'heartcolor',
|
||||||
'remote_items', 'shopsanity', 'keydropshuffle', 'mixed_travel', 'standardize_palettes', 'code',
|
'heartbeep', 'remote_items', 'shopsanity', 'dropshuffle', 'pottery', 'keydropshuffle',
|
||||||
'reduce_flashing', 'shuffle_sfx']:
|
'mixed_travel', 'standardize_palettes', 'code', 'reduce_flashing', 'shuffle_sfx',
|
||||||
|
'msu_resume', 'collection_rate', 'colorizepots']:
|
||||||
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
|
||||||
if player == 1:
|
if player == 1:
|
||||||
setattr(ret, name, {1: value})
|
setattr(ret, name, {1: value})
|
||||||
@@ -141,6 +146,8 @@ def parse_settings():
|
|||||||
"progressive": "on",
|
"progressive": "on",
|
||||||
"accessibility": "items",
|
"accessibility": "items",
|
||||||
"algorithm": "balanced",
|
"algorithm": "balanced",
|
||||||
|
'mystery': False,
|
||||||
|
'suppress_meta': False,
|
||||||
"restrict_boss_items": "none",
|
"restrict_boss_items": "none",
|
||||||
|
|
||||||
# Shuffle Ganon defaults to TRUE
|
# Shuffle Ganon defaults to TRUE
|
||||||
@@ -157,7 +164,6 @@ def parse_settings():
|
|||||||
"overworld_map": "default",
|
"overworld_map": "default",
|
||||||
"pseudoboots": False,
|
"pseudoboots": False,
|
||||||
|
|
||||||
"shufflepots": False,
|
|
||||||
"shuffleenemies": "none",
|
"shuffleenemies": "none",
|
||||||
"shufflebosses": "none",
|
"shufflebosses": "none",
|
||||||
"enemy_damage": "default",
|
"enemy_damage": "default",
|
||||||
@@ -165,7 +171,11 @@ def parse_settings():
|
|||||||
"enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),
|
"enemizercli": os.path.join(".", "EnemizerCLI", "EnemizerCLI.Core"),
|
||||||
|
|
||||||
"shopsanity": False,
|
"shopsanity": False,
|
||||||
"keydropshuffle": False,
|
'keydropshuffle': False,
|
||||||
|
'dropshuffle': False,
|
||||||
|
'pottery': 'none',
|
||||||
|
'colorizepots': False,
|
||||||
|
'shufflepots': False,
|
||||||
"mapshuffle": False,
|
"mapshuffle": False,
|
||||||
"compassshuffle": False,
|
"compassshuffle": False,
|
||||||
"keyshuffle": False,
|
"keyshuffle": False,
|
||||||
@@ -202,6 +212,8 @@ def parse_settings():
|
|||||||
"uw_palettes": "default",
|
"uw_palettes": "default",
|
||||||
"reduce_flashing": False,
|
"reduce_flashing": False,
|
||||||
"shuffle_sfx": False,
|
"shuffle_sfx": False,
|
||||||
|
'msu_resume': False,
|
||||||
|
'collection_rate': False,
|
||||||
|
|
||||||
# Spoiler defaults to TRUE
|
# Spoiler defaults to TRUE
|
||||||
# Playthrough defaults to TRUE
|
# Playthrough defaults to TRUE
|
||||||
@@ -209,9 +221,11 @@ def parse_settings():
|
|||||||
"create_spoiler": True,
|
"create_spoiler": True,
|
||||||
"calc_playthrough": True,
|
"calc_playthrough": True,
|
||||||
"create_rom": True,
|
"create_rom": True,
|
||||||
|
"bps": False,
|
||||||
"usestartinventory": False,
|
"usestartinventory": False,
|
||||||
"custom": False,
|
"custom": False,
|
||||||
"rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"),
|
"rom": os.path.join(".", "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc"),
|
||||||
|
"patch": os.path.join(".", "Patch File.bps"),
|
||||||
|
|
||||||
"seed": "",
|
"seed": "",
|
||||||
"count": 1,
|
"count": 1,
|
||||||
|
|||||||
+77
-22
@@ -6,6 +6,7 @@ from enum import unique, Flag
|
|||||||
from typing import DefaultDict, Dict, List
|
from typing import DefaultDict, Dict, List
|
||||||
|
|
||||||
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
from BaseClasses import RegionType, Region, Door, DoorType, Direction, Sector, CrystalBarrier, DungeonInfo, dungeon_keys
|
||||||
|
from BaseClasses import PotFlags, LocationType
|
||||||
from Doors import reset_portals
|
from Doors import reset_portals
|
||||||
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
from Dungeons import dungeon_regions, region_starts, standard_starts, split_region_starts
|
||||||
from Dungeons import dungeon_bigs, dungeon_hints
|
from Dungeons import dungeon_bigs, dungeon_hints
|
||||||
@@ -380,7 +381,7 @@ def choose_portals(world, player):
|
|||||||
if world.doorShuffle[player] in ['basic', 'crossed']:
|
if world.doorShuffle[player] in ['basic', 'crossed']:
|
||||||
cross_flag = world.doorShuffle[player] == 'crossed'
|
cross_flag = world.doorShuffle[player] == 'crossed'
|
||||||
# key drops allow the big key in the right place in Desert Tiles 2
|
# key drops allow the big key in the right place in Desert Tiles 2
|
||||||
bk_shuffle = world.bigkeyshuffle[player] or world.keydropshuffle[player]
|
bk_shuffle = world.bigkeyshuffle[player] or world.dropshuffle[player]
|
||||||
std_flag = world.mode[player] == 'standard'
|
std_flag = world.mode[player] == 'standard'
|
||||||
# roast incognito doors
|
# roast incognito doors
|
||||||
world.get_room(0x60, player).delete(5)
|
world.get_room(0x60, player).delete(5)
|
||||||
@@ -414,7 +415,7 @@ def choose_portals(world, player):
|
|||||||
info.sole_entrance = inaccessible_portals[0]
|
info.sole_entrance = inaccessible_portals[0]
|
||||||
info.required_passage.clear()
|
info.required_passage.clear()
|
||||||
else:
|
else:
|
||||||
raise Exception('please inspect this case')
|
raise Exception(f'No reachable entrances for {dungeon}')
|
||||||
if len(reachable_portals) == 1:
|
if len(reachable_portals) == 1:
|
||||||
info.sole_entrance = reachable_portals[0]
|
info.sole_entrance = reachable_portals[0]
|
||||||
info_map[dungeon] = info
|
info_map[dungeon] = info
|
||||||
@@ -521,7 +522,7 @@ def analyze_portals(world, player):
|
|||||||
info.sole_entrance = inaccessible_portals[0]
|
info.sole_entrance = inaccessible_portals[0]
|
||||||
info.required_passage.clear()
|
info.required_passage.clear()
|
||||||
else:
|
else:
|
||||||
raise Exception('please inspect this case')
|
raise Exception(f'No reachable entrances for {dungeon}')
|
||||||
if len(reachable_portals) == 1:
|
if len(reachable_portals) == 1:
|
||||||
info.sole_entrance = reachable_portals[0]
|
info.sole_entrance = reachable_portals[0]
|
||||||
if world.intensity[player] < 2 and world.doorShuffle[player] == 'basic' and dungeon == 'Desert Palace':
|
if world.intensity[player] < 2 and world.doorShuffle[player] == 'basic' and dungeon == 'Desert Palace':
|
||||||
@@ -996,10 +997,15 @@ def cross_dungeon(world, player):
|
|||||||
|
|
||||||
assign_cross_keys(dungeon_builders, world, player)
|
assign_cross_keys(dungeon_builders, world, player)
|
||||||
all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items))
|
all_dungeon_items_cnt = len(list(y for x in world.dungeons if x.player == player for y in x.all_items))
|
||||||
if world.keydropshuffle[player]:
|
target_items = 34
|
||||||
target_items = 35 if world.retro[player] else 96
|
if world.retro[player]:
|
||||||
|
target_items += 1 if world.dropshuffle[player] else 0 # the hc big key
|
||||||
else:
|
else:
|
||||||
target_items = 34 if world.retro[player] else 63
|
target_items += 29 # small keys in chests
|
||||||
|
if world.dropshuffle[player]:
|
||||||
|
target_items += 14 # 13 dropped smalls + 1 big
|
||||||
|
if world.pottery[player] not in ['none', 'cave']:
|
||||||
|
target_items += 19 # 19 pot keys
|
||||||
d_items = target_items - all_dungeon_items_cnt
|
d_items = target_items - all_dungeon_items_cnt
|
||||||
world.pool_adjustment[player] = d_items
|
world.pool_adjustment[player] = d_items
|
||||||
smooth_door_pairs(world, player)
|
smooth_door_pairs(world, player)
|
||||||
@@ -1057,7 +1063,11 @@ def assign_cross_keys(dungeon_builders, world, player):
|
|||||||
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
|
logging.getLogger('').info(world.fish.translate("cli", "cli", "shuffling.keydoors"))
|
||||||
start = time.process_time()
|
start = time.process_time()
|
||||||
if world.retro[player]:
|
if world.retro[player]:
|
||||||
remaining = 61 if world.keydropshuffle[player] else 29
|
remaining = 29
|
||||||
|
if world.dropshuffle[player]:
|
||||||
|
remaining += 13
|
||||||
|
if world.pottery[player] not in ['none', 'cave']:
|
||||||
|
remaining += 19
|
||||||
else:
|
else:
|
||||||
remaining = len(list(x for dgn in world.dungeons if dgn.player == player for x in dgn.small_keys))
|
remaining = len(list(x for dgn in world.dungeons if dgn.player == player for x in dgn.small_keys))
|
||||||
total_keys = remaining
|
total_keys = remaining
|
||||||
@@ -1076,11 +1086,10 @@ def assign_cross_keys(dungeon_builders, world, player):
|
|||||||
total_candidates += builder.key_doors_num
|
total_candidates += builder.key_doors_num
|
||||||
start_regions_map[name] = start_regions
|
start_regions_map[name] = start_regions
|
||||||
|
|
||||||
|
|
||||||
# Step 2: Initial Key Number Assignment & Calculate Flexibility
|
# Step 2: Initial Key Number Assignment & Calculate Flexibility
|
||||||
for name, builder in dungeon_builders.items():
|
for name, builder in dungeon_builders.items():
|
||||||
calculated = int(round(builder.key_doors_num*total_keys/total_candidates))
|
calculated = int(round(builder.key_doors_num*total_keys/total_candidates))
|
||||||
max_keys = builder.location_cnt - calc_used_dungeon_items(builder)
|
max_keys = max(0, builder.location_cnt - calc_used_dungeon_items(builder))
|
||||||
cand_len = max(0, len(builder.candidates) - builder.key_drop_cnt)
|
cand_len = max(0, len(builder.candidates) - builder.key_drop_cnt)
|
||||||
limit = min(max_keys, cand_len)
|
limit = min(max_keys, cand_len)
|
||||||
suggested = min(calculated, limit)
|
suggested = min(calculated, limit)
|
||||||
@@ -1253,6 +1262,12 @@ def refine_hints(dungeon_builders):
|
|||||||
for region in builder.master_sector.regions:
|
for region in builder.master_sector.regions:
|
||||||
for location in region.locations:
|
for location in region.locations:
|
||||||
if not location.event and '- Boss' not in location.name and '- Prize' not in location.name and location.name != 'Sanctuary':
|
if not location.event and '- Boss' not in location.name and '- Prize' not in location.name and location.name != 'Sanctuary':
|
||||||
|
if location.type == LocationType.Pot and location.pot:
|
||||||
|
hint_text = ('under a block' if location.pot.flags & PotFlags.Block else 'in a pot')
|
||||||
|
location.hint_text = f'{hint_text} {dungeon_hints[name]}'
|
||||||
|
elif location.type == LocationType.Drop:
|
||||||
|
location.hint_text = f'dropped {dungeon_hints[name]}'
|
||||||
|
else:
|
||||||
location.hint_text = dungeon_hints[name]
|
location.hint_text = dungeon_hints[name]
|
||||||
|
|
||||||
|
|
||||||
@@ -1729,7 +1744,7 @@ def smooth_door_pairs(world, player):
|
|||||||
if type_b == DoorKind.SmallKey:
|
if type_b == DoorKind.SmallKey:
|
||||||
remove_pair(door, world, player)
|
remove_pair(door, world, player)
|
||||||
else:
|
else:
|
||||||
if valid_pair:
|
if valid_pair and not std_forbidden(door, world, player):
|
||||||
bd_candidates[door.entrance.parent_region.dungeon].append(door)
|
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]:
|
elif type_a in [DoorKind.Bombable, DoorKind.Dashable] or type_b in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||||
if type_a in [DoorKind.Bombable, DoorKind.Dashable]:
|
if type_a in [DoorKind.Bombable, DoorKind.Dashable]:
|
||||||
@@ -1738,7 +1753,8 @@ def smooth_door_pairs(world, player):
|
|||||||
else:
|
else:
|
||||||
room_b.change(partner.doorListPos, DoorKind.Normal)
|
room_b.change(partner.doorListPos, DoorKind.Normal)
|
||||||
remove_pair(partner, world, player)
|
remove_pair(partner, world, player)
|
||||||
elif valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey:
|
elif (valid_pair and type_a != DoorKind.SmallKey and type_b != DoorKind.SmallKey
|
||||||
|
and not std_forbidden(door, world, player)):
|
||||||
bd_candidates[door.entrance.parent_region.dungeon].append(door)
|
bd_candidates[door.entrance.parent_region.dungeon].append(door)
|
||||||
shuffle_bombable_dashable(bd_candidates, 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]
|
world.paired_doors[player] = [x for x in world.paired_doors[player] if x.pair or x.original]
|
||||||
@@ -1777,12 +1793,37 @@ def stateful_door(door, kind):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def std_forbidden(door, world, player):
|
||||||
|
return (world.mode[player] == 'standard' and door.entrance.parent_region.dungeon.name == 'Hyrule Castle' and
|
||||||
|
'Hyrule Castle Throne Room N' in [door.name, door.dest.name])
|
||||||
|
|
||||||
|
|
||||||
|
dashable_forbidden = {
|
||||||
|
'Swamp Trench 1 Key Ledge NW', 'Swamp Left Elbow WN', 'Swamp Right Elbow SE', 'Mire Hub WN', 'Mire Hub WS',
|
||||||
|
'Mire Hub Top NW', 'Mire Hub NE', 'Ice Dead End WS'
|
||||||
|
}
|
||||||
|
|
||||||
|
ohko_forbidden = {
|
||||||
|
'GT Invisible Catwalk NE', 'GT Falling Bridge WN', 'GT Falling Bridge WS', 'GT Hidden Star ES', 'GT Hookshot EN',
|
||||||
|
'GT Torch Cross WN', 'TR Torches WN', 'Mire Falling Bridge WS', 'Mire Falling Bridge W', 'Ice Hookshot Balcony SW',
|
||||||
|
'Ice Catwalk WN', 'Ice Catwalk NW', 'Ice Bomb Jump NW', 'GT Cannonball Bridge SE'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def filter_dashable_candidates(candidates, world):
|
||||||
|
forbidden_set = dashable_forbidden
|
||||||
|
if world.timer in ['ohko', 'timed-ohko']:
|
||||||
|
forbidden_set = ohko_forbidden.union(dashable_forbidden)
|
||||||
|
return [x for x in candidates if x.name not in forbidden_set and x.dest.name not in forbidden_set]
|
||||||
|
|
||||||
|
|
||||||
def shuffle_bombable_dashable(bd_candidates, world, player):
|
def shuffle_bombable_dashable(bd_candidates, world, player):
|
||||||
if world.doorShuffle[player] == 'basic':
|
if world.doorShuffle[player] == 'basic':
|
||||||
for dungeon, candidates in bd_candidates.items():
|
for dungeon, candidates in bd_candidates.items():
|
||||||
diff = bomb_dash_counts[dungeon.name][1]
|
diff = bomb_dash_counts[dungeon.name][1]
|
||||||
if diff > 0:
|
if diff > 0:
|
||||||
for chosen in random.sample(candidates, min(diff, len(candidates))):
|
dash_candidates = filter_dashable_candidates(candidates, world)
|
||||||
|
for chosen in random.sample(dash_candidates, min(diff, len(candidates))):
|
||||||
change_pair_type(chosen, DoorKind.Dashable, world, player)
|
change_pair_type(chosen, DoorKind.Dashable, world, player)
|
||||||
candidates.remove(chosen)
|
candidates.remove(chosen)
|
||||||
diff = bomb_dash_counts[dungeon.name][0]
|
diff = bomb_dash_counts[dungeon.name][0]
|
||||||
@@ -1794,7 +1835,8 @@ def shuffle_bombable_dashable(bd_candidates, world, player):
|
|||||||
remove_pair_type_if_present(excluded, world, player)
|
remove_pair_type_if_present(excluded, world, player)
|
||||||
elif world.doorShuffle[player] == 'crossed':
|
elif world.doorShuffle[player] == 'crossed':
|
||||||
all_candidates = sum(bd_candidates.values(), [])
|
all_candidates = sum(bd_candidates.values(), [])
|
||||||
for chosen in random.sample(all_candidates, min(8, len(all_candidates))):
|
dash_candidates = filter_dashable_candidates(all_candidates, world)
|
||||||
|
for chosen in random.sample(dash_candidates, min(8, len(all_candidates))):
|
||||||
change_pair_type(chosen, DoorKind.Dashable, world, player)
|
change_pair_type(chosen, DoorKind.Dashable, world, player)
|
||||||
all_candidates.remove(chosen)
|
all_candidates.remove(chosen)
|
||||||
for chosen in random.sample(all_candidates, min(12, len(all_candidates))):
|
for chosen in random.sample(all_candidates, min(12, len(all_candidates))):
|
||||||
@@ -2113,6 +2155,7 @@ logical_connections = [
|
|||||||
('Hera Startile Wide Crystal Exit', 'Hera Startile Wide'),
|
('Hera Startile Wide Crystal Exit', 'Hera Startile Wide'),
|
||||||
('Hera Big Chest Hook Path', 'Hera Big Chest Landing'),
|
('Hera Big Chest Hook Path', 'Hera Big Chest Landing'),
|
||||||
('Hera Big Chest Landing Exit', 'Hera 4F'),
|
('Hera Big Chest Landing Exit', 'Hera 4F'),
|
||||||
|
('Hera 5F Orange Path', 'Hera 5F Pot Block'),
|
||||||
|
|
||||||
('PoD Pit Room Block Path N', 'PoD Pit Room Blocked'),
|
('PoD Pit Room Block Path N', 'PoD Pit Room Blocked'),
|
||||||
('PoD Pit Room Block Path S', 'PoD Pit Room'),
|
('PoD Pit Room Block Path S', 'PoD Pit Room'),
|
||||||
@@ -2176,6 +2219,7 @@ logical_connections = [
|
|||||||
('Swamp Trench 1 Departure Approach', 'Swamp Trench 1 Approach'),
|
('Swamp Trench 1 Departure Approach', 'Swamp Trench 1 Approach'),
|
||||||
('Swamp Trench 1 Departure Key', 'Swamp Trench 1 Key Ledge'),
|
('Swamp Trench 1 Departure Key', 'Swamp Trench 1 Key Ledge'),
|
||||||
('Swamp Hub Hook Path', 'Swamp Hub North Ledge'),
|
('Swamp Hub Hook Path', 'Swamp Hub North Ledge'),
|
||||||
|
('Swamp Hub Side Hook Path', 'Swamp Hub Side Ledges'),
|
||||||
('Swamp Hub North Ledge Drop Down', 'Swamp Hub'),
|
('Swamp Hub North Ledge Drop Down', 'Swamp Hub'),
|
||||||
('Swamp Crystal Switch Outer to Inner Barrier - Blue', 'Swamp Crystal Switch Inner'),
|
('Swamp Crystal Switch Outer to Inner Barrier - Blue', 'Swamp Crystal Switch Inner'),
|
||||||
('Swamp Crystal Switch Outer to Ranged Crystal', 'Swamp Crystal Switch Outer - Ranged Crystal'),
|
('Swamp Crystal Switch Outer to Ranged Crystal', 'Swamp Crystal Switch Outer - Ranged Crystal'),
|
||||||
@@ -2207,13 +2251,17 @@ logical_connections = [
|
|||||||
('Skull Pot Circle Star Path', 'Skull Map Room'),
|
('Skull Pot Circle Star Path', 'Skull Map Room'),
|
||||||
('Skull Big Chest Hookpath', 'Skull 1 Lobby'),
|
('Skull Big Chest Hookpath', 'Skull 1 Lobby'),
|
||||||
('Skull Back Drop Star Path', 'Skull Small Hall'),
|
('Skull Back Drop Star Path', 'Skull Small Hall'),
|
||||||
|
('Skull 2 West Lobby Pits', 'Skull 2 West Lobby Ledge'),
|
||||||
|
('Skull 2 West Lobby Ledge Pits', 'Skull 2 West Lobby'),
|
||||||
('Thieves Rail Ledge Drop Down', 'Thieves BK Corner'),
|
('Thieves Rail Ledge Drop Down', 'Thieves BK Corner'),
|
||||||
('Thieves Hellway Orange Barrier', 'Thieves Hellway S Crystal'),
|
('Thieves Hellway Orange Barrier', 'Thieves Hellway S Crystal'),
|
||||||
('Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway'),
|
('Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway'),
|
||||||
('Thieves Hellway Blue Barrier', 'Thieves Hellway N Crystal'),
|
('Thieves Hellway Blue Barrier', 'Thieves Hellway N Crystal'),
|
||||||
('Thieves Hellway Crystal Blue Barrier', 'Thieves Hellway'),
|
('Thieves Hellway Crystal Blue Barrier', 'Thieves Hellway'),
|
||||||
('Thieves Attic Orange Barrier', 'Thieves Attic Hint'),
|
('Thieves Attic Orange Barrier', 'Thieves Attic Hint'),
|
||||||
|
('Thieves Attic Blue Barrier', 'Thieves Attic Switch'),
|
||||||
('Thieves Attic Hint Orange Barrier', 'Thieves Attic'),
|
('Thieves Attic Hint Orange Barrier', 'Thieves Attic'),
|
||||||
|
('Thieves Attic Switch Blue Barrier', 'Thieves Attic'),
|
||||||
('Thieves Basement Block Path', 'Thieves Blocked Entry'),
|
('Thieves Basement Block Path', 'Thieves Blocked Entry'),
|
||||||
('Thieves Blocked Entry Path', 'Thieves Basement Block'),
|
('Thieves Blocked Entry Path', 'Thieves Basement Block'),
|
||||||
('Thieves Conveyor Bridge Block Path', 'Thieves Conveyor Block'),
|
('Thieves Conveyor Bridge Block Path', 'Thieves Conveyor Block'),
|
||||||
@@ -2272,6 +2320,8 @@ logical_connections = [
|
|||||||
|
|
||||||
('TR Main Lobby Gap', 'TR Lobby Ledge'),
|
('TR Main Lobby Gap', 'TR Lobby Ledge'),
|
||||||
('TR Lobby Ledge Gap', 'TR Main Lobby'),
|
('TR Lobby Ledge Gap', 'TR Main Lobby'),
|
||||||
|
('TR Hub Path', 'TR Hub Ledges'),
|
||||||
|
('TR Hub Ledges Path', 'TR Hub'),
|
||||||
('TR Pipe Ledge Drop Down', 'TR Pipe Pit'),
|
('TR Pipe Ledge Drop Down', 'TR Pipe Pit'),
|
||||||
('TR Big Chest Gap', 'TR Big Chest Entrance'),
|
('TR Big Chest Gap', 'TR Big Chest Entrance'),
|
||||||
('TR Big Chest Entrance Gap', 'TR Big Chest'),
|
('TR Big Chest Entrance Gap', 'TR Big Chest'),
|
||||||
@@ -2300,6 +2350,8 @@ logical_connections = [
|
|||||||
('TR Crystaroller Chest to Middle Barrier - Blue', 'TR Crystaroller Middle'),
|
('TR Crystaroller Chest to Middle Barrier - Blue', 'TR Crystaroller Middle'),
|
||||||
('TR Crystaroller Middle Ranged Crystal Exit', 'TR Crystaroller Middle'),
|
('TR Crystaroller Middle Ranged Crystal Exit', 'TR Crystaroller Middle'),
|
||||||
('TR Crystaroller Bottom Ranged Crystal Exit', 'TR Crystaroller Bottom'),
|
('TR Crystaroller Bottom Ranged Crystal Exit', 'TR Crystaroller Bottom'),
|
||||||
|
('TR Dark Ride Path', 'TR Dark Ride Ledges'),
|
||||||
|
('TR Dark Ride Ledges Path', 'TR Dark Ride'),
|
||||||
('TR Crystal Maze Start to Interior Barrier - Blue', 'TR Crystal Maze Interior'),
|
('TR Crystal Maze Start to Interior Barrier - Blue', 'TR Crystal Maze Interior'),
|
||||||
('TR Crystal Maze Start to Crystal', 'TR Crystal Maze Start - Crystal'),
|
('TR Crystal Maze Start to Crystal', 'TR Crystal Maze Start - Crystal'),
|
||||||
('TR Crystal Maze Start Crystal Exit', 'TR Crystal Maze Start'),
|
('TR Crystal Maze Start Crystal Exit', 'TR Crystal Maze Start'),
|
||||||
@@ -2310,16 +2362,18 @@ logical_connections = [
|
|||||||
('TR Crystal Maze End to Interior Barrier - Blue', 'TR Crystal Maze Interior'),
|
('TR Crystal Maze End to Interior Barrier - Blue', 'TR Crystal Maze Interior'),
|
||||||
('TR Crystal Maze End to Ranged Crystal', 'TR Crystal Maze End - Ranged Crystal'),
|
('TR Crystal Maze End to Ranged Crystal', 'TR Crystal Maze End - Ranged Crystal'),
|
||||||
('TR Crystal Maze End Ranged Crystal Exit', 'TR Crystal Maze End'),
|
('TR Crystal Maze End Ranged Crystal Exit', 'TR Crystal Maze End'),
|
||||||
|
('TR Final Abyss Balcony Path', 'TR Final Abyss Ledge'),
|
||||||
|
('TR Final Abyss Ledge Path', 'TR Final Abyss Balcony'),
|
||||||
|
|
||||||
('GT Blocked Stairs Block Path', 'GT Big Chest'),
|
('GT Blocked Stairs Block Path', 'GT Big Chest'),
|
||||||
('GT Speed Torch South Path', 'GT Speed Torch'),
|
('GT Speed Torch South Path', 'GT Speed Torch'),
|
||||||
('GT Speed Torch North Path', 'GT Speed Torch Upper'),
|
('GT Speed Torch North Path', 'GT Speed Torch Upper'),
|
||||||
('GT Hookshot East-North Path', 'GT Hookshot North Platform'),
|
('GT Hookshot East-Mid Path', 'GT Hookshot Mid Platform'),
|
||||||
('GT Hookshot East-South Path', 'GT Hookshot South Platform'),
|
('GT Hookshot Mid-East Path', 'GT Hookshot East Platform'),
|
||||||
('GT Hookshot North-East Path', 'GT Hookshot East Platform'),
|
('GT Hookshot North-Mid Path', 'GT Hookshot Mid Platform'),
|
||||||
('GT Hookshot North-South Path', 'GT Hookshot South Platform'),
|
('GT Hookshot Mid-North Path', 'GT Hookshot North Platform'),
|
||||||
('GT Hookshot South-East Path', 'GT Hookshot East Platform'),
|
('GT Hookshot South-Mid Path', 'GT Hookshot Mid Platform'),
|
||||||
('GT Hookshot South-North Path', 'GT Hookshot North Platform'),
|
('GT Hookshot Mid-South Path', 'GT Hookshot South Platform'),
|
||||||
('GT Hookshot Platform Blue Barrier', 'GT Hookshot South Entry'),
|
('GT Hookshot Platform Blue Barrier', 'GT Hookshot South Entry'),
|
||||||
('GT Hookshot Platform Barrier Bypass', 'GT Hookshot South Entry'),
|
('GT Hookshot Platform Barrier Bypass', 'GT Hookshot South Entry'),
|
||||||
('GT Hookshot Entry Blue Barrier', 'GT Hookshot South Platform'),
|
('GT Hookshot Entry Blue Barrier', 'GT Hookshot South Platform'),
|
||||||
@@ -2350,8 +2404,8 @@ logical_connections = [
|
|||||||
('GT Crystal Conveyor to Corner Barrier - Blue', 'GT Crystal Conveyor Corner'),
|
('GT Crystal Conveyor to Corner Barrier - Blue', 'GT Crystal Conveyor Corner'),
|
||||||
('GT Crystal Conveyor to Ranged Crystal', 'GT Crystal Conveyor - Ranged Crystal'),
|
('GT Crystal Conveyor to Ranged Crystal', 'GT Crystal Conveyor - Ranged Crystal'),
|
||||||
('GT Crystal Conveyor Corner to Left Bypass', 'GT Crystal Conveyor Left'),
|
('GT Crystal Conveyor Corner to Left Bypass', 'GT Crystal Conveyor Left'),
|
||||||
('GT Crystal Conveyor Corner to Barrier - Blue', 'GT Crystal Conveyor Left'),
|
('GT Crystal Conveyor Corner to Barrier - Blue', 'GT Crystal Conveyor'),
|
||||||
('GT Crystal Conveyor Corner to Barrier - Orange', 'GT Crystal Conveyor'),
|
('GT Crystal Conveyor Corner to Barrier - Orange', 'GT Crystal Conveyor Left'),
|
||||||
('GT Crystal Conveyor Corner to Ranged Crystal', 'GT Crystal Conveyor Corner - Ranged Crystal'),
|
('GT Crystal Conveyor Corner to Ranged Crystal', 'GT Crystal Conveyor Corner - Ranged Crystal'),
|
||||||
('GT Crystal Conveyor Left to Corner Barrier - Orange', 'GT Crystal Conveyor Corner'),
|
('GT Crystal Conveyor Left to Corner Barrier - Orange', 'GT Crystal Conveyor Corner'),
|
||||||
('GT Crystal Conveyor Ranged Crystal Exit', 'GT Crystal Conveyor'),
|
('GT Crystal Conveyor Ranged Crystal Exit', 'GT Crystal Conveyor'),
|
||||||
@@ -3017,7 +3071,8 @@ palette_map = {
|
|||||||
'Tower of Hera': (0x6, None),
|
'Tower of Hera': (0x6, None),
|
||||||
'Thieves Town': (0x17, None), # the attic uses 0x23
|
'Thieves Town': (0x17, None), # the attic uses 0x23
|
||||||
'Turtle Rock': (0x18, 0x19, 'TR Boss SW'),
|
'Turtle Rock': (0x18, 0x19, 'TR Boss SW'),
|
||||||
'Ganons Tower': (0x28, 0x1b, 'GT Agahnim 2 SW'), # other palettes: 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrode moldorm pit f5?)
|
'Ganons Tower': (0x28, 0x1b, 'GT Agahnim 2 SW'),
|
||||||
|
# other palettes: 0x1a (other) 0x24 (Gauntlet - Lanmo) 0x25 (conveyor-torch-wizzrobe moldorm pit f5?)
|
||||||
}
|
}
|
||||||
|
|
||||||
# implications:
|
# implications:
|
||||||
|
|||||||
@@ -309,6 +309,7 @@ def create_doors(world, player):
|
|||||||
create_door(player, 'Hera 5F Star Hole', Hole),
|
create_door(player, 'Hera 5F Star Hole', Hole),
|
||||||
create_door(player, 'Hera 5F Pothole Chain', Hole),
|
create_door(player, 'Hera 5F Pothole Chain', Hole),
|
||||||
create_door(player, 'Hera 5F Normal Holes', Hole),
|
create_door(player, 'Hera 5F Normal Holes', Hole),
|
||||||
|
create_door(player, 'Hera 5F Orange Path', Lgcl),
|
||||||
create_door(player, 'Hera Fairies\' Warp', Warp),
|
create_door(player, 'Hera Fairies\' Warp', Warp),
|
||||||
create_door(player, 'Hera Boss Down Stairs', Sprl).dir(Dn, 0x07, 0, HTH).ss(S, 0x61, 0xb0).kill(),
|
create_door(player, 'Hera Boss Down Stairs', Sprl).dir(Dn, 0x07, 0, HTH).ss(S, 0x61, 0xb0).kill(),
|
||||||
create_door(player, 'Hera Boss Outer Hole', Hole),
|
create_door(player, 'Hera Boss Outer Hole', Hole),
|
||||||
@@ -500,6 +501,7 @@ def create_doors(world, player):
|
|||||||
create_door(player, 'Swamp Hub WS', Nrml).dir(We, 0x36, Bot, High).pos(3),
|
create_door(player, 'Swamp Hub WS', Nrml).dir(We, 0x36, Bot, High).pos(3),
|
||||||
create_door(player, 'Swamp Hub WN', Nrml).dir(We, 0x36, Top, High).small_key().pos(2),
|
create_door(player, 'Swamp Hub WN', Nrml).dir(We, 0x36, Top, High).small_key().pos(2),
|
||||||
create_door(player, 'Swamp Hub Hook Path', Lgcl),
|
create_door(player, 'Swamp Hub Hook Path', Lgcl),
|
||||||
|
create_door(player, 'Swamp Hub Side Hook Path', Lgcl),
|
||||||
create_door(player, 'Swamp Hub Dead Ledge EN', Nrml).dir(Ea, 0x36, Top, High).pos(0),
|
create_door(player, 'Swamp Hub Dead Ledge EN', Nrml).dir(Ea, 0x36, Top, High).pos(0),
|
||||||
create_door(player, 'Swamp Hub North Ledge N', Nrml).dir(No, 0x36, Mid, High).small_key().pos(1),
|
create_door(player, 'Swamp Hub North Ledge N', Nrml).dir(No, 0x36, Mid, High).small_key().pos(1),
|
||||||
create_door(player, 'Swamp Hub North Ledge Drop Down', Lgcl),
|
create_door(player, 'Swamp Hub North Ledge Drop Down', Lgcl),
|
||||||
@@ -612,6 +614,8 @@ def create_doors(world, player):
|
|||||||
create_door(player, 'Skull 2 West Lobby S', Nrml).dir(So, 0x56, Left, High).pos(1).portal(Z, 0x00),
|
create_door(player, 'Skull 2 West Lobby S', Nrml).dir(So, 0x56, Left, High).pos(1).portal(Z, 0x00),
|
||||||
create_door(player, 'Skull 2 West Lobby ES', Intr).dir(Ea, 0x56, Bot, High).pos(2),
|
create_door(player, 'Skull 2 West Lobby ES', Intr).dir(Ea, 0x56, Bot, High).pos(2),
|
||||||
create_door(player, 'Skull 2 West Lobby NW', Intr).dir(No, 0x56, Left, High).small_key().pos(0),
|
create_door(player, 'Skull 2 West Lobby NW', Intr).dir(No, 0x56, Left, High).small_key().pos(0),
|
||||||
|
create_door(player, 'Skull 2 West Lobby Pits', Lgcl),
|
||||||
|
create_door(player, 'Skull 2 West Lobby Ledge Pits', Lgcl),
|
||||||
create_door(player, 'Skull X Room SW', Intr).dir(So, 0x56, Left, High).small_key().pos(0),
|
create_door(player, 'Skull X Room SW', Intr).dir(So, 0x56, Left, High).small_key().pos(0),
|
||||||
create_door(player, 'Skull Back Drop Star Path', Lgcl),
|
create_door(player, 'Skull Back Drop Star Path', Lgcl),
|
||||||
create_door(player, 'Skull 3 Lobby SW', Nrml).dir(So, 0x59, Left, High).pos(1).portal(Z, 0x02),
|
create_door(player, 'Skull 3 Lobby SW', Nrml).dir(So, 0x59, Left, High).pos(1).portal(Z, 0x02),
|
||||||
@@ -684,7 +688,9 @@ def create_doors(world, player):
|
|||||||
create_door(player, 'Thieves Attic Down Stairs', Sprl).dir(Dn, 0x64, 0, HTH).ss(Z, 0x11, 0x80, True, True),
|
create_door(player, 'Thieves Attic Down Stairs', Sprl).dir(Dn, 0x64, 0, HTH).ss(Z, 0x11, 0x80, True, True),
|
||||||
create_door(player, 'Thieves Attic ES', Intr).dir(Ea, 0x64, Bot, High).pos(0),
|
create_door(player, 'Thieves Attic ES', Intr).dir(Ea, 0x64, Bot, High).pos(0),
|
||||||
create_door(player, 'Thieves Attic Orange Barrier', Lgcl),
|
create_door(player, 'Thieves Attic Orange Barrier', Lgcl),
|
||||||
|
create_door(player, 'Thieves Attic Blue Barrier', Lgcl),
|
||||||
create_door(player, 'Thieves Attic Hint Orange Barrier', Lgcl),
|
create_door(player, 'Thieves Attic Hint Orange Barrier', Lgcl),
|
||||||
|
create_door(player, 'Thieves Attic Switch Blue Barrier', Lgcl),
|
||||||
create_door(player, 'Thieves Cricket Hall Left WS', Intr).dir(We, 0x64, Bot, High).pos(0),
|
create_door(player, 'Thieves Cricket Hall Left WS', Intr).dir(We, 0x64, Bot, High).pos(0),
|
||||||
create_door(player, 'Thieves Cricket Hall Left Edge', Open).dir(Ea, 0x64, None, High).edge(0, X, 0x30),
|
create_door(player, 'Thieves Cricket Hall Left Edge', Open).dir(Ea, 0x64, None, High).edge(0, X, 0x30),
|
||||||
create_door(player, 'Thieves Cricket Hall Right Edge', Open).dir(We, 0x65, None, High).edge(0, Z, 0x30),
|
create_door(player, 'Thieves Cricket Hall Right Edge', Open).dir(We, 0x65, None, High).edge(0, Z, 0x30),
|
||||||
@@ -955,6 +961,8 @@ def create_doors(world, player):
|
|||||||
create_door(player, 'TR Hub EN', Nrml).dir(Ea, 0xc6, Top, High).pos(2),
|
create_door(player, 'TR Hub EN', Nrml).dir(Ea, 0xc6, Top, High).pos(2),
|
||||||
create_door(player, 'TR Hub NW', Nrml).dir(No, 0xc6, Left, High).small_key().pos(0),
|
create_door(player, 'TR Hub NW', Nrml).dir(No, 0xc6, Left, High).small_key().pos(0),
|
||||||
create_door(player, 'TR Hub NE', Nrml).dir(No, 0xc6, Right, High).pos(1),
|
create_door(player, 'TR Hub NE', Nrml).dir(No, 0xc6, Right, High).pos(1),
|
||||||
|
create_door(player, 'TR Hub Path', Lgcl),
|
||||||
|
create_door(player, 'TR Hub Ledges Path', Lgcl),
|
||||||
create_door(player, 'TR Torches Ledge WS', Nrml).dir(We, 0xc7, Bot, High).pos(2),
|
create_door(player, 'TR Torches Ledge WS', Nrml).dir(We, 0xc7, Bot, High).pos(2),
|
||||||
create_door(player, 'TR Torches WN', Nrml).dir(We, 0xc7, Top, High).pos(1),
|
create_door(player, 'TR Torches WN', Nrml).dir(We, 0xc7, Top, High).pos(1),
|
||||||
create_door(player, 'TR Torches NW', Nrml).dir(No, 0xc7, Left, High).trap(0x4).pos(0),
|
create_door(player, 'TR Torches NW', Nrml).dir(No, 0xc7, Left, High).trap(0x4).pos(0),
|
||||||
@@ -1030,6 +1038,8 @@ def create_doors(world, player):
|
|||||||
create_door(player, 'TR Crystaroller Down Stairs', Sprl).dir(Dn, 0x04, 0, HTH).ss(A, 0x12, 0x80, True, True).small_key().pos(0),
|
create_door(player, 'TR Crystaroller Down Stairs', Sprl).dir(Dn, 0x04, 0, HTH).ss(A, 0x12, 0x80, True, True).small_key().pos(0),
|
||||||
create_door(player, 'TR Dark Ride Up Stairs', Sprl).dir(Up, 0xb5, 0, HTH).ss(A, 0x1b, 0x6c),
|
create_door(player, 'TR Dark Ride Up Stairs', Sprl).dir(Up, 0xb5, 0, HTH).ss(A, 0x1b, 0x6c),
|
||||||
create_door(player, 'TR Dark Ride SW', Nrml).dir(So, 0xb5, Left, High).trap(0x4).pos(0).portal(Z, 0x22),
|
create_door(player, 'TR Dark Ride SW', Nrml).dir(So, 0xb5, Left, High).trap(0x4).pos(0).portal(Z, 0x22),
|
||||||
|
create_door(player, 'TR Dark Ride Path', Lgcl),
|
||||||
|
create_door(player, 'TR Dark Ride Ledges Path', Lgcl),
|
||||||
create_door(player, 'TR Dash Bridge NW', Nrml).dir(No, 0xc5, Left, High).pos(1),
|
create_door(player, 'TR Dash Bridge NW', Nrml).dir(No, 0xc5, Left, High).pos(1),
|
||||||
create_door(player, 'TR Dash Bridge SW', Nrml).dir(So, 0xc5, Left, High).pos(2).portal(Z, 0x02),
|
create_door(player, 'TR Dash Bridge SW', Nrml).dir(So, 0xc5, Left, High).pos(2).portal(Z, 0x02),
|
||||||
create_door(player, 'TR Dash Bridge WS', Nrml).dir(We, 0xc5, Bot, High).small_key().pos(0),
|
create_door(player, 'TR Dash Bridge WS', Nrml).dir(We, 0xc5, Bot, High).small_key().pos(0),
|
||||||
@@ -1048,6 +1058,8 @@ def create_doors(world, player):
|
|||||||
create_door(player, 'TR Crystal Maze End Ranged Crystal Exit', Lgcl),
|
create_door(player, 'TR Crystal Maze End Ranged Crystal Exit', Lgcl),
|
||||||
create_door(player, 'TR Crystal Maze North Stairs', StrS).dir(No, 0xc4, Mid, High),
|
create_door(player, 'TR Crystal Maze North Stairs', StrS).dir(No, 0xc4, Mid, High),
|
||||||
create_door(player, 'TR Final Abyss South Stairs', StrS).dir(So, 0xb4, Mid, High),
|
create_door(player, 'TR Final Abyss South Stairs', StrS).dir(So, 0xb4, Mid, High),
|
||||||
|
create_door(player, 'TR Final Abyss Balcony Path', Lgcl),
|
||||||
|
create_door(player, 'TR Final Abyss Ledge Path', Lgcl),
|
||||||
create_door(player, 'TR Final Abyss NW', Nrml).dir(No, 0xb4, Left, High).big_key().pos(0),
|
create_door(player, 'TR Final Abyss NW', Nrml).dir(No, 0xb4, Left, High).big_key().pos(0),
|
||||||
create_door(player, 'TR Boss SW', Nrml).dir(So, 0xa4, Left, High).no_exit().trap(0x4).pos(0), # .portal(Z, 0x00), -enemizer doesn't work
|
create_door(player, 'TR Boss SW', Nrml).dir(So, 0xa4, Left, High).no_exit().trap(0x4).pos(0), # .portal(Z, 0x00), -enemizer doesn't work
|
||||||
|
|
||||||
@@ -1098,12 +1110,12 @@ def create_doors(world, player):
|
|||||||
create_door(player, 'GT Conveyor Cross EN', Nrml).dir(Ea, 0x8b, Top, High).pos(2),
|
create_door(player, 'GT Conveyor Cross EN', Nrml).dir(Ea, 0x8b, Top, High).pos(2),
|
||||||
create_door(player, 'GT Conveyor Cross WN', Intr).dir(We, 0x8b, Top, High).pos(0),
|
create_door(player, 'GT Conveyor Cross WN', Intr).dir(We, 0x8b, Top, High).pos(0),
|
||||||
create_door(player, 'GT Hookshot EN', Intr).dir(Ea, 0x8b, Top, High).pos(0),
|
create_door(player, 'GT Hookshot EN', Intr).dir(Ea, 0x8b, Top, High).pos(0),
|
||||||
create_door(player, 'GT Hookshot East-North Path', Lgcl),
|
create_door(player, 'GT Hookshot East-Mid Path', Lgcl),
|
||||||
create_door(player, 'GT Hookshot East-South Path', Lgcl),
|
create_door(player, 'GT Hookshot Mid-East Path', Lgcl),
|
||||||
create_door(player, 'GT Hookshot North-East Path', Lgcl),
|
create_door(player, 'GT Hookshot North-Mid Path', Lgcl),
|
||||||
create_door(player, 'GT Hookshot North-South Path', Lgcl),
|
create_door(player, 'GT Hookshot Mid-North Path', Lgcl),
|
||||||
create_door(player, 'GT Hookshot South-East Path', Lgcl),
|
create_door(player, 'GT Hookshot South-Mid Path', Lgcl),
|
||||||
create_door(player, 'GT Hookshot South-North Path', Lgcl),
|
create_door(player, 'GT Hookshot Mid-South Path', Lgcl),
|
||||||
create_door(player, 'GT Hookshot Platform Blue Barrier', Lgcl),
|
create_door(player, 'GT Hookshot Platform Blue Barrier', Lgcl),
|
||||||
create_door(player, 'GT Hookshot Entry Blue Barrier', Lgcl),
|
create_door(player, 'GT Hookshot Entry Blue Barrier', Lgcl),
|
||||||
create_door(player, 'GT Hookshot Platform Barrier Bypass', Lgcl),
|
create_door(player, 'GT Hookshot Platform Barrier Bypass', Lgcl),
|
||||||
@@ -1291,6 +1303,7 @@ def create_doors(world, player):
|
|||||||
world.get_door('Hera Beetles Holes Front', player).c_switch()
|
world.get_door('Hera Beetles Holes Front', player).c_switch()
|
||||||
world.get_door('Hera Beetles Holes Landing', player).c_switch()
|
world.get_door('Hera Beetles Holes Landing', player).c_switch()
|
||||||
world.get_door('Hera Startile Wide Crystal Exit', player).c_switch()
|
world.get_door('Hera Startile Wide Crystal Exit', player).c_switch()
|
||||||
|
world.get_door('Hera 5F Orange Path', player).barrier(CrystalBarrier.Orange)
|
||||||
|
|
||||||
world.get_door('PoD Arena North to Landing Barrier - Orange', player).barrier(CrystalBarrier.Orange)
|
world.get_door('PoD Arena North to Landing Barrier - Orange', player).barrier(CrystalBarrier.Orange)
|
||||||
world.get_door('PoD Arena Main to Landing Barrier - Blue', player).barrier(CrystalBarrier.Blue)
|
world.get_door('PoD Arena Main to Landing Barrier - Blue', player).barrier(CrystalBarrier.Blue)
|
||||||
@@ -1345,7 +1358,9 @@ def create_doors(world, player):
|
|||||||
world.get_door('Thieves Hellway Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
world.get_door('Thieves Hellway Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||||
world.get_door('Thieves Hellway Crystal Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
world.get_door('Thieves Hellway Crystal Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||||
world.get_door('Thieves Attic Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
world.get_door('Thieves Attic Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||||
|
world.get_door('Thieves Attic Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('Thieves Attic Hint Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
world.get_door('Thieves Attic Hint Orange Barrier', player).barrier(CrystalBarrier.Orange)
|
||||||
|
world.get_door('Thieves Attic Switch Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
|
|
||||||
world.get_door('Ice Bomb Drop SE', player).c_switch()
|
world.get_door('Ice Bomb Drop SE', player).c_switch()
|
||||||
world.get_door('Ice Conveyor Crystal Exit', player).c_switch()
|
world.get_door('Ice Conveyor Crystal Exit', player).c_switch()
|
||||||
@@ -1421,8 +1436,7 @@ def create_doors(world, player):
|
|||||||
world.get_door('GT Crystal Conveyor Ranged Crystal Exit', player).c_switch()
|
world.get_door('GT Crystal Conveyor Ranged Crystal Exit', player).c_switch()
|
||||||
world.get_door('GT Crystal Conveyor Corner Ranged Crystal Exit', player).c_switch()
|
world.get_door('GT Crystal Conveyor Corner Ranged Crystal Exit', player).c_switch()
|
||||||
world.get_door('GT Crystal Conveyor Corner to Left Bypass', player).barrier(CrystalBarrier.Blue)
|
world.get_door('GT Crystal Conveyor Corner to Left Bypass', player).barrier(CrystalBarrier.Blue)
|
||||||
world.get_door('GT Hookshot South-North Path', player).c_switch()
|
world.get_door('GT Hookshot South-Mid Path', player).c_switch()
|
||||||
world.get_door('GT Hookshot South-East Path', player).c_switch()
|
|
||||||
world.get_door('GT Hookshot ES', player).c_switch()
|
world.get_door('GT Hookshot ES', player).c_switch()
|
||||||
world.get_door('GT Hookshot Platform Barrier Bypass', player).barrier(CrystalBarrier.Orange)
|
world.get_door('GT Hookshot Platform Barrier Bypass', player).barrier(CrystalBarrier.Orange)
|
||||||
world.get_door('GT Hookshot Platform Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
world.get_door('GT Hookshot Platform Blue Barrier', player).barrier(CrystalBarrier.Blue)
|
||||||
|
|||||||
+74
-12
@@ -1326,7 +1326,17 @@ def create_dungeon_builders(all_sectors, connections_tuple, world, player,
|
|||||||
polarized_sectors[sector] = None
|
polarized_sectors[sector] = None
|
||||||
if bow_sectors:
|
if bow_sectors:
|
||||||
assign_bow_sectors(dungeon_map, bow_sectors, global_pole)
|
assign_bow_sectors(dungeon_map, bow_sectors, global_pole)
|
||||||
assign_location_sectors(dungeon_map, free_location_sectors, global_pole, world, player)
|
leftover = assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_pole, world, player)
|
||||||
|
free_location_sectors = scatter_extra_location_sectors(dungeon_map, leftover, global_pole)
|
||||||
|
for sector in free_location_sectors:
|
||||||
|
if sector.c_switch:
|
||||||
|
crystal_switches[sector] = None
|
||||||
|
elif sector.blue_barrier:
|
||||||
|
crystal_barriers[sector] = None
|
||||||
|
elif sector.polarity().is_neutral():
|
||||||
|
neutral_sectors[sector] = None
|
||||||
|
else:
|
||||||
|
polarized_sectors[sector] = None
|
||||||
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
|
leftover = assign_crystal_switch_sectors(dungeon_map, crystal_switches, crystal_barriers, global_pole)
|
||||||
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
|
ensure_crystal_switches_reachable(dungeon_map, leftover, polarized_sectors, crystal_barriers, global_pole)
|
||||||
for sector in leftover:
|
for sector in leftover:
|
||||||
@@ -1558,21 +1568,45 @@ def assign_bow_sectors(dungeon_map, bow_sectors, global_pole):
|
|||||||
assign_sector(sector_list[i], builder, bow_sectors, global_pole)
|
assign_sector(sector_list[i], builder, bow_sectors, global_pole)
|
||||||
|
|
||||||
|
|
||||||
def assign_location_sectors(dungeon_map, free_location_sectors, global_pole, world, player):
|
def scatter_extra_location_sectors(dungeon_map, free_location_sectors, global_pole):
|
||||||
|
population = [n for n in dungeon_map.keys()]
|
||||||
|
k = round(len(free_location_sectors) * .50)
|
||||||
valid = False
|
valid = False
|
||||||
choices = None
|
choices = None
|
||||||
|
candidates = []
|
||||||
|
sector_list = list(free_location_sectors)
|
||||||
|
while not valid:
|
||||||
|
candidates = random.sample(sector_list, k=k)
|
||||||
|
choices = random.choices(population, k=len(candidates))
|
||||||
|
sector_dict = defaultdict(list)
|
||||||
|
for i, choice in enumerate(choices):
|
||||||
|
builder = dungeon_map[choice]
|
||||||
|
sector_dict[builder].append(candidates[i])
|
||||||
|
valid = global_pole.is_valid_multi_choice_2(dungeon_map, dungeon_map.values(), sector_dict)
|
||||||
|
for i, choice in enumerate(choices):
|
||||||
|
builder = dungeon_map[choice]
|
||||||
|
assign_sector(candidates[i], builder, free_location_sectors, global_pole)
|
||||||
|
return free_location_sectors
|
||||||
|
|
||||||
|
|
||||||
|
def assign_location_sectors_minimal(dungeon_map, free_location_sectors, global_pole, world, player):
|
||||||
|
valid = False
|
||||||
|
choices = defaultdict(list)
|
||||||
sector_list = list(free_location_sectors)
|
sector_list = list(free_location_sectors)
|
||||||
random.shuffle(sector_list)
|
random.shuffle(sector_list)
|
||||||
orig_location_set = build_orig_location_set(dungeon_map)
|
orig_location_set = build_orig_location_set(dungeon_map)
|
||||||
num_dungeon_items = requested_dungeon_items(world, player)
|
num_dungeon_items = requested_dungeon_items(world, player)
|
||||||
|
d_idx = {builder.name: i for i, builder in enumerate(dungeon_map.values())}
|
||||||
|
next_sector = sector_list.pop()
|
||||||
while not valid:
|
while not valid:
|
||||||
choices, d_idx, totals = weighted_random_locations(dungeon_map, sector_list)
|
choice, totals, location_set = weighted_random_location(dungeon_map, choices, orig_location_set, world, player)
|
||||||
location_set = {x: set(y) for x, y in orig_location_set.items()}
|
if not choice:
|
||||||
for i, sector in enumerate(sector_list):
|
break
|
||||||
d_name = choices[i].name
|
choices[choice].append(next_sector)
|
||||||
choice = d_idx[d_name]
|
if global_pole.is_valid_multi_choice_2(dungeon_map, dungeon_map.values(), choices):
|
||||||
totals[choice] += sector.chest_locations
|
idx = d_idx[choice.name]
|
||||||
location_set[d_name].update(sector.chest_location_set)
|
totals[idx] += next_sector.chest_locations
|
||||||
|
location_set[choice.name].update(next_sector.chest_location_set)
|
||||||
valid = True
|
valid = True
|
||||||
for d_name, idx in d_idx.items():
|
for d_name, idx in d_idx.items():
|
||||||
free_items = count_reserved_locations(world, player, location_set[d_name])
|
free_items = count_reserved_locations(world, player, location_set[d_name])
|
||||||
@@ -1580,11 +1614,39 @@ def assign_location_sectors(dungeon_map, free_location_sectors, global_pole, wor
|
|||||||
if totals[idx] < target:
|
if totals[idx] < target:
|
||||||
valid = False
|
valid = False
|
||||||
break
|
break
|
||||||
for i, choice in enumerate(choices):
|
if not valid:
|
||||||
builder = dungeon_map[choice.name]
|
if len(sector_list) == 0:
|
||||||
assign_sector(sector_list[i], builder, free_location_sectors, global_pole)
|
choices = defaultdict(list)
|
||||||
|
sector_list = list(free_location_sectors)
|
||||||
|
else:
|
||||||
|
next_sector = sector_list.pop()
|
||||||
|
else:
|
||||||
|
choices[choice].remove(next_sector)
|
||||||
|
for builder, choice_list in choices.items():
|
||||||
|
for choice in choice_list:
|
||||||
|
assign_sector(choice, builder, free_location_sectors, global_pole)
|
||||||
|
return free_location_sectors
|
||||||
|
|
||||||
|
|
||||||
|
def weighted_random_location(dungeon_map, choices, orig_location_set, world, player):
|
||||||
|
population = []
|
||||||
|
totals = []
|
||||||
|
location_set = {x: set(y) for x, y in orig_location_set.items()}
|
||||||
|
num_dungeon_items = requested_dungeon_items(world, player)
|
||||||
|
for i, dungeon_builder in enumerate(dungeon_map.values()):
|
||||||
|
ttl = dungeon_builder.location_cnt + sum(sector.chest_locations for sector in choices[dungeon_builder])
|
||||||
|
totals.append(ttl)
|
||||||
|
builder_set = location_set[dungeon_builder.name]
|
||||||
|
builder_set.update(set().union(*(s.chest_location_set for s in choices[dungeon_builder])))
|
||||||
|
free_items = count_reserved_locations(world, player, builder_set)
|
||||||
|
target = max(free_items, 2) + num_dungeon_items
|
||||||
|
if ttl < target:
|
||||||
|
population.append(dungeon_builder)
|
||||||
|
choice = random.choice(population) if len(population) > 0 else None
|
||||||
|
return choice, totals, location_set
|
||||||
|
|
||||||
|
|
||||||
|
# deprecated
|
||||||
def weighted_random_locations(dungeon_map, free_location_sectors):
|
def weighted_random_locations(dungeon_map, free_location_sectors):
|
||||||
population = []
|
population = []
|
||||||
ttl_assigned = 0
|
ttl_assigned = 0
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
import argparse
|
if __name__ == '__main__':
|
||||||
import copy
|
from source.meta.check_requirements import check_requirements
|
||||||
|
check_requirements(console=True)
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
import textwrap
|
|
||||||
import shlex
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from source.classes.BabelFish import BabelFish
|
from source.classes.BabelFish import BabelFish
|
||||||
|
|||||||
+35
-33
@@ -84,7 +84,7 @@ hera_regions = [
|
|||||||
'Hera Back - Ranged Crystal', 'Hera Basement Cage', 'Hera Basement Cage - Crystal', 'Hera Tile Room',
|
'Hera Back - Ranged Crystal', 'Hera Basement Cage', 'Hera Basement Cage - Crystal', 'Hera Tile Room',
|
||||||
'Hera Tridorm', 'Hera Tridorm - Crystal', 'Hera Torches', 'Hera Beetles', 'Hera Startile Corner',
|
'Hera Tridorm', 'Hera Tridorm - Crystal', 'Hera Torches', 'Hera Beetles', 'Hera Startile Corner',
|
||||||
'Hera Startile Wide', 'Hera Startile Wide - Crystal', 'Hera 4F', 'Hera Big Chest Landing', 'Hera 5F',
|
'Hera Startile Wide', 'Hera Startile Wide - Crystal', 'Hera 4F', 'Hera Big Chest Landing', 'Hera 5F',
|
||||||
'Hera Fairies', 'Hera Boss', 'Hera Portal'
|
'Hera 5F Pot Block', 'Hera Fairies', 'Hera Boss', 'Hera Portal'
|
||||||
]
|
]
|
||||||
|
|
||||||
tower_regions = [
|
tower_regions = [
|
||||||
@@ -110,24 +110,24 @@ pod_regions = [
|
|||||||
swamp_regions = [
|
swamp_regions = [
|
||||||
'Swamp Lobby', 'Swamp Entrance', 'Swamp Pot Row', 'Swamp Map Ledge', 'Swamp Trench 1 Approach',
|
'Swamp Lobby', 'Swamp Entrance', 'Swamp Pot Row', 'Swamp Map Ledge', 'Swamp Trench 1 Approach',
|
||||||
'Swamp Trench 1 Nexus', 'Swamp Trench 1 Alcove', 'Swamp Trench 1 Key Ledge', 'Swamp Trench 1 Departure',
|
'Swamp Trench 1 Nexus', 'Swamp Trench 1 Alcove', 'Swamp Trench 1 Key Ledge', 'Swamp Trench 1 Departure',
|
||||||
'Swamp Hammer Switch', 'Swamp Hub', 'Swamp Hub Dead Ledge', 'Swamp Hub North Ledge', 'Swamp Donut Top',
|
'Swamp Hammer Switch', 'Swamp Hub', 'Swamp Hub Side Ledges', 'Swamp Hub Dead Ledge', 'Swamp Hub North Ledge',
|
||||||
'Swamp Donut Bottom', 'Swamp Compass Donut', 'Swamp Crystal Switch Outer', 'Swamp Crystal Switch Outer - Ranged Crystal',
|
'Swamp Donut Top', 'Swamp Donut Bottom', 'Swamp Compass Donut', 'Swamp Crystal Switch Outer',
|
||||||
'Swamp Crystal Switch Inner', 'Swamp Crystal Switch Inner - Crystal', 'Swamp Shortcut', 'Swamp Trench 2 Pots',
|
'Swamp Crystal Switch Outer - Ranged Crystal', 'Swamp Crystal Switch Inner', 'Swamp Crystal Switch Inner - Crystal',
|
||||||
'Swamp Trench 2 Blocks', 'Swamp Trench 2 Alcove', 'Swamp Trench 2 Departure', 'Swamp Big Key Ledge',
|
'Swamp Shortcut', 'Swamp Trench 2 Pots', 'Swamp Trench 2 Blocks', 'Swamp Trench 2 Alcove',
|
||||||
'Swamp West Shallows', 'Swamp West Block Path', 'Swamp West Ledge', 'Swamp Barrier Ledge', 'Swamp Barrier',
|
'Swamp Trench 2 Departure', 'Swamp Big Key Ledge', 'Swamp West Shallows', 'Swamp West Block Path',
|
||||||
'Swamp Attic', 'Swamp Push Statue', 'Swamp Shooters', 'Swamp Left Elbow', 'Swamp Right Elbow', 'Swamp Drain Left',
|
'Swamp West Ledge', 'Swamp Barrier Ledge', 'Swamp Barrier', 'Swamp Attic', 'Swamp Push Statue', 'Swamp Shooters',
|
||||||
'Swamp Drain Right', 'Swamp Flooded Room', 'Swamp Flooded Spot', 'Swamp Basement Shallows', 'Swamp Waterfall Room',
|
'Swamp Left Elbow', 'Swamp Right Elbow', 'Swamp Drain Left', 'Swamp Drain Right', 'Swamp Flooded Room',
|
||||||
'Swamp Refill', 'Swamp Behind Waterfall', 'Swamp C', 'Swamp Waterway', 'Swamp I', 'Swamp T', 'Swamp Boss',
|
'Swamp Flooded Spot', 'Swamp Basement Shallows', 'Swamp Waterfall Room', 'Swamp Refill', 'Swamp Behind Waterfall',
|
||||||
'Swamp Portal'
|
'Swamp C', 'Swamp Waterway', 'Swamp I', 'Swamp T', 'Swamp Boss', 'Swamp Portal'
|
||||||
]
|
]
|
||||||
|
|
||||||
skull_regions = [
|
skull_regions = [
|
||||||
'Skull 1 Lobby', 'Skull Map Room', 'Skull Pot Circle', 'Skull Pull Switch', 'Skull Big Chest', 'Skull Pinball',
|
'Skull 1 Lobby', 'Skull Map Room', 'Skull Pot Circle', 'Skull Pull Switch', 'Skull Big Chest', 'Skull Pinball',
|
||||||
'Skull Pot Prison', 'Skull Compass Room', 'Skull Left Drop', 'Skull 2 East Lobby', 'Skull Big Key',
|
'Skull Pot Prison', 'Skull Compass Room', 'Skull Left Drop', 'Skull 2 East Lobby', 'Skull Big Key',
|
||||||
'Skull Lone Pot', 'Skull Small Hall', 'Skull Back Drop', 'Skull 2 West Lobby', 'Skull X Room', 'Skull 3 Lobby',
|
'Skull Lone Pot', 'Skull Small Hall', 'Skull Back Drop', 'Skull 2 West Lobby', 'Skull 2 West Lobby Ledge',
|
||||||
'Skull East Bridge', 'Skull West Bridge Nook', 'Skull Star Pits', 'Skull Torch Room', 'Skull Vines',
|
'Skull X Room', 'Skull 3 Lobby', 'Skull East Bridge', 'Skull West Bridge Nook', 'Skull Star Pits',
|
||||||
'Skull Spike Corner', 'Skull Final Drop', 'Skull Boss', 'Skull 1 Portal', 'Skull 2 East Portal',
|
'Skull Torch Room', 'Skull Vines', 'Skull Spike Corner', 'Skull Final Drop', 'Skull Boss',
|
||||||
'Skull 2 West Portal', 'Skull 3 Portal'
|
'Skull 1 Portal', 'Skull 2 East Portal', 'Skull 2 West Portal', 'Skull 3 Portal'
|
||||||
]
|
]
|
||||||
|
|
||||||
thieves_regions = [
|
thieves_regions = [
|
||||||
@@ -135,10 +135,10 @@ thieves_regions = [
|
|||||||
'Thieves Big Chest Nook', 'Thieves Hallway', 'Thieves Boss', 'Thieves Pot Alcove Mid', 'Thieves Pot Alcove Bottom',
|
'Thieves Big Chest Nook', 'Thieves Hallway', 'Thieves Boss', 'Thieves Pot Alcove Mid', 'Thieves Pot Alcove Bottom',
|
||||||
'Thieves Pot Alcove Top', 'Thieves Conveyor Maze', 'Thieves Spike Track', 'Thieves Hellway',
|
'Thieves Pot Alcove Top', 'Thieves Conveyor Maze', 'Thieves Spike Track', 'Thieves Hellway',
|
||||||
'Thieves Hellway N Crystal', 'Thieves Hellway S Crystal', 'Thieves Triple Bypass', 'Thieves Spike Switch',
|
'Thieves Hellway N Crystal', 'Thieves Hellway S Crystal', 'Thieves Triple Bypass', 'Thieves Spike Switch',
|
||||||
'Thieves Attic', 'Thieves Attic Hint', 'Thieves Cricket Hall Left', 'Thieves Cricket Hall Right',
|
'Thieves Attic', 'Thieves Attic Hint', 'Thieves Attic Switch', 'Thieves Cricket Hall Left',
|
||||||
'Thieves Attic Window', 'Thieves Basement Block', 'Thieves Blocked Entry', 'Thieves Lonely Zazak',
|
'Thieves Cricket Hall Right', 'Thieves Attic Window', 'Thieves Basement Block', 'Thieves Blocked Entry',
|
||||||
"Thieves Blind's Cell", "Thieves Blind's Cell Interior", 'Thieves Conveyor Bridge', 'Thieves Conveyor Block',
|
'Thieves Lonely Zazak', "Thieves Blind's Cell", "Thieves Blind's Cell Interior", 'Thieves Conveyor Bridge',
|
||||||
'Thieves Big Chest Room', 'Thieves Trap', 'Thieves Town Portal'
|
'Thieves Conveyor Block', 'Thieves Big Chest Room', 'Thieves Trap', 'Thieves Town Portal'
|
||||||
]
|
]
|
||||||
|
|
||||||
ice_regions = [
|
ice_regions = [
|
||||||
@@ -168,29 +168,31 @@ mire_regions = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
tr_regions = [
|
tr_regions = [
|
||||||
'TR Main Lobby', 'TR Lobby Ledge', 'TR Compass Room', 'TR Hub', 'TR Torches Ledge', 'TR Torches', 'TR Roller Room',
|
'TR Main Lobby', 'TR Lobby Ledge', 'TR Compass Room', 'TR Hub', 'TR Hub Ledges', 'TR Torches Ledge', 'TR Torches',
|
||||||
'TR Tile Room', 'TR Refill', 'TR Pokey 1', 'TR Chain Chomps Top', 'TR Chain Chomps Top - Crystal',
|
'TR Roller Room', 'TR Tile Room', 'TR Refill', 'TR Pokey 1', 'TR Chain Chomps Top', 'TR Chain Chomps Top - Crystal',
|
||||||
'TR Chain Chomps Bottom', 'TR Chain Chomps Bottom - Ranged Crystal', 'TR Pipe Pit', 'TR Pipe Ledge', 'TR Lava Dual Pipes',
|
'TR Chain Chomps Bottom', 'TR Chain Chomps Bottom - Ranged Crystal', 'TR Pipe Pit', 'TR Pipe Ledge',
|
||||||
'TR Lava Island', 'TR Lava Escape', 'TR Pokey 2 Top', 'TR Pokey 2 Top - Crystal', 'TR Pokey 2 Bottom', 'TR Pokey 2 Bottom - Ranged Crystal',
|
'TR Lava Dual Pipes', 'TR Lava Island', 'TR Lava Escape', 'TR Pokey 2 Top', 'TR Pokey 2 Top - Crystal',
|
||||||
'TR Twin Pokeys', 'TR Hallway', 'TR Dodgers', 'TR Big View','TR Big Chest', 'TR Big Chest Entrance',
|
'TR Pokey 2 Bottom', 'TR Pokey 2 Bottom - Ranged Crystal', 'TR Twin Pokeys', 'TR Hallway', 'TR Dodgers',
|
||||||
'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull', 'TR Rupees', 'TR Crystaroller Bottom',
|
'TR Big View', 'TR Big Chest', 'TR Big Chest Entrance', 'TR Lazy Eyes', 'TR Dash Room', 'TR Tongue Pull',
|
||||||
'TR Crystaroller Middle', 'TR Crystaroller Top', 'TR Crystaroller Top - Crystal', 'TR Crystaroller Chest',
|
'TR Rupees', 'TR Crystaroller Bottom', 'TR Crystaroller Middle', 'TR Crystaroller Top',
|
||||||
'TR Crystaroller Middle - Ranged Crystal', 'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride', 'TR Dash Bridge', 'TR Eye Bridge',
|
'TR Crystaroller Top - Crystal', 'TR Crystaroller Chest', 'TR Crystaroller Middle - Ranged Crystal',
|
||||||
|
'TR Crystaroller Bottom - Ranged Crystal', 'TR Dark Ride', 'TR Dark Ride Ledges', 'TR Dash Bridge', 'TR Eye Bridge',
|
||||||
'TR Crystal Maze Start', 'TR Crystal Maze Start - Crystal', 'TR Crystal Maze Interior', 'TR Crystal Maze End',
|
'TR Crystal Maze Start', 'TR Crystal Maze Start - Crystal', 'TR Crystal Maze Interior', 'TR Crystal Maze End',
|
||||||
'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss', 'TR Boss', 'Turtle Rock Main Portal',
|
'TR Crystal Maze End - Ranged Crystal', 'TR Final Abyss Balcony', 'TR Final Abyss Ledge', 'TR Boss',
|
||||||
'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal', 'Turtle Rock Eye Bridge Portal'
|
'Turtle Rock Main Portal', 'Turtle Rock Lazy Eyes Portal', 'Turtle Rock Chest Portal',
|
||||||
|
'Turtle Rock Eye Bridge Portal'
|
||||||
]
|
]
|
||||||
|
|
||||||
gt_regions = [
|
gt_regions = [
|
||||||
'GT Lobby', 'GT Bob\'s Torch', 'GT Hope Room', 'GT Big Chest', 'GT Blocked Stairs', 'GT Bob\'s Room',
|
'GT Lobby', 'GT Bob\'s Torch', 'GT Hope Room', 'GT Big Chest', 'GT Blocked Stairs', 'GT Bob\'s Room',
|
||||||
'GT Tile Room', 'GT Speed Torch', 'GT Speed Torch Upper', 'GT Pots n Blocks', 'GT Crystal Conveyor',
|
'GT Tile Room', 'GT Speed Torch', 'GT Speed Torch Upper', 'GT Pots n Blocks', 'GT Crystal Conveyor',
|
||||||
'GT Crystal Conveyor Corner', 'GT Crystal Conveyor Left', 'GT Crystal Conveyor - Ranged Crystal',
|
'GT Crystal Conveyor Corner', 'GT Crystal Conveyor Left', 'GT Crystal Conveyor - Ranged Crystal',
|
||||||
'GT Crystal Conveyor Corner - Ranged Crystal',
|
'GT Crystal Conveyor Corner - Ranged Crystal', 'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk',
|
||||||
'GT Compass Room', 'GT Invisible Bridges', 'GT Invisible Catwalk', 'GT Conveyor Cross', 'GT Hookshot East Platform',
|
'GT Conveyor Cross', 'GT Hookshot East Platform', 'GT Hookshot Mid Platform', 'GT Hookshot North Platform',
|
||||||
'GT Hookshot North Platform', 'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room',
|
'GT Hookshot South Platform', 'GT Hookshot South Entry', 'GT Hookshot South Entry - Ranged Crystal', 'GT Map Room',
|
||||||
'GT Double Switch Entry', 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners',
|
'GT Double Switch Entry', 'GT Double Switch Pot Corners - Ranged Switches', 'GT Double Switch Pot Corners',
|
||||||
'GT Double Switch Left', 'GT Double Switch Left - Crystal',
|
'GT Double Switch Left', 'GT Double Switch Left - Crystal', 'GT Double Switch Entry - Ranged Switches',
|
||||||
'GT Double Switch Entry - Ranged Switches', 'GT Double Switch Exit', 'GT Spike Crystal Left',
|
'GT Double Switch Exit', 'GT Spike Crystal Left',
|
||||||
'GT Spike Crystal Right', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section',
|
'GT Spike Crystal Right', 'GT Warp Maze - Left Section', 'GT Warp Maze - Mid Section',
|
||||||
'GT Warp Maze - Right Section', 'GT Warp Maze - Pit Section', 'GT Warp Maze - Pit Exit Warp Spot',
|
'GT Warp Maze - Right Section', 'GT Warp Maze - Pit Section', 'GT Warp Maze - Pit Exit Warp Spot',
|
||||||
'GT Warp Maze Exit Section', 'GT Firesnake Room', 'GT Firesnake Room Ledge', 'GT Warp Maze - Rail Choice',
|
'GT Warp Maze Exit Section', 'GT Firesnake Room', 'GT Firesnake Room Ledge', 'GT Warp Maze - Rail Choice',
|
||||||
|
|||||||
+10
-6
@@ -1448,9 +1448,8 @@ def junk_fill_inaccessible(world, player):
|
|||||||
|
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world)
|
base_world = copy_world(world, True)
|
||||||
base_world.override_bomb_check = True
|
base_world.override_bomb_check = True
|
||||||
world.key_logic = {}
|
|
||||||
|
|
||||||
# remove regions that have a dungeon entrance
|
# remove regions that have a dungeon entrance
|
||||||
accessible_regions = list()
|
accessible_regions = list()
|
||||||
@@ -1617,9 +1616,8 @@ def build_accessible_entrance_list(world, start_region, player, assumed_inventor
|
|||||||
|
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world)
|
base_world = copy_world(world, True)
|
||||||
base_world.override_bomb_check = True
|
base_world.override_bomb_check = True
|
||||||
world.key_logic = {}
|
|
||||||
|
|
||||||
connect_simple(base_world, 'Links House S&Q', start_region, player)
|
connect_simple(base_world, 'Links House S&Q', start_region, player)
|
||||||
blank_state = CollectionState(base_world)
|
blank_state = CollectionState(base_world)
|
||||||
@@ -1727,9 +1725,8 @@ def can_reach(world, entrance_name, region_name, player):
|
|||||||
|
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world)
|
base_world = copy_world(world, True)
|
||||||
base_world.override_bomb_check = True
|
base_world.override_bomb_check = True
|
||||||
world.key_logic = {}
|
|
||||||
|
|
||||||
entrance = world.get_entrance(entrance_name, player)
|
entrance = world.get_entrance(entrance_name, player)
|
||||||
connect_simple(base_world, 'Links House S&Q', entrance.parent_region.name, player)
|
connect_simple(base_world, 'Links House S&Q', entrance.parent_region.name, player)
|
||||||
@@ -2059,6 +2056,8 @@ mandatory_connections = [('Old Man S&Q', 'Old Man House'),
|
|||||||
('Lost Woods Hideout (top to bottom)', 'Lost Woods Hideout (bottom)'),
|
('Lost Woods Hideout (top to bottom)', 'Lost Woods Hideout (bottom)'),
|
||||||
('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'),
|
('Lumberjack Tree (top to bottom)', 'Lumberjack Tree (bottom)'),
|
||||||
('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'),
|
('Kakariko Well (top to bottom)', 'Kakariko Well (bottom)'),
|
||||||
|
('Kakariko Well (top to back)', 'Kakariko Well (back)'),
|
||||||
|
('Blinds Hideout N', 'Blinds Hideout (Top)'),
|
||||||
('Bat Cave Door', 'Bat Cave (left)'),
|
('Bat Cave Door', 'Bat Cave (left)'),
|
||||||
('Sewer Drop', 'Sewers Rat Path'),
|
('Sewer Drop', 'Sewers Rat Path'),
|
||||||
('Old Man Cave Dropdown', 'Old Man Cave'),
|
('Old Man Cave Dropdown', 'Old Man Cave'),
|
||||||
@@ -2066,10 +2065,13 @@ mandatory_connections = [('Old Man S&Q', 'Old Man House'),
|
|||||||
('Old Man House Back to Front', 'Old Man House'),
|
('Old Man House Back to Front', 'Old Man House'),
|
||||||
('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'),
|
('Spectacle Rock Cave Drop', 'Spectacle Rock Cave (Bottom)'),
|
||||||
('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'),
|
('Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave (Bottom)'),
|
||||||
|
('Death Mountain Return Cave E', 'Death Mountain Return Cave (right)'),
|
||||||
|
('Death Mountain Return Cave W', 'Death Mountain Return Cave (left)'),
|
||||||
('Spiral Cave (top to bottom)', 'Spiral Cave (Bottom)'),
|
('Spiral Cave (top to bottom)', 'Spiral Cave (Bottom)'),
|
||||||
('Light World Death Mountain Shop', 'Light World Death Mountain Shop'),
|
('Light World Death Mountain Shop', 'Light World Death Mountain Shop'),
|
||||||
('Paradox Cave Push Block Reverse', 'Paradox Cave Chest Area'),
|
('Paradox Cave Push Block Reverse', 'Paradox Cave Chest Area'),
|
||||||
('Paradox Cave Push Block', 'Paradox Cave Front'),
|
('Paradox Cave Push Block', 'Paradox Cave Front'),
|
||||||
|
('Paradox Cave Chest Area NE', 'Paradox Cave Bomb Area'),
|
||||||
('Paradox Cave Bomb Jump', 'Paradox Cave'),
|
('Paradox Cave Bomb Jump', 'Paradox Cave'),
|
||||||
('Paradox Cave Drop', 'Paradox Cave Chest Area'),
|
('Paradox Cave Drop', 'Paradox Cave Chest Area'),
|
||||||
('Fairy Ascension Cave Climb', 'Fairy Ascension Cave (Top)'),
|
('Fairy Ascension Cave Climb', 'Fairy Ascension Cave (Top)'),
|
||||||
@@ -2081,6 +2083,8 @@ mandatory_connections = [('Old Man S&Q', 'Old Man House'),
|
|||||||
('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'),
|
('Hookshot Cave Middle to Front', 'Hookshot Cave (Front)'),
|
||||||
('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'),
|
('Hookshot Cave Middle to Back', 'Hookshot Cave (Back)'),
|
||||||
('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'),
|
('Hookshot Cave Back to Middle', 'Hookshot Cave (Middle)'),
|
||||||
|
('Hookshot Cave Bonk Path', 'Hookshot Cave (Bonk Islands)'),
|
||||||
|
('Hookshot Cave Hook Path', 'Hookshot Cave (Hook Islands)'),
|
||||||
('Ganon Drop', 'Bottom of Pyramid')
|
('Ganon Drop', 'Bottom of Pyramid')
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import RaceRandom as random
|
|||||||
import collections
|
import collections
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
import math
|
||||||
|
|
||||||
from BaseClasses import CollectionState, FillError
|
from BaseClasses import CollectionState, FillError, LocationType
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from Regions import shop_to_location_table, retro_shops
|
from Regions import shop_to_location_table, retro_shops
|
||||||
from source.item.FillUtil import filter_locations, classify_major_items, replace_trash_item, vanilla_fallback
|
from source.item.FillUtil import filter_locations, classify_major_items, replace_trash_item, vanilla_fallback
|
||||||
|
from source.item.FillUtil import filter_pot_locations, valid_pot_items
|
||||||
|
|
||||||
|
|
||||||
def get_dungeon_item_pool(world):
|
def get_dungeon_item_pool(world):
|
||||||
@@ -350,6 +352,25 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
|
|
||||||
# get items to distribute
|
# get items to distribute
|
||||||
classify_major_items(world)
|
classify_major_items(world)
|
||||||
|
# handle pot shuffle
|
||||||
|
pots_used = False
|
||||||
|
pot_item_pool = collections.defaultdict(list)
|
||||||
|
for item in world.itempool:
|
||||||
|
if item.name in ['Chicken', 'Big Magic']: # can only fill these in that players world
|
||||||
|
pot_item_pool[item.player].append(item)
|
||||||
|
for player, pot_pool in pot_item_pool.items():
|
||||||
|
if pot_pool:
|
||||||
|
for pot_item in pot_pool:
|
||||||
|
world.itempool.remove(pot_item)
|
||||||
|
pot_locations = [location for location in fill_locations
|
||||||
|
if location.type == LocationType.Pot and location.player == player]
|
||||||
|
pot_locations = filter_pot_locations(pot_locations, world)
|
||||||
|
fast_fill_helper(world, pot_pool, pot_locations)
|
||||||
|
pots_used = True
|
||||||
|
if pots_used:
|
||||||
|
fill_locations = world.get_unfilled_locations()
|
||||||
|
random.shuffle(fill_locations)
|
||||||
|
|
||||||
random.shuffle(world.itempool)
|
random.shuffle(world.itempool)
|
||||||
progitempool = [item for item in world.itempool if item.advancement]
|
progitempool = [item for item in world.itempool if item.advancement]
|
||||||
prioitempool = [item for item in world.itempool if not item.advancement and item.priority]
|
prioitempool = [item for item in world.itempool if not item.advancement and item.priority]
|
||||||
@@ -360,12 +381,24 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
|
|
||||||
# fill in gtower locations with trash first
|
# fill in gtower locations with trash first
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if not gftower_trash or not world.ganonstower_vanilla[player] or world.doorShuffle[player] == 'crossed' or world.logic[player] in ['owglitches', 'nologic']:
|
if (not gftower_trash or not world.ganonstower_vanilla[player]
|
||||||
|
or world.logic[player] in ['owglitches', 'nologic']):
|
||||||
continue
|
continue
|
||||||
max_trash = 8 if world.algorithm == 'dungeon_only' else 15
|
gt_count, total_count = calc_trash_locations(world, player)
|
||||||
gftower_trash_count = (random.randint(15, 50) if world.goal[player] in ['triforcehunt', 'trinity'] else random.randint(0, max_trash))
|
scale_factor = .75 * (world.crystals_needed_for_gt[player] / 7)
|
||||||
|
if world.algorithm == 'dungeon_only':
|
||||||
|
reserved_space = sum(1 for i in progitempool+prioitempool if i.player == player)
|
||||||
|
max_trash = max(0, min(gt_count, total_count - reserved_space))
|
||||||
|
else:
|
||||||
|
max_trash = gt_count
|
||||||
|
scaled_trash = math.floor(max_trash * scale_factor)
|
||||||
|
if world.goal[player] in ['triforcehunt', 'trinity']:
|
||||||
|
gftower_trash_count = random.randint(scaled_trash, max_trash)
|
||||||
|
else:
|
||||||
|
gftower_trash_count = random.randint(0, scaled_trash)
|
||||||
|
|
||||||
gtower_locations = [location for location in fill_locations if 'Ganons Tower' in location.name and location.player == player]
|
gtower_locations = [location for location in fill_locations if location.parent_region.dungeon
|
||||||
|
and location.parent_region.dungeon.name == 'Ganons Tower' and location.player == player]
|
||||||
random.shuffle(gtower_locations)
|
random.shuffle(gtower_locations)
|
||||||
trashcnt = 0
|
trashcnt = 0
|
||||||
while gtower_locations and restitempool and trashcnt < gftower_trash_count:
|
while gtower_locations and restitempool and trashcnt < gftower_trash_count:
|
||||||
@@ -415,6 +448,8 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
fill_locations.remove(l)
|
fill_locations.remove(l)
|
||||||
filtered_fill(world, placeholder_items, placeholder_locations)
|
filtered_fill(world, placeholder_items, placeholder_locations)
|
||||||
|
|
||||||
|
if world.players > 1:
|
||||||
|
fast_fill_pot_for_multiworld(world, restitempool, fill_locations)
|
||||||
if world.algorithm == 'vanilla_fill':
|
if world.algorithm == 'vanilla_fill':
|
||||||
fast_vanilla_fill(world, restitempool, fill_locations)
|
fast_vanilla_fill(world, restitempool, fill_locations)
|
||||||
else:
|
else:
|
||||||
@@ -424,6 +459,55 @@ def distribute_items_restrictive(world, gftower_trash=False, fill_locations=None
|
|||||||
unfilled = [location.name for location in fill_locations]
|
unfilled = [location.name for location in fill_locations]
|
||||||
if unplaced or unfilled:
|
if unplaced or unfilled:
|
||||||
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
logging.warning('Unplaced items: %s - Unfilled Locations: %s', unplaced, unfilled)
|
||||||
|
ensure_good_pots(world)
|
||||||
|
|
||||||
|
|
||||||
|
def calc_trash_locations(world, player):
|
||||||
|
total_count, gt_count = 0, 0
|
||||||
|
for loc in world.get_locations():
|
||||||
|
if (loc.player == player and loc.item is None
|
||||||
|
and (loc.type not in {LocationType.Pot, LocationType.Drop, LocationType.Normal} or not loc.forced_item)
|
||||||
|
and (loc.type != LocationType.Shop or world.shopsanity[player])
|
||||||
|
and loc.parent_region.dungeon):
|
||||||
|
total_count += 1
|
||||||
|
if loc.parent_region.dungeon.name == 'Ganons Tower':
|
||||||
|
gt_count += 1
|
||||||
|
return gt_count, total_count
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_good_pots(world, write_skips=False):
|
||||||
|
for loc in world.get_locations():
|
||||||
|
# convert Arrows 5 and Nothing when necessary
|
||||||
|
if (loc.item.name in {'Arrows (5)', 'Nothing'}
|
||||||
|
and (loc.type != LocationType.Pot or loc.item.player != loc.player)):
|
||||||
|
loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.item.player)
|
||||||
|
# can be placed here by multiworld balancing or shop balancing
|
||||||
|
# change it to something normal for the player it got swapped to
|
||||||
|
elif (loc.item.name in {'Chicken', 'Big Magic'}
|
||||||
|
and (loc.type != LocationType.Pot or loc.item.player != loc.player)):
|
||||||
|
if loc.type == LocationType.Pot:
|
||||||
|
loc.item.player = loc.player
|
||||||
|
else:
|
||||||
|
loc.item = ItemFactory(invalid_location_replacement[loc.item.name], loc.player)
|
||||||
|
# do the arrow retro check
|
||||||
|
if world.retro[loc.item.player] and loc.item.name in {'Arrows (5)', 'Arrows (10)'}:
|
||||||
|
loc.item = ItemFactory('Rupees (5)', loc.item.player)
|
||||||
|
# don't write out all pots to spoiler
|
||||||
|
if write_skips:
|
||||||
|
if loc.type == LocationType.Pot and loc.item.name in valid_pot_items:
|
||||||
|
loc.skip = True
|
||||||
|
|
||||||
|
|
||||||
|
invalid_location_replacement = {'Arrows (5)': 'Arrows (10)', 'Nothing': 'Rupees (5)',
|
||||||
|
'Chicken': 'Rupees (5)', 'Big Magic': 'Small Magic'}
|
||||||
|
|
||||||
|
|
||||||
|
def fast_fill_helper(world, item_pool, fill_locations):
|
||||||
|
if world.algorithm == 'vanilla_fill':
|
||||||
|
fast_vanilla_fill(world, item_pool, fill_locations)
|
||||||
|
else:
|
||||||
|
fast_fill(world, item_pool, fill_locations)
|
||||||
|
# todo: other fast fill methods?
|
||||||
|
|
||||||
|
|
||||||
def fast_fill(world, item_pool, fill_locations):
|
def fast_fill(world, item_pool, fill_locations):
|
||||||
@@ -433,6 +517,28 @@ def fast_fill(world, item_pool, fill_locations):
|
|||||||
world.push_item(spot_to_fill, item_to_place, False)
|
world.push_item(spot_to_fill, item_to_place, False)
|
||||||
|
|
||||||
|
|
||||||
|
def fast_fill_pot_for_multiworld(world, item_pool, fill_locations):
|
||||||
|
pot_item_pool = collections.defaultdict(list)
|
||||||
|
pot_fill_locations = collections.defaultdict(list)
|
||||||
|
for item in item_pool:
|
||||||
|
if item.name in valid_pot_items:
|
||||||
|
pot_item_pool[item.player].append(item)
|
||||||
|
for loc in fill_locations:
|
||||||
|
if loc.type == LocationType.Pot:
|
||||||
|
pot_fill_locations[loc.player].append(loc)
|
||||||
|
for player in range(1, world.players+1):
|
||||||
|
flex = 256 - world.pot_contents[player].multiworld_count
|
||||||
|
fill_count = len(pot_fill_locations[player]) - flex
|
||||||
|
if fill_count > 0:
|
||||||
|
fill_spots = random.sample(pot_fill_locations[player], fill_count)
|
||||||
|
fill_items = random.sample(pot_item_pool[player], fill_count)
|
||||||
|
for x in fill_items:
|
||||||
|
item_pool.remove(x)
|
||||||
|
for x in fill_spots:
|
||||||
|
fill_locations.remove(x)
|
||||||
|
fast_fill(world, fill_items, fill_spots)
|
||||||
|
|
||||||
|
|
||||||
def filtered_fill(world, item_pool, fill_locations):
|
def filtered_fill(world, item_pool, fill_locations):
|
||||||
while item_pool and fill_locations:
|
while item_pool and fill_locations:
|
||||||
item_to_place = item_pool.pop()
|
item_to_place = item_pool.pop()
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
if __name__ == '__main__':
|
||||||
|
from source.meta.check_requirements import check_requirements
|
||||||
|
check_requirements()
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
@@ -106,7 +110,7 @@ def guiMain(args=None):
|
|||||||
self.pages["startinventory"] = ttk.Frame(self.notebook)
|
self.pages["startinventory"] = ttk.Frame(self.notebook)
|
||||||
self.pages["custom"] = ttk.Frame(self.notebook)
|
self.pages["custom"] = ttk.Frame(self.notebook)
|
||||||
self.notebook.add(self.pages["randomizer"], text='Randomize')
|
self.notebook.add(self.pages["randomizer"], text='Randomize')
|
||||||
self.notebook.add(self.pages["adjust"], text='Adjust')
|
self.notebook.add(self.pages["adjust"], text='Adjust/Patch')
|
||||||
self.notebook.add(self.pages["startinventory"], text='Starting Inventory')
|
self.notebook.add(self.pages["startinventory"], text='Starting Inventory')
|
||||||
self.notebook.add(self.pages["custom"], text='Custom Item Pool')
|
self.notebook.add(self.pages["custom"], text='Custom Item Pool')
|
||||||
self.notebook.pack()
|
self.notebook.pack()
|
||||||
|
|||||||
+256
@@ -0,0 +1,256 @@
|
|||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from BaseClasses import CollectionState
|
||||||
|
from Utils import count_set_bits
|
||||||
|
|
||||||
|
SRAM_SIZE = 0x500
|
||||||
|
ROOM_DATA = 0x000
|
||||||
|
OVERWORLD_DATA = 0x280
|
||||||
|
|
||||||
|
def _new_default_sram():
|
||||||
|
sram_buf = [0x00] * 0x500
|
||||||
|
sram_buf[ROOM_DATA+0x20D] = 0xF0
|
||||||
|
sram_buf[ROOM_DATA+0x20F] = 0xF0
|
||||||
|
sram_buf[0x379] = 0x68
|
||||||
|
sram_buf[0x401] = 0xFF
|
||||||
|
sram_buf[0x402] = 0xFF
|
||||||
|
return sram_buf
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InitialSram:
|
||||||
|
_initial_sram_bytes: List[int] = field(default_factory=_new_default_sram)
|
||||||
|
|
||||||
|
def _set_value(self, idx: int, val:int):
|
||||||
|
if idx > SRAM_SIZE:
|
||||||
|
raise IndexError('SRAM index out of bounds: {idx}')
|
||||||
|
if not (-1 < val < 256):
|
||||||
|
raise ValueError('SRAM value must be between 0 and 255: {val}')
|
||||||
|
self._initial_sram_bytes[idx] = val
|
||||||
|
|
||||||
|
def _or_value(self, idx: int, val:int):
|
||||||
|
if idx > SRAM_SIZE:
|
||||||
|
raise IndexError('SRAM index out of bounds: {idx}')
|
||||||
|
if not (-1 < val < 256):
|
||||||
|
raise ValueError('SRAM value must be between 0 and 255: {val}')
|
||||||
|
self._initial_sram_bytes[idx] |= val
|
||||||
|
|
||||||
|
def pre_open_aga_curtains(self):
|
||||||
|
self._or_value(ROOM_DATA+0x61, 0x80)
|
||||||
|
|
||||||
|
def pre_open_skullwoods_curtains(self):
|
||||||
|
self._or_value(ROOM_DATA+0x93, 0x80)
|
||||||
|
|
||||||
|
def pre_open_lumberjack(self):
|
||||||
|
self._or_value(OVERWORLD_DATA+0x02, 0x20)
|
||||||
|
|
||||||
|
def pre_open_castle_gate(self):
|
||||||
|
self._or_value(OVERWORLD_DATA+0x1B, 0x20)
|
||||||
|
|
||||||
|
def pre_open_ganons_tower(self):
|
||||||
|
self._or_value(OVERWORLD_DATA+0x43, 0x20)
|
||||||
|
|
||||||
|
def pre_open_pyramid_hole(self):
|
||||||
|
self._or_value(OVERWORLD_DATA+0x5B, 0x20)
|
||||||
|
|
||||||
|
def pre_set_overworld_flag(self, owid, bitmask):
|
||||||
|
self._or_value(OVERWORLD_DATA+owid, bitmask)
|
||||||
|
|
||||||
|
def set_starting_equipment(self, world: object, player: int):
|
||||||
|
equip = [0] * (0x340 + 0x4F)
|
||||||
|
equip[0x36C] = 0x18
|
||||||
|
equip[0x36D] = 0x18
|
||||||
|
equip[0x379] = 0x68
|
||||||
|
if world.bombbag[player]:
|
||||||
|
starting_max_bombs = 0
|
||||||
|
else:
|
||||||
|
starting_max_bombs = 10
|
||||||
|
starting_max_arrows = 30
|
||||||
|
starting_bomb_cap_upgrades = 0
|
||||||
|
starting_arrow_cap_upgrades = 0
|
||||||
|
starting_bombs = 0
|
||||||
|
starting_arrows = 0
|
||||||
|
|
||||||
|
startingstate = CollectionState(world)
|
||||||
|
|
||||||
|
if startingstate.has('Bow', player):
|
||||||
|
equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1
|
||||||
|
equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases
|
||||||
|
if not world.retro[player]:
|
||||||
|
equip[0x38E] |= 0x80
|
||||||
|
if startingstate.has('Silver Arrows', player):
|
||||||
|
equip[0x38E] |= 0x40
|
||||||
|
|
||||||
|
if startingstate.has('Titans Mitts', player):
|
||||||
|
equip[0x354] = 2
|
||||||
|
elif startingstate.has('Power Glove', player):
|
||||||
|
equip[0x354] = 1
|
||||||
|
|
||||||
|
if startingstate.has('Golden Sword', player):
|
||||||
|
equip[0x359] = 4
|
||||||
|
elif startingstate.has('Tempered Sword', player):
|
||||||
|
equip[0x359] = 3
|
||||||
|
elif startingstate.has('Master Sword', player):
|
||||||
|
equip[0x359] = 2
|
||||||
|
elif startingstate.has('Fighter Sword', player):
|
||||||
|
equip[0x359] = 1
|
||||||
|
|
||||||
|
if startingstate.has('Mirror Shield', player):
|
||||||
|
equip[0x35A] = 3
|
||||||
|
elif startingstate.has('Red Shield', player):
|
||||||
|
equip[0x35A] = 2
|
||||||
|
elif startingstate.has('Blue Shield', player):
|
||||||
|
equip[0x35A] = 1
|
||||||
|
|
||||||
|
if startingstate.has('Red Mail', player):
|
||||||
|
equip[0x35B] = 2
|
||||||
|
elif startingstate.has('Blue Mail', player):
|
||||||
|
equip[0x35B] = 1
|
||||||
|
|
||||||
|
if startingstate.has('Magic Upgrade (1/4)', player):
|
||||||
|
equip[0x37B] = 2
|
||||||
|
equip[0x36E] = 0x80
|
||||||
|
elif startingstate.has('Magic Upgrade (1/2)', player):
|
||||||
|
equip[0x37B] = 1
|
||||||
|
equip[0x36E] = 0x80
|
||||||
|
|
||||||
|
for item in world.precollected_items:
|
||||||
|
if item.player != player:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if item.name in ['Bow', 'Silver Arrows', 'Progressive Bow', 'Progressive Bow (Alt)',
|
||||||
|
'Titans Mitts', 'Power Glove', 'Progressive Glove',
|
||||||
|
'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword',
|
||||||
|
'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield',
|
||||||
|
'Red Mail', 'Blue Mail', 'Progressive Armor',
|
||||||
|
'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']:
|
||||||
|
continue
|
||||||
|
|
||||||
|
set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2),
|
||||||
|
'Cape': (0x352, 1), 'Lamp': (0x34A, 1), 'Moon Pearl': (0x357, 1), 'Cane of Somaria': (0x350, 1), 'Cane of Byrna': (0x351, 1),
|
||||||
|
'Fire Rod': (0x345, 1), 'Ice Rod': (0x346, 1), 'Bombos': (0x347, 1), 'Ether': (0x348, 1), 'Quake': (0x349, 1)}
|
||||||
|
or_table = {'Green Pendant': (0x374, 0x04), 'Red Pendant': (0x374, 0x01), 'Blue Pendant': (0x374, 0x02),
|
||||||
|
'Crystal 1': (0x37A, 0x02), 'Crystal 2': (0x37A, 0x10), 'Crystal 3': (0x37A, 0x40), 'Crystal 4': (0x37A, 0x20),
|
||||||
|
'Crystal 5': (0x37A, 0x04), 'Crystal 6': (0x37A, 0x01), 'Crystal 7': (0x37A, 0x08),
|
||||||
|
'Big Key (Eastern Palace)': (0x367, 0x20), 'Compass (Eastern Palace)': (0x365, 0x20), 'Map (Eastern Palace)': (0x369, 0x20),
|
||||||
|
'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10),
|
||||||
|
'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20),
|
||||||
|
'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0),
|
||||||
|
'Big Key (Agahnims Tower)': (0x367, 0x08), 'Compass (Agahnims Tower)': (0x365, 0x08), 'Map (Agahnims Tower)': (0x369, 0x08),
|
||||||
|
'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02),
|
||||||
|
'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10),
|
||||||
|
'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80),
|
||||||
|
'Big Key (Swamp Palace)': (0x367, 0x04), 'Compass (Swamp Palace)': (0x365, 0x04), 'Map (Swamp Palace)': (0x369, 0x04),
|
||||||
|
'Big Key (Ice Palace)': (0x366, 0x40), 'Compass (Ice Palace)': (0x364, 0x40), 'Map (Ice Palace)': (0x368, 0x40),
|
||||||
|
'Big Key (Misery Mire)': (0x367, 0x01), 'Compass (Misery Mire)': (0x365, 0x01), 'Map (Misery Mire)': (0x369, 0x01),
|
||||||
|
'Big Key (Turtle Rock)': (0x366, 0x08), 'Compass (Turtle Rock)': (0x364, 0x08), 'Map (Turtle Rock)': (0x368, 0x08),
|
||||||
|
'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), 'Map (Ganons Tower)': (0x368, 0x04)}
|
||||||
|
set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02),'Pegasus Boots': (0x355, 1, 0x379, 0x04),
|
||||||
|
'Shovel': (0x34C, 1, 0x38C, 0x04), 'Ocarina': (0x34C, 3, 0x38C, 0x01),
|
||||||
|
'Mushroom': (0x344, 1, 0x38C, 0x20 | 0x08), 'Magic Powder': (0x344, 2, 0x38C, 0x10),
|
||||||
|
'Blue Boomerang': (0x341, 1, 0x38C, 0x80), 'Red Boomerang': (0x341, 2, 0x38C, 0x40)}
|
||||||
|
keys = {'Small Key (Eastern Palace)': [0x37E], 'Small Key (Desert Palace)': [0x37F],
|
||||||
|
'Small Key (Tower of Hera)': [0x386],
|
||||||
|
'Small Key (Agahnims Tower)': [0x380], 'Small Key (Palace of Darkness)': [0x382],
|
||||||
|
'Small Key (Thieves Town)': [0x387],
|
||||||
|
'Small Key (Skull Woods)': [0x384], 'Small Key (Swamp Palace)': [0x381],
|
||||||
|
'Small Key (Ice Palace)': [0x385],
|
||||||
|
'Small Key (Misery Mire)': [0x383], 'Small Key (Turtle Rock)': [0x388],
|
||||||
|
'Small Key (Ganons Tower)': [0x389],
|
||||||
|
'Small Key (Universal)': [0x38B], 'Small Key (Escape)': [0x37C, 0x37D]}
|
||||||
|
bottles = {'Bottle': 2, 'Bottle (Red Potion)': 3, 'Bottle (Green Potion)': 4, 'Bottle (Blue Potion)': 5,
|
||||||
|
'Bottle (Fairy)': 6, 'Bottle (Bee)': 7, 'Bottle (Good Bee)': 8}
|
||||||
|
rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100, 'Rupees (300)': 300}
|
||||||
|
bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10}
|
||||||
|
arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10}
|
||||||
|
bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10}
|
||||||
|
arrows = {'Single Arrow': 1, 'Arrows (10)': 10}
|
||||||
|
|
||||||
|
if item.name in set_table:
|
||||||
|
equip[set_table[item.name][0]] = set_table[item.name][1]
|
||||||
|
elif item.name in or_table:
|
||||||
|
equip[or_table[item.name][0]] |= or_table[item.name][1]
|
||||||
|
elif item.name in set_or_table:
|
||||||
|
equip[set_or_table[item.name][0]] = set_or_table[item.name][1]
|
||||||
|
equip[set_or_table[item.name][2]] |= set_or_table[item.name][3]
|
||||||
|
elif item.name in keys:
|
||||||
|
for address in keys[item.name]:
|
||||||
|
equip[address] = min(equip[address] + 1, 99)
|
||||||
|
elif item.name in bottles:
|
||||||
|
if equip[0x34F] < world.difficulty_requirements[player].progressive_bottle_limit:
|
||||||
|
equip[0x35C + equip[0x34F]] = bottles[item.name]
|
||||||
|
equip[0x34F] += 1
|
||||||
|
elif item.name in rupees:
|
||||||
|
equip[0x360:0x362] = list(min(equip[0x360] + (equip[0x361] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False))
|
||||||
|
equip[0x362:0x364] = list(min(equip[0x362] + (equip[0x363] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False))
|
||||||
|
elif item.name in bomb_caps:
|
||||||
|
starting_bomb_cap_upgrades += bomb_caps[item.name]
|
||||||
|
elif item.name in arrow_caps:
|
||||||
|
starting_arrow_cap_upgrades += arrow_caps[item.name]
|
||||||
|
elif item.name in bombs:
|
||||||
|
starting_bombs += bombs[item.name]
|
||||||
|
elif item.name in arrows:
|
||||||
|
if world.retro[player]:
|
||||||
|
equip[0x38E] |= 0x80
|
||||||
|
starting_arrows = 1
|
||||||
|
else:
|
||||||
|
starting_arrows += arrows[item.name]
|
||||||
|
elif item.name in ['Piece of Heart', 'Boss Heart Container', 'Sanctuary Heart Container']:
|
||||||
|
if item.name == 'Piece of Heart':
|
||||||
|
equip[0x36B] = (equip[0x36B] + 1) % 4
|
||||||
|
if item.name != 'Piece of Heart' or equip[0x36B] == 0:
|
||||||
|
equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0)
|
||||||
|
equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f'Unsupported item in starting equipment: {item.name}')
|
||||||
|
|
||||||
|
equip[0x370] = min(starting_bomb_cap_upgrades, 50)
|
||||||
|
equip[0x371] = min(starting_arrow_cap_upgrades, 70)
|
||||||
|
equip[0x343] = min(starting_bombs, (equip[0x370] + starting_max_bombs))
|
||||||
|
equip[0x377] = min(starting_arrows, (equip[0x371] + starting_max_arrows))
|
||||||
|
|
||||||
|
# Assertion and copy equip to initial_sram_bytes
|
||||||
|
assert equip[:0x340] == [0] * 0x340
|
||||||
|
self._initial_sram_bytes[0x340:0x38F] = equip[0x340:0x38F]
|
||||||
|
|
||||||
|
# Set counters and highest equipment values
|
||||||
|
self._initial_sram_bytes[0x471] = count_set_bits(self._initial_sram_bytes[0x37A])
|
||||||
|
self._initial_sram_bytes[0x429] = count_set_bits(self._initial_sram_bytes[0x374])
|
||||||
|
self._initial_sram_bytes[0x417] = self._initial_sram_bytes[0x359]
|
||||||
|
self._initial_sram_bytes[0x422] = self._initial_sram_bytes[0x35A]
|
||||||
|
self._initial_sram_bytes[0x46E] = self._initial_sram_bytes[0x35B]
|
||||||
|
|
||||||
|
if world.swords[player] == "swordless":
|
||||||
|
self._initial_sram_bytes[0x359] = 0xFF
|
||||||
|
self._initial_sram_bytes[0x417] = 0x00
|
||||||
|
|
||||||
|
def set_starting_rupees(self, rupees: int):
|
||||||
|
if not (-1 < rupees < 10000):
|
||||||
|
raise ValueError("Starting rupees must be between 0 and 9999")
|
||||||
|
self._initial_sram_bytes[0x362] = self._initial_sram_bytes[0x360] = rupees & 0xFF
|
||||||
|
self._initial_sram_bytes[0x363] = self._initial_sram_bytes[0x361] = rupees >> 8
|
||||||
|
|
||||||
|
def set_progress_indicator(self, indicator: int):
|
||||||
|
self._set_value(0x3C5, indicator)
|
||||||
|
|
||||||
|
def set_progress_flags(self, flags: int):
|
||||||
|
self._set_value(0x3C6, flags)
|
||||||
|
|
||||||
|
def set_starting_entrance(self, entrance: int):
|
||||||
|
self._set_value(0x3C8, entrance)
|
||||||
|
|
||||||
|
def set_starting_timer(self, seconds: int):
|
||||||
|
timer = (seconds * 60).to_bytes(4, "little")
|
||||||
|
self._initial_sram_bytes[0x454] = timer[0]
|
||||||
|
self._initial_sram_bytes[0x455] = timer[1]
|
||||||
|
self._initial_sram_bytes[0x456] = timer[2]
|
||||||
|
self._initial_sram_bytes[0x457] = timer[3]
|
||||||
|
|
||||||
|
def set_swordless_curtains(self):
|
||||||
|
self._or_value(ROOM_DATA+0x61, 0x80)
|
||||||
|
self._or_value(ROOM_DATA+0x93, 0x80)
|
||||||
|
|
||||||
|
def get_initial_sram(self):
|
||||||
|
assert len(self._initial_sram_bytes) == SRAM_SIZE
|
||||||
|
|
||||||
|
return self._initial_sram_bytes[:]
|
||||||
+99
-24
@@ -3,13 +3,14 @@ import logging
|
|||||||
import math
|
import math
|
||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
|
|
||||||
from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState
|
from BaseClasses import Region, RegionType, Shop, ShopType, Location, CollectionState, PotItem
|
||||||
from EntranceShuffle import connect_entrance
|
from EntranceShuffle import connect_entrance
|
||||||
from Regions import shop_to_location_table, retro_shops, shop_table_by_location
|
from Regions import shop_to_location_table, retro_shops, shop_table_by_location, valid_pot_location
|
||||||
from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool
|
from Fill import FillError, fill_restrictive, fast_fill, get_dungeon_item_pool
|
||||||
|
from PotShuffle import vanilla_pots
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
|
|
||||||
from source.item.FillUtil import trash_items
|
from source.item.FillUtil import trash_items, pot_items
|
||||||
|
|
||||||
import source.classes.constants as CONST
|
import source.classes.constants as CONST
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ Difficulty = namedtuple('Difficulty',
|
|||||||
'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit'])
|
'progressive_bow_limit', 'heart_piece_limit', 'boss_heart_container_limit'])
|
||||||
|
|
||||||
total_items_to_place = 153
|
total_items_to_place = 153
|
||||||
|
max_goal = 850
|
||||||
|
|
||||||
difficulties = {
|
difficulties = {
|
||||||
'normal': Difficulty(
|
'normal': Difficulty(
|
||||||
@@ -203,6 +205,7 @@ def generate_itempool(world, player):
|
|||||||
world.push_item(loc, ItemFactory('Triforce', player), False)
|
world.push_item(loc, ItemFactory('Triforce', player), False)
|
||||||
loc.event = True
|
loc.event = True
|
||||||
loc.locked = True
|
loc.locked = True
|
||||||
|
loc.forced_item = loc.item
|
||||||
|
|
||||||
world.get_location('Ganon', player).event = True
|
world.get_location('Ganon', player).event = True
|
||||||
world.get_location('Ganon', player).locked = True
|
world.get_location('Ganon', player).locked = True
|
||||||
@@ -254,6 +257,9 @@ def generate_itempool(world, player):
|
|||||||
world.push_item(world.get_location('Ice Block Drop', player), ItemFactory('Convenient Block', player), False)
|
world.push_item(world.get_location('Ice Block Drop', player), ItemFactory('Convenient Block', player), False)
|
||||||
world.get_location('Ice Block Drop', player).event = True
|
world.get_location('Ice Block Drop', player).event = True
|
||||||
world.get_location('Ice Block Drop', player).locked = True
|
world.get_location('Ice Block Drop', player).locked = True
|
||||||
|
world.push_item(world.get_location('Skull Star Tile', player), ItemFactory('Hidden Pits', player), False)
|
||||||
|
world.get_location('Skull Star Tile', player).event = True
|
||||||
|
world.get_location('Skull Star Tile', player).locked = True
|
||||||
if world.mode[player] == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
world.push_item(world.get_location('Zelda Pickup', player), ItemFactory('Zelda Herself', player), False)
|
world.push_item(world.get_location('Zelda Pickup', player), ItemFactory('Zelda Herself', player), False)
|
||||||
world.get_location('Zelda Pickup', player).event = True
|
world.get_location('Zelda Pickup', player).event = True
|
||||||
@@ -342,8 +348,10 @@ def generate_itempool(world, player):
|
|||||||
if clock_mode is not None:
|
if clock_mode is not None:
|
||||||
world.clock_mode = clock_mode
|
world.clock_mode = clock_mode
|
||||||
|
|
||||||
if world.goal[player] in ['triforcehunt', 'trinity']:
|
goal = world.goal[player]
|
||||||
world.treasure_hunt_count[player], world.treasure_hunt_total[player] = set_default_triforce(world.goal[player], world.treasure_hunt_count[player], world.treasure_hunt_total[player])
|
if goal in ['triforcehunt', 'trinity']:
|
||||||
|
g, t = set_default_triforce(goal, world.treasure_hunt_count[player], world.treasure_hunt_total[player])
|
||||||
|
world.treasure_hunt_count[player], world.treasure_hunt_total[player] = g, t
|
||||||
world.treasure_hunt_icon[player] = 'Triforce Piece'
|
world.treasure_hunt_icon[player] = 'Triforce Piece'
|
||||||
|
|
||||||
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player
|
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player
|
||||||
@@ -393,11 +401,16 @@ def generate_itempool(world, player):
|
|||||||
|
|
||||||
if world.retro[player]:
|
if world.retro[player]:
|
||||||
set_up_take_anys(world, player)
|
set_up_take_anys(world, player)
|
||||||
if world.keydropshuffle[player]:
|
if world.dropshuffle[player]:
|
||||||
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 32
|
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 13
|
||||||
|
if world.pottery[player] not in ['none', 'cave']:
|
||||||
|
world.itempool += [ItemFactory('Small Key (Universal)', player)] * 19
|
||||||
|
|
||||||
create_dynamic_shop_locations(world, player)
|
create_dynamic_shop_locations(world, player)
|
||||||
|
|
||||||
|
if world.pottery[player] not in ['none', 'keys']:
|
||||||
|
add_pot_contents(world, player)
|
||||||
|
|
||||||
|
|
||||||
take_any_locations = [
|
take_any_locations = [
|
||||||
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
|
'Snitch Lady (East)', 'Snitch Lady (West)', 'Bush Covered House', 'Light World Bomb Hut',
|
||||||
@@ -418,7 +431,9 @@ def set_up_take_anys(world, player):
|
|||||||
if 'Archery Game' in take_any_locations:
|
if 'Archery Game' in take_any_locations:
|
||||||
take_any_locations.remove('Archery Game')
|
take_any_locations.remove('Archery Game')
|
||||||
|
|
||||||
regions = random.sample(take_any_locations, 5)
|
take_any_candidates = [x for x in take_any_locations if len(world.get_region(x, player).locations) == 0]
|
||||||
|
|
||||||
|
regions = random.sample(take_any_candidates, 5)
|
||||||
|
|
||||||
old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player)
|
old_man_take_any = Region("Old Man Sword Cave", RegionType.Cave, 'the sword cave', player)
|
||||||
world.regions.append(old_man_take_any)
|
world.regions.append(old_man_take_any)
|
||||||
@@ -754,17 +769,25 @@ rupee_chart = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)'
|
|||||||
'Rupees (100)': 100, 'Rupees (300)': 300}
|
'Rupees (100)': 100, 'Rupees (300)': 300}
|
||||||
|
|
||||||
|
|
||||||
|
def add_pot_contents(world, player):
|
||||||
|
for super_tile, pot_list in vanilla_pots.items():
|
||||||
|
for pot in pot_list:
|
||||||
|
if pot.item not in [PotItem.Hole, PotItem.Key, PotItem.Switch]:
|
||||||
|
if valid_pot_location(pot, world.pot_pool[player], world, player):
|
||||||
|
item = ('Rupees (5)' if world.retro[player] and pot_items[pot.item] == 'Arrows (5)'
|
||||||
|
else pot_items[pot.item])
|
||||||
|
world.itempool.append(ItemFactory(item, player))
|
||||||
|
|
||||||
|
|
||||||
def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic, flute_activated):
|
def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer, goal, mode, swords, retro, bombbag, door_shuffle, logic, flute_activated):
|
||||||
pool = []
|
pool = []
|
||||||
placed_items = {}
|
placed_items = {}
|
||||||
precollected_items = []
|
precollected_items = []
|
||||||
clock_mode = None
|
clock_mode = None
|
||||||
if treasure_hunt_total == 0:
|
if treasure_hunt_total == 0 and goal in ['triforcehunt', 'trinity']:
|
||||||
if goal == 'triforcehunt':
|
treasure_hunt_total = 30 if goal == 'triforcehunt' else 10
|
||||||
treasure_hunt_total = 30
|
# triforce pieces max out
|
||||||
elif goal == 'trinity':
|
triforcepool = ['Triforce Piece'] * min(treasure_hunt_total, max_goal)
|
||||||
treasure_hunt_total = 10
|
|
||||||
triforcepool = ['Triforce Piece'] * int(treasure_hunt_total)
|
|
||||||
|
|
||||||
pool.extend(alwaysitems)
|
pool.extend(alwaysitems)
|
||||||
|
|
||||||
@@ -851,6 +874,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
|
|||||||
place_item('Master Sword Pedestal', swords_to_use.pop())
|
place_item('Master Sword Pedestal', swords_to_use.pop())
|
||||||
else:
|
else:
|
||||||
place_item('Master Sword Pedestal', 'Triforce')
|
place_item('Master Sword Pedestal', 'Triforce')
|
||||||
|
pool.append(swords_to_use.pop())
|
||||||
else:
|
else:
|
||||||
pool.extend(diff.progressivesword if want_progressives() else diff.basicsword)
|
pool.extend(diff.progressivesword if want_progressives() else diff.basicsword)
|
||||||
if swords == 'assured':
|
if swords == 'assured':
|
||||||
@@ -862,26 +886,19 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
|
|||||||
pool.remove('Fighter Sword')
|
pool.remove('Fighter Sword')
|
||||||
pool.extend(['Rupees (50)'])
|
pool.extend(['Rupees (50)'])
|
||||||
|
|
||||||
extraitems = total_items_to_place - len(pool) - len(placed_items)
|
|
||||||
|
|
||||||
if timer in ['timed', 'timed-countdown']:
|
if timer in ['timed', 'timed-countdown']:
|
||||||
pool.extend(diff.timedother)
|
pool.extend(diff.timedother)
|
||||||
extraitems -= len(diff.timedother)
|
|
||||||
clock_mode = 'stopwatch' if timer == 'timed' else 'countdown'
|
clock_mode = 'stopwatch' if timer == 'timed' else 'countdown'
|
||||||
elif timer == 'timed-ohko':
|
elif timer == 'timed-ohko':
|
||||||
pool.extend(diff.timedohko)
|
pool.extend(diff.timedohko)
|
||||||
extraitems -= len(diff.timedohko)
|
|
||||||
clock_mode = 'countdown-ohko'
|
clock_mode = 'countdown-ohko'
|
||||||
if goal in ['triforcehunt', 'trinity']:
|
if goal in ['triforcehunt', 'trinity']:
|
||||||
pool.extend(triforcepool)
|
pool.extend(triforcepool)
|
||||||
extraitems -= len(triforcepool)
|
|
||||||
|
|
||||||
for extra in diff.extras:
|
for extra in diff.extras:
|
||||||
if extraitems > 0:
|
|
||||||
if len(extra) > extraitems:
|
|
||||||
extra = random.choices(extra, k=extraitems)
|
|
||||||
pool.extend(extra)
|
pool.extend(extra)
|
||||||
extraitems -= len(extra)
|
|
||||||
|
# note: massage item pool now handles shrinking the pool appropriately
|
||||||
|
|
||||||
if goal in ['pedestal', 'trinity'] and swords != 'vanilla':
|
if goal in ['pedestal', 'trinity'] and swords != 'vanilla':
|
||||||
place_item('Master Sword Pedestal', 'Triforce')
|
place_item('Master Sword Pedestal', 'Triforce')
|
||||||
@@ -906,6 +923,7 @@ def get_pool_core(progressive, shuffle, difficulty, treasure_hunt_total, timer,
|
|||||||
pool.extend(['Small Key (Universal)'])
|
pool.extend(['Small Key (Universal)'])
|
||||||
return (pool, placed_items, precollected_items, clock_mode, lamps_needed_for_dark_rooms)
|
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, bombbag, customitemarray):
|
def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, swords, retro, bombbag, customitemarray):
|
||||||
pool = []
|
pool = []
|
||||||
placed_items = {}
|
placed_items = {}
|
||||||
@@ -932,7 +950,8 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
|||||||
|
|
||||||
# Triforce Pieces
|
# Triforce Pieces
|
||||||
if goal in ['triforcehunt', 'trinity']:
|
if goal in ['triforcehunt', 'trinity']:
|
||||||
customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
|
g, t = set_default_triforce(goal, customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"])
|
||||||
|
customitemarray["triforcepiecesgoal"], customitemarray["triforcepieces"] = g, t
|
||||||
|
|
||||||
itemtotal = 0
|
itemtotal = 0
|
||||||
# Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors
|
# Bow to Silver Arrows Upgrade, including Generic Keys & Rupoors
|
||||||
@@ -964,7 +983,15 @@ def make_custom_item_pool(progressive, shuffle, difficulty, timer, goal, mode, s
|
|||||||
pool.append(thisbottle)
|
pool.append(thisbottle)
|
||||||
|
|
||||||
if customitemarray["triforcepieces"] > 0 or customitemarray["triforcepiecesgoal"] > 0:
|
if customitemarray["triforcepieces"] > 0 or customitemarray["triforcepiecesgoal"] > 0:
|
||||||
|
# Location pool doesn't support larger values
|
||||||
|
treasure_hunt_count = max(min(customitemarray["triforcepiecesgoal"], max_goal), 1)
|
||||||
treasure_hunt_icon = 'Triforce Piece'
|
treasure_hunt_icon = 'Triforce Piece'
|
||||||
|
# Ensure game is always possible to complete here, force sufficient pieces if the player is unwilling.
|
||||||
|
if ((customitemarray["triforcepieces"] < treasure_hunt_count) and (goal in ['triforcehunt', 'trinity'])
|
||||||
|
and (customitemarray["triforce"] == 0)):
|
||||||
|
extrapieces = treasure_hunt_count - customitemarray["triforcepieces"]
|
||||||
|
pool.extend(['Triforce Piece'] * extrapieces)
|
||||||
|
itemtotal = itemtotal + extrapieces
|
||||||
|
|
||||||
if timer in ['display', 'timed', 'timed-countdown']:
|
if timer in ['display', 'timed', 'timed-countdown']:
|
||||||
clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch'
|
clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch'
|
||||||
@@ -1020,6 +1047,54 @@ def set_default_triforce(goal, custom_goal, custom_total):
|
|||||||
triforce_total = max(min(custom_total, 128), triforce_goal) #128 max to ensure other progression can fit.
|
triforce_total = max(min(custom_total, 128), triforce_goal) #128 max to ensure other progression can fit.
|
||||||
return (triforce_goal, triforce_total)
|
return (triforce_goal, triforce_total)
|
||||||
|
|
||||||
|
|
||||||
|
def make_customizer_pool(world, player):
|
||||||
|
pool = []
|
||||||
|
placed_items = {}
|
||||||
|
precollected_items = []
|
||||||
|
clock_mode = None
|
||||||
|
|
||||||
|
def place_item(loc, item):
|
||||||
|
assert loc not in placed_items
|
||||||
|
placed_items[loc] = item
|
||||||
|
|
||||||
|
diff = difficulties[world.difficulty[player]]
|
||||||
|
for item_name, amount in world.customizer.get_item_pool()[player].items():
|
||||||
|
if isinstance(amount, int):
|
||||||
|
if item_name == 'Bottle (Random)':
|
||||||
|
for _ in range(amount):
|
||||||
|
pool.append(random.choice(diff.bottles))
|
||||||
|
else:
|
||||||
|
pool.extend([item_name] * amount)
|
||||||
|
|
||||||
|
timer = world.timer[player]
|
||||||
|
if timer in ['display', 'timed', 'timed-countdown']:
|
||||||
|
clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch'
|
||||||
|
elif timer == 'timed-ohko':
|
||||||
|
clock_mode = 'countdown-ohko'
|
||||||
|
elif timer == 'ohko':
|
||||||
|
clock_mode = 'ohko'
|
||||||
|
|
||||||
|
if world.goal[player] == 'pedestal':
|
||||||
|
place_item('Master Sword Pedestal', 'Triforce')
|
||||||
|
|
||||||
|
return pool, placed_items, precollected_items, clock_mode, 1
|
||||||
|
|
||||||
|
|
||||||
|
# location pool doesn't support larger values at this time
|
||||||
|
def set_default_triforce(goal, custom_goal, custom_total):
|
||||||
|
triforce_goal, triforce_total = 0, 0
|
||||||
|
if goal == 'triforcehunt':
|
||||||
|
triforce_goal, triforce_total = 20, 30
|
||||||
|
elif goal == 'trinity':
|
||||||
|
triforce_goal, triforce_total = 8, 10
|
||||||
|
if custom_goal > 0:
|
||||||
|
triforce_goal = max(min(custom_goal, max_goal), 1)
|
||||||
|
if custom_total > 0:
|
||||||
|
triforce_total = max(min(custom_total, max_goal), triforce_goal)
|
||||||
|
return triforce_goal, triforce_total
|
||||||
|
|
||||||
|
|
||||||
# A quick test to ensure all combinations generate the correct amount of items.
|
# A quick test to ensure all combinations generate the correct amount of items.
|
||||||
def test():
|
def test():
|
||||||
for difficulty in ['normal', 'hard', 'expert']:
|
for difficulty in ['normal', 'hard', 'expert']:
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
|
|||||||
'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
'Progressive Bow': (True, False, None, 0x64, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
||||||
'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
'Progressive Bow (Alt)': (True, False, None, 0x65, 150, 'You have\nchosen the\narcher class.', 'the stick and twine', 'arrow-slinging kid', 'arrow sling for sale', 'witch and robin hood', 'archer boy shoots again', 'a Bow'),
|
||||||
'Book of Mudora': (True, False, None, 0x1D, 150, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'),
|
'Book of Mudora': (True, False, None, 0x1D, 150, 'This is a\nparadox?!', 'and the story book', 'the scholarly kid', 'moon runes for sale', 'drugs for literacy', 'book-worm boy can read again', 'the Book'),
|
||||||
'Hammer': (True, False, None, 0x09, 250, 'stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the Hammer'),
|
'Hammer': (True, False, None, 0x09, 250, 'Stop\nhammer time!', 'and m c hammer', 'hammer-smashing kid', 'm c hammer for sale', 'stop... hammer time', 'stop, hammer time', 'the Hammer'),
|
||||||
'Hookshot': (True, False, None, 0x0A, 250, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'),
|
'Hookshot': (True, False, None, 0x0A, 250, 'BOING!!!\nBOING!!!\nBOING!!!', 'and the tickle beam', 'tickle-monster kid', 'tickle beam for sale', 'witch and tickle boy', 'beam boy tickles again', 'the Hookshot'),
|
||||||
'Magic Mirror': (True, False, None, 0x1A, 250, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'),
|
'Magic Mirror': (True, False, None, 0x1A, 250, 'Isn\'t your\nreflection so\npretty?', 'the face reflector', 'the narcissistic kid', 'your face for sale', 'trades looking-glass', 'narcissistic boy is happy again', 'the Mirror'),
|
||||||
'Ocarina': (True, False, None, 0x14, 250, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'),
|
'Ocarina': (True, False, None, 0x14, 250, 'Save the duck\nand fly to\nfreedom!', 'and the duck call', 'the duck-call kid', 'duck call for sale', 'duck-calls for trade', 'ocarina boy plays again', 'the Flute'),
|
||||||
@@ -38,65 +38,70 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
|
|||||||
'Mushroom': (True, False, None, 0x29, 50, 'I\'m a fun guy!\n\nI\'m a funghi!', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'the Mushroom'),
|
'Mushroom': (True, False, None, 0x29, 50, 'I\'m a fun guy!\n\nI\'m a funghi!', 'and the legal drugs', 'the drug-dealing kid', 'legal drugs for sale', 'shroom swap', 'shroom boy sells drugs again', 'the Mushroom'),
|
||||||
'Shovel': (True, False, None, 0x13, 50, 'Can\n You\n Dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'the Shovel'),
|
'Shovel': (True, False, None, 0x13, 50, 'Can\n You\n Dig it?', 'and the spade', 'archaeologist kid', 'dirt spade for sale', 'can you dig it', 'shovel boy digs again', 'the Shovel'),
|
||||||
'Lamp': (True, False, None, 0x12, 150, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the Lamp'),
|
'Lamp': (True, False, None, 0x12, 150, 'Baby, baby,\nbaby.\nLight my way!', 'and the flashlight', 'light-shining kid', 'flashlight for sale', 'fungus for illumination', 'illuminated boy can see again', 'the Lamp'),
|
||||||
'Magic Powder': (True, False, None, 0x0D, 50, 'you can turn\nanti-faeries\ninto faeries', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the Powder'),
|
'Magic Powder': (True, False, None, 0x0D, 50, 'You can turn\nanti-faeries\ninto faeries', 'and the magic sack', 'the sack-holding kid', 'magic sack for sale', 'the witch and assistant', 'magic boy plays marbles again', 'the Powder'),
|
||||||
'Moon Pearl': (True, False, None, 0x1F, 200, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'),
|
'Moon Pearl': (True, False, None, 0x1F, 200, ' Bunny Link\n be\n gone!', 'and the jaw breaker', 'fortune-telling kid', 'lunar orb for sale', 'shrooms for moon rock', 'moon boy plays ball again', 'the Moon Pearl'),
|
||||||
'Cane of Somaria': (True, False, None, 0x15, 250, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'),
|
'Cane of Somaria': (True, False, None, 0x15, 250, 'I make blocks\nto hold down\nswitches!', 'and the red blocks', 'the block-making kid', 'block stick for sale', 'block stick for trade', 'cane boy makes blocks again', 'the Red Cane'),
|
||||||
'Fire Rod': (True, False, None, 0x07, 250, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'),
|
'Fire Rod': (True, False, None, 0x07, 250, 'I\'m the hot\nrod. I make\nthings burn!', 'and the flamethrower', 'fire-starting kid', 'rage rod for sale', 'fungus for rage-rod', 'firestarter boy burns again', 'the Fire Rod'),
|
||||||
'Flippers': (True, False, None, 0x1E, 250, 'fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the Flippers'),
|
'Flippers': (True, False, None, 0x1E, 250, 'Fancy a swim?', 'and the toewebs', 'the swimming kid', 'finger webs for sale', 'shrooms let you swim', 'swimming boy swims again', 'the Flippers'),
|
||||||
'Ice Rod': (True, False, None, 0x08, 250, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'),
|
'Ice Rod': (True, False, None, 0x08, 250, 'I\'m the cold\nrod. I make\nthings freeze!', 'and the freeze ray', 'the ice-bending kid', 'freeze ray for sale', 'fungus for ice-rod', 'ice-cube boy freezes again', 'the Ice Rod'),
|
||||||
'Titans Mitts': (True, False, None, 0x1C, 200, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'),
|
'Titans Mitts': (True, False, None, 0x1C, 200, 'Now you can\nlift heavy\nstuff!', 'and the golden glove', 'body-building kid', 'carry glove for sale', 'fungus for bling-gloves', 'body-building boy has gold again', 'the Mitts'),
|
||||||
'Bombos': (True, False, None, 0x0F, 100, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'),
|
'Bombos': (True, False, None, 0x0F, 100, 'Burn, baby,\nburn! Fear my\nring of fire!', 'and the swirly coin', 'coin-collecting kid', 'swirly coin for sale', 'shrooms for swirly-coin', 'medallion boy melts room again', 'Bombos'),
|
||||||
'Ether': (True, False, None, 0x10, 100, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'),
|
'Ether': (True, False, None, 0x10, 100, 'This magic\ncoin freezes\neverything!', 'and the bolt coin', 'coin-collecting kid', 'bolt coin for sale', 'shrooms for bolt-coin', 'medallion boy sees floor again', 'Ether'),
|
||||||
'Quake': (True, False, None, 0x11, 100, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'),
|
'Quake': (True, False, None, 0x11, 100, 'Maxing out the\nRichter scale\nis what I do!', 'and the wavy coin', 'coin-collecting kid', 'wavy coin for sale', 'shrooms for wavy-coin', 'medallion boy shakes dirt again', 'Quake'),
|
||||||
'Bottle': (True, False, None, 0x16, 50, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a Bottle'),
|
'Bottle': (True, False, None, 0x16, 50, 'Now you can\nstore potions\nand stuff!', 'and the terrarium', 'the terrarium kid', 'terrarium for sale', 'special promotion', 'bottle boy has terrarium again', 'a bottle'),
|
||||||
'Bottle (Red Potion)': (True, False, None, 0x2B, 70, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a Bottle'),
|
'Bottle (Red Potion)': (True, False, None, 0x2B, 70, 'Hearty red goop!', 'and the red goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has red goo again', 'a bottle'),
|
||||||
'Bottle (Green Potion)': (True, False, None, 0x2C, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a Bottle'),
|
'Bottle (Green Potion)': (True, False, None, 0x2C, 60, 'Refreshing green goop!', 'and the green goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has green goo again', 'a bottle'),
|
||||||
'Bottle (Blue Potion)': (True, False, None, 0x2D, 80, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a Bottle'),
|
'Bottle (Blue Potion)': (True, False, None, 0x2D, 80, 'Delicious blue goop!', 'and the blue goo', 'the liquid kid', 'potion for sale', 'free samples', 'bottle boy has blue goo again', 'a bottle'),
|
||||||
'Bottle (Fairy)': (True, False, None, 0x3D, 70, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a Bottle'),
|
'Bottle (Fairy)': (True, False, None, 0x3D, 70, 'Save me and I will revive you', 'and the captive', 'the tingle kid', 'hostage for sale', 'fairy dust and shrooms', 'bottle boy has friend again', 'a bottle'),
|
||||||
'Bottle (Bee)': (True, False, None, 0x3C, 50, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a Bottle'),
|
'Bottle (Bee)': (True, False, None, 0x3C, 50, 'I will sting your foes a few times', 'and the sting buddy', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has mad bee again', 'a bottle'),
|
||||||
'Bottle (Good Bee)': (True, False, None, 0x48, 60, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a Bottle'),
|
'Bottle (Good Bee)': (True, False, None, 0x48, 60, 'I will sting your foes a whole lot!', 'and the sparkle sting', 'the beekeeper kid', 'insect for sale', 'shroom pollenation', 'bottle boy has beetor again', 'a bottle'),
|
||||||
'Master Sword': (True, False, 'Sword', 0x50, 100, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
|
'Master Sword': (True, False, 'Sword', 0x50, 100, 'I beat barries and pigs alike', 'and the master sword', 'sword-wielding kid', 'glow sword for sale', 'fungus for blue slasher', 'sword boy fights again', 'the Master Sword'),
|
||||||
'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'),
|
'Tempered Sword': (True, False, 'Sword', 0x02, 150, 'I stole the\nblacksmith\'s\njob!', 'the tempered sword', 'sword-wielding kid', 'flame sword for sale', 'fungus for red slasher', 'sword boy fights again', 'the Tempered Sword'),
|
||||||
'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Fighter Sword'),
|
'Fighter Sword': (True, False, 'Sword', 0x49, 50, 'A pathetic\nsword rests\nhere!', 'the tiny sword', 'sword-wielding kid', 'tiny sword for sale', 'fungus for tiny slasher', 'sword boy fights again', 'the Fighter Sword'),
|
||||||
|
'Sword and Shield': (True, False, 'Sword', 0x00, 'An uncle\nsword rests\nhere!', 'the sword and shield', 'sword and shield-wielding kid', 'training set for sale', 'fungus for training set', 'sword and shield boy fights again', 'the small sword and shield'),
|
||||||
'Golden Sword': (True, False, 'Sword', 0x03, 200, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'),
|
'Golden Sword': (True, False, 'Sword', 0x03, 200, 'The butter\nsword rests\nhere!', 'and the butter sword', 'sword-wielding kid', 'butter for sale', 'cap churned to butter', 'sword boy fights again', 'the Golden Sword'),
|
||||||
'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'a better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
|
'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'A better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
|
||||||
'Progressive Glove': (True, False, None, 0x61, 150, 'a way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
|
'Progressive Glove': (True, False, None, 0x61, 150, 'A way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
|
||||||
'Silver Arrows': (True, False, None, 0x58, 100, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the Silver Arrows'),
|
'Silver Arrows': (True, False, None, 0x58, 100, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the Silver Arrows'),
|
||||||
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01], 999, None, None, None, None, None, None, None),
|
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x01, 0x08], 999, None, None, None, None, None, None, None),
|
||||||
'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02], 999, None, None, None, None, None, None, None),
|
'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x02, 0x09], 999, None, None, None, None, None, None, None),
|
||||||
'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03], 999, None, None, None, None, None, None, None),
|
'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x03, 0x0a], 999, None, None, None, None, None, None, None),
|
||||||
'Triforce': (True, False, None, 0x6A, 777, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'),
|
'Triforce': (True, False, None, 0x6A, 777, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'),
|
||||||
'Power Star': (True, False, None, 0x6B, 100, 'a small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'),
|
'Power Star': (True, False, None, 0x6B, 100, 'A small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'),
|
||||||
'Triforce Piece': (True, False, None, 0x6C, 100, 'a small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce Piece'),
|
'Triforce Piece': (True, False, None, 0x6C, 100, 'A small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce piece'),
|
||||||
'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06], 999, None, None, None, None, None, None, None),
|
'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x06, 0x01], 999, None, None, None, None, None, None, None),
|
||||||
'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x06], 999, None, None, None, None, None, None, None),
|
'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x06, 0x02], 999, None, None, None, None, None, None, None),
|
||||||
'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06], 999, None, None, None, None, None, None, None),
|
'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x06, 0x03], 999, None, None, None, None, None, None, None),
|
||||||
'Crystal 4': (True, False, 'Crystal', [0x20, 0x34, 0x64, 0x40, 0x6D, 0x06], 999, None, None, None, None, None, None, None),
|
'Crystal 4': (True, False, 'Crystal', [0x20, 0x34, 0x64, 0x40, 0x6D, 0x06, 0x04], 999, None, None, None, None, None, None, None),
|
||||||
'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x06], 999, None, None, None, None, None, None, None),
|
'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x06, 0x05], 999, None, None, None, None, None, None, None),
|
||||||
'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06], 999, None, None, None, None, None, None, None),
|
'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06, 0x06], 999, None, None, None, None, None, None, None),
|
||||||
'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x06], 999, None, None, None, None, None, None, None),
|
'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x06, 0x07], 999, None, None, None, None, None, None, None),
|
||||||
'Single Arrow': (False, False, None, 0x43, 3, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
|
'Single Arrow': (False, False, None, 0x43, 3, 'A lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
|
||||||
'Arrows (10)': (False, False, None, 0x44, 30, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'ten arrows'),
|
'Arrows (10)': (False, False, None, 0x44, 30, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'ten arrows'),
|
||||||
'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
||||||
'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
'Arrow Upgrade (+5)': (False, False, None, 0x53, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
|
||||||
'Single Bomb': (False, False, None, 0x27, 5, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
|
'Single Bomb': (False, False, None, 0x27, 5, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
|
||||||
|
'Arrows (5)': (False, False, None, 0x5A, 15, 'This will give\nyou five shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'five arrows'),
|
||||||
|
'Small Magic': (False, False, None, 0x45, 5, 'A bit of magic', 'and the bit of magic', 'bit-o-magic kid', 'magic bit for sale', 'fungus for magic', 'magic boy conjures again', 'a bit of magic'),
|
||||||
|
'Big Magic': (False, False, None, 0x5A, 40, 'A lot of magic', 'and lots of magic', 'lot-o-magic kid', 'magic refill for sale', 'fungus for magic', 'magic boy conjures again', 'a magic refill'),
|
||||||
|
'Chicken': (False, False, None, 0x5A, 999, 'Cucco of Legend', 'and the legendary cucco', 'chicken kid', 'fried chicken for sale', 'fungus for chicken', 'cucco boy clucks again', 'a cucco'),
|
||||||
'Bombs (3)': (False, False, None, 0x28, 15, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
|
'Bombs (3)': (False, False, None, 0x28, 15, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
|
||||||
'Bombs (10)': (False, False, None, 0x31, 50, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),
|
'Bombs (10)': (False, False, None, 0x31, 50, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),
|
||||||
'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
|
'Bomb Upgrade (+10)': (False, False, None, 0x52, 100, 'Increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
|
||||||
'Bomb Upgrade (+5)': (False, False, None, 0x51, 100, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
|
'Bomb Upgrade (+5)': (False, False, None, 0x51, 100, 'Increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
|
||||||
'Blue Mail': (False, True, None, 0x22, 50, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the Blue Mail'),
|
'Blue Mail': (False, True, None, 0x22, 50, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the Blue Mail'),
|
||||||
'Red Mail': (False, True, None, 0x23, 100, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the Red Mail'),
|
'Red Mail': (False, True, None, 0x23, 100, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the Red Mail'),
|
||||||
'Progressive Armor': (False, True, None, 0x60, 50, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'),
|
'Progressive Armor': (False, True, None, 0x60, 50, 'Time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'),
|
||||||
'Blue Boomerang': (True, False, None, 0x0C, 50, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the Blue Boomerang'),
|
'Blue Boomerang': (True, False, None, 0x0C, 50, 'No matter what\nyou do, blue\nreturns to you', 'and the bluemarang', 'the bat-throwing kid', 'bent stick for sale', 'fungus for puma-stick', 'throwing boy plays fetch again', 'the Blue Boomerang'),
|
||||||
'Red Boomerang': (True, False, None, 0x2A, 50, 'No matter what\nyou do, red\nreturns to you', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again', 'the Red Boomerang'),
|
'Red Boomerang': (True, False, None, 0x2A, 50, 'No matter what\nyou do, red\nreturns to you', 'and the badmarang', 'the bat-throwing kid', 'air foil for sale', 'fungus for return-stick', 'magical boy plays fetch again', 'the Red Boomerang'),
|
||||||
'Blue Shield': (False, True, None, 0x04, 50, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'the Blue Shield'),
|
'Blue Shield': (False, True, None, 0x04, 50, 'Now you can\ndefend against\npebbles!', 'and the stone blocker', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a Blue Shield'),
|
||||||
'Red Shield': (False, True, None, 0x05, 500, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'the Red Shield'),
|
'Red Shield': (False, True, None, 0x05, 500, 'Now you can\ndefend against\nfireballs!', 'and the shot blocker', 'shield-wielding kid', 'fire shield for sale', 'fungus for fire shield', 'shield boy defends again', 'a Red Shield'),
|
||||||
'Mirror Shield': (True, False, None, 0x06, 200, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'the Mirror Shield'),
|
'Mirror Shield': (True, False, None, 0x06, 200, 'Now you can\ndefend against\nlasers!', 'and the laser blocker', 'shield-wielding kid', 'face shield for sale', 'fungus for face shield', 'shield boy defends again', 'the Mirror Shield'),
|
||||||
'Progressive Shield': (True, False, None, 0x5F, 50, 'have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'),
|
'Progressive Shield': (True, False, None, 0x5F, 50, 'Have a better\nblocker in\nfront of you', 'and the new shield', 'shield-wielding kid', 'shield for sale', 'fungus for shield', 'shield boy defends again', 'a shield'),
|
||||||
'Bug Catching Net': (True, False, None, 0x21, 50, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again', 'the Bug Net'),
|
'Bug Catching Net': (True, False, None, 0x21, 50, 'Let\'s catch\nsome bees and\nfaeries!', 'and the bee catcher', 'the bug-catching kid', 'stick web for sale', 'fungus for butterflies', 'wrong boy catches bees again', 'the Bug Net'),
|
||||||
'Cane of Byrna': (True, False, None, 0x18, 50, 'Use this to\nbecome\ninvincible!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'the Blue Cane'),
|
'Cane of Byrna': (True, False, None, 0x18, 50, 'Use this to\nbecome\ninvincible!', 'and the bad cane', 'the spark-making kid', 'spark stick for sale', 'spark-stick for trade', 'cane boy encircles again', 'the Blue Cane'),
|
||||||
'Boss Heart Container': (False, False, None, 0x3E, 40, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
|
'Boss Heart Container': (False, True, None, 0x3E, 40, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
|
||||||
'Sanctuary Heart Container': (False, False, None, 0x3F, 50, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
|
'Sanctuary Heart Container': (False, True, None, 0x3F, 50, 'Maximum health\nincreased!\nYeah!', 'and the full heart', 'the life-giving kid', 'love for sale', 'fungus for life', 'life boy feels love again', 'a heart'),
|
||||||
'Piece of Heart': (False, False, None, 0x17, 10, 'Just a little\npiece of love!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart piece'),
|
'Piece of Heart': (False, False, None, 0x17, 10, 'Just a little\npiece of love!', 'and the broken heart', 'the life-giving kid', 'little love for sale', 'fungus for life', 'life boy feels some love again', 'a heart piece'),
|
||||||
'Rupee (1)': (False, False, None, 0x34, 0, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a green rupee'),
|
'Rupee (1)': (False, False, None, 0x34, 0, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a green rupee'),
|
||||||
'Rupees (5)': (False, False, None, 0x35, 2, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a blue rupee'),
|
'Rupees (5)': (False, False, None, 0x35, 2, 'Just pocket\nchange. Move\nright along.', 'the pocket change', 'poverty-struck kid', 'life lesson for sale', 'buying cheap drugs', 'destitute boy has snack again', 'a blue rupee'),
|
||||||
@@ -104,12 +109,12 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
|
|||||||
'Rupees (50)': (False, False, None, 0x41, 25, 'A rupee pile!\nOkay?', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again', 'fifty rupees'),
|
'Rupees (50)': (False, False, None, 0x41, 25, 'A rupee pile!\nOkay?', 'and the rupee pile', 'the well-off kid', 'life lesson for sale', 'buying okay drugs', 'destitute boy has dinner again', 'fifty rupees'),
|
||||||
'Rupees (100)': (False, False, None, 0x40, 50, 'A rupee stash!\nHell yeah!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again', 'one hundred rupees'),
|
'Rupees (100)': (False, False, None, 0x40, 50, 'A rupee stash!\nHell yeah!', 'and the rupee stash', 'the kind-of-rich kid', 'life lesson for sale', 'buying good drugs', 'affluent boy goes drinking again', 'one hundred rupees'),
|
||||||
'Rupees (300)': (False, False, None, 0x46, 150, 'A rupee hoard!\nHell yeah!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again', 'three hundred rupees'),
|
'Rupees (300)': (False, False, None, 0x46, 150, 'A rupee hoard!\nHell yeah!', 'and the rupee hoard', 'the really-rich kid', 'life lesson for sale', 'buying the best drugs', 'fat-cat boy is rich again', 'three hundred rupees'),
|
||||||
'Rupoor': (False, False, None, 0x59, 0, 'a debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'),
|
'Rupoor': (False, False, None, 0x59, 0, 'A debt collector', 'and the toll-booth', 'the toll-booth kid', 'double loss for sale', 'witch stole your rupees', 'affluent boy steals rupees', 'a rupoor'),
|
||||||
'Red Clock': (False, True, None, 0x5B, 0, 'a waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'),
|
'Red Clock': (False, True, None, 0x5B, 0, 'A waste of time', 'the ruby clock', 'the ruby-time kid', 'red time for sale', 'for ruby time', 'moment boy travels time again', 'a red clock'),
|
||||||
'Blue Clock': (False, True, None, 0x5C, 50, 'a bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'),
|
'Blue Clock': (False, True, None, 0x5C, 50, 'A bit of time', 'the sapphire clock', 'sapphire-time kid', 'blue time for sale', 'for sapphire time', 'moment boy time travels again', 'a blue clock'),
|
||||||
'Green Clock': (False, True, None, 0x5D, 200, 'a lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'),
|
'Green Clock': (False, True, None, 0x5D, 200, 'A lot of time', 'the emerald clock', 'the emerald-time kid', 'green time for sale', 'for emerald time', 'moment boy adjusts time again', 'a red clock'),
|
||||||
'Single RNG': (False, True, None, 0x62, 300, 'something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'a new mystery'),
|
'Single RNG': (False, True, None, 0x62, 300, 'Something you don\'t yet have', None, None, None, None, 'unknown boy somethings again', 'a new mystery'),
|
||||||
'Multi RNG': (False, True, None, 0x63, 100, 'something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'),
|
'Multi RNG': (False, True, None, 0x63, 100, 'Something you may already have', None, None, None, None, 'unknown boy somethings again', 'a total mystery'),
|
||||||
'Magic Upgrade (1/2)': (True, False, None, 0x4E, 50, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
|
'Magic Upgrade (1/2)': (True, False, None, 0x4E, 50, 'Your magic\npower has been\ndoubled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Half Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
|
||||||
'Magic Upgrade (1/4)': (True, False, None, 0x4F, 100, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
|
'Magic Upgrade (1/4)': (True, False, None, 0x4F, 100, 'Your magic\npower has been\nquadrupled!', 'and the spell power', 'the magic-saving kid', 'wizardry for sale', 'mekalekahi mekahiney ho', 'magic boy saves magic again', 'Quarter Magic'), # can be required to beat mothula in an open seed in very very rare circumstance
|
||||||
'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 40, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Eastern Palace'),
|
'Small Key (Eastern Palace)': (False, False, 'SmallKey', 0xA2, 40, 'A small key to Armos Knights', 'and the key', 'the unlocking kid', 'keys for sale', 'unlock the fungus', 'key boy opens door again', 'a Small Key to Eastern Palace'),
|
||||||
@@ -188,6 +193,7 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
|
|||||||
'Maiden Rescued': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
'Maiden Rescued': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
||||||
'Maiden Unmasked': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
'Maiden Unmasked': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
||||||
'Convenient Block': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
'Convenient Block': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
||||||
|
'Hidden Pits': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
||||||
'Zelda Herself': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
'Zelda Herself': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
||||||
'Zelda Delivered': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
'Zelda Delivered': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from Bosses import place_bosses
|
|||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from KeyDoorShuffle import validate_key_placement
|
from KeyDoorShuffle import validate_key_placement
|
||||||
from OverworldGlitchRules import create_owg_connections
|
from OverworldGlitchRules import create_owg_connections
|
||||||
from PotShuffle import shuffle_pots
|
from PotShuffle import shuffle_pots, shuffle_pot_switches
|
||||||
from Regions import create_regions, create_shops, mark_light_world_regions, mark_dark_world_regions, create_dungeon_regions, adjust_locations
|
from Regions import create_regions, create_shops, mark_light_world_regions, mark_dark_world_regions, create_dungeon_regions, adjust_locations
|
||||||
from OWEdges import create_owedges
|
from OWEdges import create_owedges
|
||||||
from OverworldShuffle import link_overworld, update_world_regions, create_flute_exits
|
from OverworldShuffle import link_overworld, update_world_regions, create_flute_exits
|
||||||
@@ -24,15 +24,15 @@ from DoorShuffle import link_doors, connect_portal, link_doors_prep
|
|||||||
from RoomData import create_rooms
|
from RoomData import create_rooms
|
||||||
from Rules import set_rules
|
from Rules import set_rules
|
||||||
from Dungeons import create_dungeons
|
from Dungeons import create_dungeons
|
||||||
from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dungeons_restrictive
|
from Fill import distribute_items_restrictive, promote_dungeon_items, fill_dungeons_restrictive, ensure_good_pots
|
||||||
from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops
|
from Fill import sell_potions, sell_keys, balance_multiworld_progression, balance_money_progression, lock_shop_locations, set_prize_drops
|
||||||
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
|
from ItemList import generate_itempool, difficulties, fill_prizes, customize_shops
|
||||||
from Utils import output_path, parse_player_names
|
from Utils import output_path, parse_player_names
|
||||||
|
|
||||||
from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config
|
from source.item.FillUtil import create_item_pool_config, massage_item_pool, district_item_pool_config
|
||||||
|
from source.tools.BPS import create_bps_from_data
|
||||||
|
|
||||||
|
__version__ = '1.0.1.0-u'
|
||||||
__version__ = '1.0.0.3-u'
|
|
||||||
|
|
||||||
from source.classes.BabelFish import BabelFish
|
from source.classes.BabelFish import BabelFish
|
||||||
|
|
||||||
@@ -100,18 +100,21 @@ def main(args, seed=None, fish=None):
|
|||||||
world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)}
|
world.intensity = {player: random.randint(1, 3) if args.intensity[player] == 'random' else int(args.intensity[player]) for player in range(1, world.players + 1)}
|
||||||
world.experimental = args.experimental.copy()
|
world.experimental = args.experimental.copy()
|
||||||
world.dungeon_counters = args.dungeon_counters.copy()
|
world.dungeon_counters = args.dungeon_counters.copy()
|
||||||
world.potshuffle = args.shufflepots.copy()
|
|
||||||
world.fish = fish
|
world.fish = fish
|
||||||
world.shopsanity = args.shopsanity.copy()
|
world.shopsanity = args.shopsanity.copy()
|
||||||
world.keydropshuffle = args.keydropshuffle.copy()
|
world.dropshuffle = args.dropshuffle.copy()
|
||||||
|
world.pottery = args.pottery.copy()
|
||||||
|
world.potshuffle = args.shufflepots.copy()
|
||||||
world.mixed_travel = args.mixed_travel.copy()
|
world.mixed_travel = args.mixed_travel.copy()
|
||||||
world.standardize_palettes = args.standardize_palettes.copy()
|
world.standardize_palettes = args.standardize_palettes.copy()
|
||||||
world.treasure_hunt_count = args.triforce_goal.copy()
|
world.treasure_hunt_count = {k: int(v) for k, v in args.triforce_goal.items()}
|
||||||
world.treasure_hunt_total = args.triforce_pool.copy()
|
world.treasure_hunt_total = {k: int(v) for k, v in args.triforce_pool.items()}
|
||||||
world.shufflelinks = args.shufflelinks.copy()
|
world.shufflelinks = args.shufflelinks.copy()
|
||||||
world.pseudoboots = args.pseudoboots.copy()
|
world.pseudoboots = args.pseudoboots.copy()
|
||||||
world.overworld_map = args.overworld_map.copy()
|
world.overworld_map = args.overworld_map.copy()
|
||||||
world.restrict_boss_items = args.restrict_boss_items.copy()
|
world.restrict_boss_items = args.restrict_boss_items.copy()
|
||||||
|
world.collection_rate = args.collection_rate.copy()
|
||||||
|
world.colorizepots = args.colorizepots.copy()
|
||||||
|
|
||||||
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
world.rom_seeds = {player: random.randint(0, 999999999) for player in range(1, world.players + 1)}
|
||||||
|
|
||||||
@@ -147,14 +150,17 @@ def main(args, seed=None, fish=None):
|
|||||||
if hasattr(world,"escape_assist") and player in world.escape_assist:
|
if hasattr(world,"escape_assist") and player in world.escape_assist:
|
||||||
world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it
|
world.escape_assist[player].append('bombs') # enemized escape assumes infinite bombs available and will likely be unbeatable without it
|
||||||
|
|
||||||
|
if args.usestartinventory[player]:
|
||||||
for tok in filter(None, args.startinventory[player].split(',')):
|
for tok in filter(None, args.startinventory[player].split(',')):
|
||||||
item = ItemFactory(tok.strip(), player)
|
item = ItemFactory(tok.strip(), player)
|
||||||
if item:
|
if item:
|
||||||
world.push_precollected(item)
|
world.push_precollected(item)
|
||||||
|
|
||||||
if args.create_spoiler and not args.jsonout:
|
if args.create_spoiler and not args.jsonout:
|
||||||
logger.info(world.fish.translate("cli","cli","patching.spoiler"))
|
logger.info(world.fish.translate("cli", "cli", "create.meta"))
|
||||||
world.spoiler.meta_to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
world.spoiler.meta_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
|
||||||
|
if args.mystery and not args.suppress_meta:
|
||||||
|
world.spoiler.mystery_meta_to_file(output_path(f'{outfilebase}_meta.txt'))
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
create_regions(world, player)
|
create_regions(world, player)
|
||||||
@@ -173,7 +179,10 @@ def main(args, seed=None, fish=None):
|
|||||||
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
|
logger.info(world.fish.translate("cli", "cli", "shuffling.pots"))
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if world.potshuffle[player]:
|
if world.potshuffle[player]:
|
||||||
|
if world.pottery[player] in ['none', 'cave', 'keys', 'cavekeys']:
|
||||||
shuffle_pots(world, player)
|
shuffle_pots(world, player)
|
||||||
|
else:
|
||||||
|
shuffle_pot_switches(world, player)
|
||||||
|
|
||||||
logger.info(world.fish.translate("cli","cli","shuffling.overworld"))
|
logger.info(world.fish.translate("cli","cli","shuffling.overworld"))
|
||||||
|
|
||||||
@@ -273,11 +282,12 @@ def main(args, seed=None, fish=None):
|
|||||||
customize_shops(world, player)
|
customize_shops(world, player)
|
||||||
if args.algorithm in ['balanced', 'equitable']:
|
if args.algorithm in ['balanced', 'equitable']:
|
||||||
balance_money_progression(world)
|
balance_money_progression(world)
|
||||||
|
ensure_good_pots(world, True)
|
||||||
|
|
||||||
rom_names = []
|
rom_names = []
|
||||||
jsonout = {}
|
jsonout = {}
|
||||||
enemized = False
|
enemized = False
|
||||||
if not args.suppress_rom:
|
if not args.suppress_rom or args.bps:
|
||||||
logger.info(world.fish.translate("cli","cli","patching.rom"))
|
logger.info(world.fish.translate("cli","cli","patching.rom"))
|
||||||
for team in range(world.teams):
|
for team in range(world.teams):
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
@@ -303,7 +313,7 @@ def main(args, seed=None, fish=None):
|
|||||||
logging.warning(enemizerMsg)
|
logging.warning(enemizerMsg)
|
||||||
raise EnemizerError(enemizerMsg)
|
raise EnemizerError(enemizerMsg)
|
||||||
|
|
||||||
patch_rom(world, rom, player, team, enemized, bool(args.outputname))
|
patch_rom(world, rom, player, team, enemized, bool(args.mystery))
|
||||||
|
|
||||||
if args.race:
|
if args.race:
|
||||||
patch_race_rom(rom)
|
patch_race_rom(rom)
|
||||||
@@ -314,7 +324,7 @@ def main(args, seed=None, fish=None):
|
|||||||
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
|
apply_rom_settings(rom, args.heartbeep[player], args.heartcolor[player], args.quickswap[player],
|
||||||
args.fastmenu[player], args.disablemusic[player], args.sprite[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])
|
args.shuffle_sfx[player], args.msu_resume[player])
|
||||||
|
|
||||||
if args.jsonout:
|
if args.jsonout:
|
||||||
jsonout[f'patch_t{team}_p{player}'] = rom.patches
|
jsonout[f'patch_t{team}_p{player}'] = rom.patches
|
||||||
@@ -325,7 +335,14 @@ def main(args, seed=None, fish=None):
|
|||||||
if world.players > 1 or world.teams > 1:
|
if world.players > 1 or world.teams > 1:
|
||||||
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else ''
|
outfilepname += f"_{world.player_names[player][team].replace(' ', '_')}" if world.player_names[player][team] != 'Player %d' % player else ''
|
||||||
outfilesuffix = f'_{Settings.make_code(world, player)}' if not args.outputname else ''
|
outfilesuffix = f'_{Settings.make_code(world, player)}' if not args.outputname else ''
|
||||||
rom.write_to_file(output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc'))
|
if args.bps:
|
||||||
|
patchfile = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.bps')
|
||||||
|
patch = create_bps_from_data(LocalRom(args.rom, patch=False).buffer, rom.buffer)
|
||||||
|
with open(patchfile, 'wb') as stream:
|
||||||
|
stream.write(patch.binary_ba)
|
||||||
|
if not args.suppress_rom:
|
||||||
|
sfc_file = output_path(f'{outfilebase}{outfilepname}{outfilesuffix}.sfc')
|
||||||
|
rom.write_to_file(sfc_file)
|
||||||
|
|
||||||
if world.players > 1:
|
if world.players > 1:
|
||||||
multidata = zlib.compress(json.dumps({"names": parsed_names,
|
multidata = zlib.compress(json.dumps({"names": parsed_names,
|
||||||
@@ -341,9 +358,13 @@ def main(args, seed=None, fish=None):
|
|||||||
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
|
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
|
||||||
f.write(multidata)
|
f.write(multidata)
|
||||||
|
|
||||||
|
if args.mystery and not args.suppress_meta:
|
||||||
|
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_meta.txt'))
|
||||||
|
elif args.create_spoiler and not args.jsonout:
|
||||||
|
world.spoiler.hashes_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
|
||||||
if args.create_spoiler and not args.jsonout:
|
if args.create_spoiler and not args.jsonout:
|
||||||
logger.info(world.fish.translate("cli", "cli", "patching.spoiler"))
|
logger.info(world.fish.translate("cli", "cli", "patching.spoiler"))
|
||||||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
world.spoiler.to_file(output_path(f'{outfilebase}_Spoiler.txt'))
|
||||||
|
|
||||||
if not args.skip_playthrough:
|
if not args.skip_playthrough:
|
||||||
logger.info(world.fish.translate("cli","cli","calc.playthrough"))
|
logger.info(world.fish.translate("cli","cli","calc.playthrough"))
|
||||||
@@ -356,8 +377,8 @@ def main(args, seed=None, fish=None):
|
|||||||
if args.jsonout:
|
if args.jsonout:
|
||||||
with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile:
|
with open(output_path('%s_Spoiler.json' % outfilebase), 'w') as outfile:
|
||||||
outfile.write(world.spoiler.to_json())
|
outfile.write(world.spoiler.to_json())
|
||||||
else:
|
elif world.players > 1 or world.logic[1] != "nologic":
|
||||||
world.spoiler.playthru_to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
world.spoiler.playthrough_to_file(output_path(f'{outfilebase}_Spoiler.txt'))
|
||||||
|
|
||||||
YES = world.fish.translate("cli","cli","yes")
|
YES = world.fish.translate("cli","cli","yes")
|
||||||
NO = world.fish.translate("cli","cli","no")
|
NO = world.fish.translate("cli","cli","no")
|
||||||
@@ -377,7 +398,7 @@ def main(args, seed=None, fish=None):
|
|||||||
return world
|
return world
|
||||||
|
|
||||||
|
|
||||||
def copy_world(world):
|
def copy_world(world, partial_copy=False):
|
||||||
# ToDo: Not good yet
|
# ToDo: Not good yet
|
||||||
ret = World(world.players, world.owShuffle, world.owCrossed, world.owMixed, world.shuffle, world.doorShuffle, world.logic, world.mode, world.swords,
|
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.difficulty, world.difficulty_adjustments, world.timer, world.progressive, world.goal, world.algorithm,
|
||||||
@@ -426,7 +447,9 @@ def copy_world(world):
|
|||||||
ret.intensity = world.intensity.copy()
|
ret.intensity = world.intensity.copy()
|
||||||
ret.experimental = world.experimental.copy()
|
ret.experimental = world.experimental.copy()
|
||||||
ret.shopsanity = world.shopsanity.copy()
|
ret.shopsanity = world.shopsanity.copy()
|
||||||
ret.keydropshuffle = world.keydropshuffle.copy()
|
ret.dropshuffle = world.dropshuffle.copy()
|
||||||
|
ret.pottery = world.pottery.copy()
|
||||||
|
ret.potshuffle = world.potshuffle.copy()
|
||||||
ret.mixed_travel = world.mixed_travel.copy()
|
ret.mixed_travel = world.mixed_travel.copy()
|
||||||
ret.standardize_palettes = world.standardize_palettes.copy()
|
ret.standardize_palettes = world.standardize_palettes.copy()
|
||||||
ret.owswaps = world.owswaps.copy()
|
ret.owswaps = world.owswaps.copy()
|
||||||
@@ -434,8 +457,6 @@ def copy_world(world):
|
|||||||
ret.prizes = world.prizes.copy()
|
ret.prizes = world.prizes.copy()
|
||||||
ret.restrict_boss_items = world.restrict_boss_items.copy()
|
ret.restrict_boss_items = world.restrict_boss_items.copy()
|
||||||
|
|
||||||
ret.exp_cache = world.exp_cache.copy()
|
|
||||||
|
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
create_regions(ret, player)
|
create_regions(ret, player)
|
||||||
update_world_regions(ret, player)
|
update_world_regions(ret, player)
|
||||||
@@ -447,6 +468,9 @@ def copy_world(world):
|
|||||||
if world.logic[player] in ('owglitches', 'nologic'):
|
if world.logic[player] in ('owglitches', 'nologic'):
|
||||||
create_owg_connections(ret, player)
|
create_owg_connections(ret, player)
|
||||||
|
|
||||||
|
# there are region references here they must be migrated to preserve integrity
|
||||||
|
# ret.exp_cache = world.exp_cache.copy()
|
||||||
|
|
||||||
copy_dynamic_regions_and_locations(world, ret)
|
copy_dynamic_regions_and_locations(world, ret)
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
if world.mode[player] == 'standard':
|
if world.mode[player] == 'standard':
|
||||||
@@ -495,6 +519,7 @@ def copy_world(world):
|
|||||||
new_location.access_rule = lambda state: True
|
new_location.access_rule = lambda state: True
|
||||||
new_location.item_rule = lambda state: True
|
new_location.item_rule = lambda state: True
|
||||||
new_location.forced_item = location.forced_item
|
new_location.forced_item = location.forced_item
|
||||||
|
new_location.pot = location.pot
|
||||||
|
|
||||||
# copy remaining itempool. No item in itempool should have an assigned location
|
# copy remaining itempool. No item in itempool should have an assigned location
|
||||||
for item in world.itempool:
|
for item in world.itempool:
|
||||||
@@ -519,6 +544,7 @@ def copy_world(world):
|
|||||||
ret.dungeon_layouts = world.dungeon_layouts
|
ret.dungeon_layouts = world.dungeon_layouts
|
||||||
ret.key_logic = world.key_logic
|
ret.key_logic = world.key_logic
|
||||||
ret.dungeon_portals = world.dungeon_portals
|
ret.dungeon_portals = world.dungeon_portals
|
||||||
|
if not partial_copy:
|
||||||
for player, portals in world.dungeon_portals.items():
|
for player, portals in world.dungeon_portals.items():
|
||||||
for portal in portals:
|
for portal in portals:
|
||||||
connect_portal(portal, ret, player)
|
connect_portal(portal, ret, player)
|
||||||
@@ -529,6 +555,10 @@ def copy_world(world):
|
|||||||
categorize_world_regions(ret, player)
|
categorize_world_regions(ret, player)
|
||||||
set_rules(ret, player)
|
set_rules(ret, player)
|
||||||
|
|
||||||
|
if partial_copy:
|
||||||
|
# undo some of the things that unintentionally affect the original world object
|
||||||
|
world.key_logic = {}
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
|
||||||
@@ -566,7 +596,7 @@ def create_playthrough(world):
|
|||||||
|
|
||||||
# get locations containing progress items
|
# get locations containing progress items
|
||||||
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
|
prog_locations = [location for location in world.get_filled_locations() if location.item.advancement]
|
||||||
optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop']
|
optional_locations = ['Trench 1 Switch', 'Trench 2 Switch', 'Ice Block Drop', 'Skull Star Tile']
|
||||||
state_cache = [None]
|
state_cache = [None]
|
||||||
collection_spheres = []
|
collection_spheres = []
|
||||||
state = CollectionState(world)
|
state = CollectionState(world)
|
||||||
@@ -672,9 +702,11 @@ def create_playthrough(world):
|
|||||||
|
|
||||||
old_world.spoiler.paths = dict()
|
old_world.spoiler.paths = dict()
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
|
if world.logic[player] != 'nologic':
|
||||||
old_world.spoiler.paths.update({location.gen_name(): get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player})
|
old_world.spoiler.paths.update({location.gen_name(): get_path(state, location.parent_region) for sphere in collection_spheres for location in sphere if location.player == player})
|
||||||
|
|
||||||
# we can finally output our playthrough
|
# we can finally output our playthrough
|
||||||
old_world.spoiler.playthrough = {"0": [str(item) for item in world.precollected_items if item.advancement]}
|
old_world.spoiler.playthrough = {"0": [str(item) for item in world.precollected_items if item.advancement]}
|
||||||
for i, sphere in enumerate(collection_spheres):
|
for i, sphere in enumerate(collection_spheres):
|
||||||
|
if world.logic[player] != 'nologic':
|
||||||
old_world.spoiler.playthrough[str(i + 1)] = {location.gen_name(): str(location.item) for location in sphere}
|
old_world.spoiler.playthrough[str(i + 1)] = {location.gen_name(): str(location.item) for location in sphere}
|
||||||
|
|||||||
+80
-16
@@ -8,8 +8,10 @@ import shlex
|
|||||||
import urllib.parse
|
import urllib.parse
|
||||||
import websockets
|
import websockets
|
||||||
|
|
||||||
|
from BaseClasses import PotItem, PotFlags
|
||||||
import Items
|
import Items
|
||||||
import Regions
|
import Regions
|
||||||
|
import PotShuffle
|
||||||
|
|
||||||
|
|
||||||
class ReceivedItem:
|
class ReceivedItem:
|
||||||
@@ -57,8 +59,13 @@ class Context:
|
|||||||
self.key_drop_mode = False
|
self.key_drop_mode = False
|
||||||
self.shop_mode = False
|
self.shop_mode = False
|
||||||
self.retro_mode = False
|
self.retro_mode = False
|
||||||
|
self.pottery_mode = False
|
||||||
|
self.mystery_mode = False
|
||||||
self.ignore_count = 0
|
self.ignore_count = 0
|
||||||
|
|
||||||
|
self.lookup_name_to_id = {}
|
||||||
|
self.lookup_id_to_name = {}
|
||||||
|
|
||||||
def color_code(*args):
|
def color_code(*args):
|
||||||
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
||||||
'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
|
'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
|
||||||
@@ -83,6 +90,12 @@ INGAME_MODES = {0x07, 0x09, 0x0b}
|
|||||||
SAVEDATA_START = WRAM_START + 0xF000
|
SAVEDATA_START = WRAM_START + 0xF000
|
||||||
SAVEDATA_SIZE = 0x500
|
SAVEDATA_SIZE = 0x500
|
||||||
|
|
||||||
|
POT_ITEMS_SRAM_START = WRAM_START + 0x016018 # 2 bytes per room
|
||||||
|
SPRITE_ITEMS_SRAM_START = WRAM_START + 0x016268 # 2 bytes per room
|
||||||
|
SHOP_SRAM_START = WRAM_START + 0x0164B8 # 2 bytes?
|
||||||
|
ITEM_SRAM_SIZE = 0x250
|
||||||
|
SHOP_SRAM_LEN = 0x29 # 41 tracked items
|
||||||
|
|
||||||
RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes
|
RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes
|
||||||
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
||||||
RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
|
RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
|
||||||
@@ -92,11 +105,9 @@ SCOUT_LOCATION_ADDR = SAVEDATA_START + 0x4D7 # 1 byte
|
|||||||
SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte
|
SCOUTREPLY_LOCATION_ADDR = SAVEDATA_START + 0x4D8 # 1 byte
|
||||||
SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte
|
SCOUTREPLY_ITEM_ADDR = SAVEDATA_START + 0x4D9 # 1 byte
|
||||||
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
|
SCOUTREPLY_PLAYER_ADDR = SAVEDATA_START + 0x4DA # 1 byte
|
||||||
SHOP_ADDR = SAVEDATA_START + 0x302 # 2 bytes?
|
|
||||||
DYNAMIC_TOTAL_ADDR = SAVEDATA_START + 0x33E # 2 bytes
|
DYNAMIC_TOTAL_ADDR = SAVEDATA_START + 0x33E # 2 bytes
|
||||||
MODE_FLAGS = SAVEDATA_START + 0x33D # 1 byte
|
MODE_FLAGS = SAVEDATA_START + 0x33D # 1 byte
|
||||||
|
|
||||||
SHOP_SRAM_LEN = 0x29 # 41 tracked items
|
|
||||||
location_shop_order = [Regions.shop_to_location_table.keys()] + [Regions.retro_shops.keys()]
|
location_shop_order = [Regions.shop_to_location_table.keys()] + [Regions.retro_shops.keys()]
|
||||||
location_shop_ids = {0x0112, 0x0110, 0x010F, 0x00FF, 0x011F, 0x0109, 0x0115}
|
location_shop_ids = {0x0112, 0x0110, 0x010F, 0x00FF, 0x011F, 0x0109, 0x0115}
|
||||||
|
|
||||||
@@ -349,6 +360,8 @@ location_table_misc = {'Bottle Merchant': (0x3c9, 0x2),
|
|||||||
'Purple Chest': (0x3c9, 0x10),
|
'Purple Chest': (0x3c9, 0x10),
|
||||||
"Link's Uncle": (0x3c6, 0x1),
|
"Link's Uncle": (0x3c6, 0x1),
|
||||||
'Hobo': (0x3c9, 0x1)}
|
'Hobo': (0x3c9, 0x1)}
|
||||||
|
location_table_pot_items = {}
|
||||||
|
location_table_sprite_items = {}
|
||||||
|
|
||||||
SNES_DISCONNECTED = 0
|
SNES_DISCONNECTED = 0
|
||||||
SNES_CONNECTING = 1
|
SNES_CONNECTING = 1
|
||||||
@@ -676,7 +689,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
|||||||
ctx.player_names = {p: n for p, n in args[1]}
|
ctx.player_names = {p: n for p, n in args[1]}
|
||||||
msgs = []
|
msgs = []
|
||||||
if ctx.locations_checked:
|
if ctx.locations_checked:
|
||||||
msgs.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
|
msgs.append(['LocationChecks', [ctx.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
|
||||||
if ctx.locations_scouted:
|
if ctx.locations_scouted:
|
||||||
msgs.append(['LocationScouts', list(ctx.locations_scouted)])
|
msgs.append(['LocationScouts', list(ctx.locations_scouted)])
|
||||||
if msgs:
|
if msgs:
|
||||||
@@ -689,7 +702,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
|||||||
elif start_index != len(ctx.items_received):
|
elif start_index != len(ctx.items_received):
|
||||||
sync_msg = [['Sync']]
|
sync_msg = [['Sync']]
|
||||||
if ctx.locations_checked:
|
if ctx.locations_checked:
|
||||||
sync_msg.append(['LocationChecks', [Regions.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
|
sync_msg.append(['LocationChecks', [ctx.lookup_name_to_id[loc] for loc in ctx.locations_checked]])
|
||||||
await send_msgs(ctx.socket, sync_msg)
|
await send_msgs(ctx.socket, sync_msg)
|
||||||
if start_index == len(ctx.items_received):
|
if start_index == len(ctx.items_received):
|
||||||
for item in items:
|
for item in items:
|
||||||
@@ -701,7 +714,7 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
|||||||
if location not in ctx.locations_info:
|
if location not in ctx.locations_info:
|
||||||
replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'}
|
replacements = {0xA2: 'Small Key', 0x9D: 'Big Key', 0x8D: 'Compass', 0x7D: 'Map'}
|
||||||
item_name = replacements.get(item, get_item_name_from_id(item))
|
item_name = replacements.get(item, get_item_name_from_id(item))
|
||||||
logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(Regions.lookup_id_to_name.keys())[location - 1]}")
|
logging.info(f"Saw {color(item_name, 'red', 'bold')} at {list(ctx.lookup_id_to_name.keys())[location - 1]}")
|
||||||
ctx.locations_info[location] = (item, player)
|
ctx.locations_info[location] = (item, player)
|
||||||
ctx.watcher_event.set()
|
ctx.watcher_event.set()
|
||||||
|
|
||||||
@@ -710,7 +723,8 @@ async def process_server_cmd(ctx : Context, cmd, args):
|
|||||||
item = color(get_item_name_from_id(item), 'cyan' if player_sent != ctx.slot else 'green')
|
item = color(get_item_name_from_id(item), 'cyan' if player_sent != ctx.slot else 'green')
|
||||||
player_sent = color(ctx.player_names[player_sent], 'yellow' if player_sent != ctx.slot else 'magenta')
|
player_sent = color(ctx.player_names[player_sent], 'yellow' if player_sent != ctx.slot else 'magenta')
|
||||||
player_recvd = color(ctx.player_names[player_recvd], 'yellow' if player_recvd != ctx.slot else 'magenta')
|
player_recvd = color(ctx.player_names[player_recvd], 'yellow' if player_recvd != ctx.slot else 'magenta')
|
||||||
logging.info('%s sent %s to %s (%s)' % (player_sent, item, player_recvd, get_location_name_from_address(location)))
|
location_name = get_location_name_from_address(ctx, location)
|
||||||
|
logging.info('%s sent %s to %s (%s)' % (player_sent, item, player_recvd, location_name))
|
||||||
|
|
||||||
if cmd == 'Print':
|
if cmd == 'Print':
|
||||||
logging.info(args)
|
logging.info(args)
|
||||||
@@ -779,10 +793,10 @@ async def console_loop(ctx : Context):
|
|||||||
for index, item in enumerate(ctx.items_received, 1):
|
for index, item in enumerate(ctx.items_received, 1):
|
||||||
logging.info('%s from %s (%s) (%d/%d in list)' % (
|
logging.info('%s from %s (%s) (%d/%d in list)' % (
|
||||||
color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||||
get_location_name_from_address(item.location), index, len(ctx.items_received)))
|
get_location_name_from_address(ctx, item.location), index, len(ctx.items_received)))
|
||||||
|
|
||||||
if command[0] == '/missing':
|
if command[0] == '/missing':
|
||||||
for location in [k for k, v in Regions.lookup_name_to_id.items()
|
for location in [k for k, v in ctx.lookup_name_to_id.items()
|
||||||
if type(v) is int and not filter_location(ctx, k)]:
|
if type(v) is int and not filter_location(ctx, k)]:
|
||||||
if location not in ctx.locations_checked:
|
if location not in ctx.locations_checked:
|
||||||
logging.info('Missing: ' + location)
|
logging.info('Missing: ' + location)
|
||||||
@@ -804,15 +818,19 @@ def get_item_name_from_id(code):
|
|||||||
return items[0] if items else f'Unknown item (ID:{code})'
|
return items[0] if items else f'Unknown item (ID:{code})'
|
||||||
|
|
||||||
|
|
||||||
def get_location_name_from_address(address):
|
def get_location_name_from_address(ctx, address):
|
||||||
if type(address) is str:
|
if type(address) is str:
|
||||||
return address
|
return address
|
||||||
|
|
||||||
return Regions.lookup_id_to_name.get(address, f'Unknown location (ID:{address})')
|
return ctx.lookup_id_to_name.get(address, f'Unknown location (ID:{address})')
|
||||||
|
|
||||||
|
|
||||||
def filter_location(ctx, location):
|
def filter_location(ctx, location):
|
||||||
if not ctx.key_drop_mode and ('Key Drop' in location or 'Pot Key' in location):
|
if (not ctx.key_drop_mode and location in PotShuffle.key_drop_data
|
||||||
|
and PotShuffle.key_drop_data[location][0] == 'Drop'):
|
||||||
|
return True
|
||||||
|
if (not ctx.pottery_mode and location in PotShuffle.key_drop_data
|
||||||
|
and PotShuffle.key_drop_data[location][0] == 'Pot'):
|
||||||
return True
|
return True
|
||||||
if not ctx.shop_mode and location in Regions.flat_normal_shops:
|
if not ctx.shop_mode and location in Regions.flat_normal_shops:
|
||||||
return True
|
return True
|
||||||
@@ -821,6 +839,31 @@ def filter_location(ctx, location):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def init_lookups(ctx):
|
||||||
|
ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()}
|
||||||
|
ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()}
|
||||||
|
for location, datum in PotShuffle.key_drop_data.items():
|
||||||
|
type = datum[0]
|
||||||
|
if type == 'Drop':
|
||||||
|
location_id, super_tile, sprite_index = datum[1]
|
||||||
|
location_table_sprite_items[location] = (2 * super_tile, 0x8000 >> sprite_index)
|
||||||
|
ctx.lookup_name_to_id[location] = location_id
|
||||||
|
ctx.lookup_id_to_name[location_id] = location
|
||||||
|
for super_tile, pot_list in PotShuffle.vanilla_pots.items():
|
||||||
|
for pot_index, pot in enumerate(pot_list):
|
||||||
|
if pot.item != PotItem.Hole:
|
||||||
|
if pot.item == PotItem.Key:
|
||||||
|
loc_name = next(loc for loc, datum in PotShuffle.key_drop_data.items()
|
||||||
|
if datum[1] == super_tile)
|
||||||
|
else:
|
||||||
|
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||||
|
loc_name = f'{pot.room} {descriptor}'
|
||||||
|
location_table_pot_items[loc_name] = (2 * super_tile, 0x8000 >> pot_index)
|
||||||
|
location_id = Regions.pot_address(pot_index, super_tile)
|
||||||
|
ctx.lookup_name_to_id[loc_name] = location_id
|
||||||
|
ctx.lookup_id_to_name[location_id] = loc_name
|
||||||
|
|
||||||
|
|
||||||
async def track_locations(ctx : Context, roomid, roomdata):
|
async def track_locations(ctx : Context, roomid, roomdata):
|
||||||
new_locations = []
|
new_locations = []
|
||||||
|
|
||||||
@@ -832,9 +875,12 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
|||||||
|
|
||||||
if ctx.mode_flags is None:
|
if ctx.mode_flags is None:
|
||||||
flags = await snes_read(ctx, MODE_FLAGS, 1)
|
flags = await snes_read(ctx, MODE_FLAGS, 1)
|
||||||
|
ctx.mode_flags = flags
|
||||||
ctx.key_drop_mode = flags[0] & 0x1
|
ctx.key_drop_mode = flags[0] & 0x1
|
||||||
ctx.shop_mode = flags[0] & 0x2
|
ctx.shop_mode = flags[0] & 0x2
|
||||||
ctx.retro_mode = flags[0] & 0x4
|
ctx.retro_mode = flags[0] & 0x4
|
||||||
|
ctx.pottery_mode = flags[0] & 0x8
|
||||||
|
ctx.mystery_mode = flags[0] & 0x10
|
||||||
|
|
||||||
def new_check(location):
|
def new_check(location):
|
||||||
ctx.locations_checked.add(location)
|
ctx.locations_checked.add(location)
|
||||||
@@ -842,12 +888,13 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
|||||||
if ignored:
|
if ignored:
|
||||||
ctx.ignore_count += 1
|
ctx.ignore_count += 1
|
||||||
else:
|
else:
|
||||||
logging.info(f"New check: {location} ({len(ctx.locations_checked)-ctx.ignore_count}/{ctx.total_locations})")
|
total = '???' if ctx.mystery_mode else ctx.total_locations
|
||||||
new_locations.append(Regions.lookup_name_to_id[location])
|
logging.info(f"New check: {location} ({len(ctx.locations_checked)-ctx.ignore_count}/{total})")
|
||||||
|
new_locations.append(ctx.lookup_name_to_id[location])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if ctx.shop_mode or ctx.retro_mode:
|
if ctx.shop_mode or ctx.retro_mode:
|
||||||
misc_data = await snes_read(ctx, SHOP_ADDR, SHOP_SRAM_LEN)
|
misc_data = await snes_read(ctx, SHOP_SRAM_START, SHOP_SRAM_LEN)
|
||||||
for cnt, b in enumerate(misc_data):
|
for cnt, b in enumerate(misc_data):
|
||||||
my_check = Regions.shop_table_by_location_id[0x400000 + cnt]
|
my_check = Regions.shop_table_by_location_id[0x400000 + cnt]
|
||||||
if int(b) > 0 and my_check not in ctx.locations_checked:
|
if int(b) > 0 and my_check not in ctx.locations_checked:
|
||||||
@@ -908,6 +955,22 @@ async def track_locations(ctx : Context, roomid, roomdata):
|
|||||||
if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.locations_checked:
|
if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.locations_checked:
|
||||||
new_check(location)
|
new_check(location)
|
||||||
|
|
||||||
|
if not all([location in ctx.locations_checked for location in location_table_pot_items.keys()]):
|
||||||
|
pot_items_data = await snes_read(ctx, POT_ITEMS_SRAM_START, ITEM_SRAM_SIZE)
|
||||||
|
if pot_items_data is not None:
|
||||||
|
for location, (offset, mask) in location_table_pot_items.items():
|
||||||
|
pot_value = pot_items_data[offset] | (pot_items_data[offset + 1] << 8)
|
||||||
|
if pot_value & mask != 0 and location not in ctx.locations_checked:
|
||||||
|
new_check(location)
|
||||||
|
|
||||||
|
if not all([location in ctx.locations_checked for location in location_table_sprite_items.keys()]):
|
||||||
|
sprite_items_data = await snes_read(ctx, SPRITE_ITEMS_SRAM_START, ITEM_SRAM_SIZE)
|
||||||
|
if sprite_items_data is not None:
|
||||||
|
for location, (offset, mask) in location_table_sprite_items.items():
|
||||||
|
sprite_value = sprite_items_data[offset] | (sprite_items_data[offset + 1] << 8)
|
||||||
|
if sprite_value & mask != 0 and location not in ctx.locations_checked:
|
||||||
|
new_check(location)
|
||||||
|
|
||||||
await send_msgs(ctx.socket, [['LocationChecks', new_locations]])
|
await send_msgs(ctx.socket, [['LocationChecks', new_locations]])
|
||||||
|
|
||||||
async def game_watcher(ctx : Context):
|
async def game_watcher(ctx : Context):
|
||||||
@@ -955,7 +1018,7 @@ async def game_watcher(ctx : Context):
|
|||||||
item = ctx.items_received[recv_index]
|
item = ctx.items_received[recv_index]
|
||||||
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
logging.info('Received %s from %s (%s) (%d/%d in list)' % (
|
||||||
color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
color(get_item_name_from_id(item.item), 'red', 'bold'), color(ctx.player_names[item.player], 'yellow'),
|
||||||
get_location_name_from_address(item.location), recv_index + 1, len(ctx.items_received)))
|
get_location_name_from_address(ctx, item.location), recv_index + 1, len(ctx.items_received)))
|
||||||
recv_index += 1
|
recv_index += 1
|
||||||
snes_buffered_write(ctx, RECV_PROGRESS_ADDR, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
snes_buffered_write(ctx, RECV_PROGRESS_ADDR, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||||
snes_buffered_write(ctx, RECV_ITEM_ADDR, bytes([item.item]))
|
snes_buffered_write(ctx, RECV_ITEM_ADDR, bytes([item.item]))
|
||||||
@@ -969,7 +1032,7 @@ async def game_watcher(ctx : Context):
|
|||||||
|
|
||||||
if scout_location > 0 and scout_location not in ctx.locations_scouted:
|
if scout_location > 0 and scout_location not in ctx.locations_scouted:
|
||||||
ctx.locations_scouted.add(scout_location)
|
ctx.locations_scouted.add(scout_location)
|
||||||
logging.info(f'Scouting item at {list(Regions.lookup_id_to_name.keys())[scout_location - 1]}')
|
logging.info(f'Scouting item at {list(ctx.lookup_id_to_name.keys())[scout_location - 1]}')
|
||||||
await send_msgs(ctx.socket, [['LocationScouts', [scout_location]]])
|
await send_msgs(ctx.socket, [['LocationScouts', [scout_location]]])
|
||||||
await track_locations(ctx, roomid, roomdata)
|
await track_locations(ctx, roomid, roomdata)
|
||||||
|
|
||||||
@@ -984,6 +1047,7 @@ async def main():
|
|||||||
logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
logging.basicConfig(format='%(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
||||||
|
|
||||||
ctx = Context(args.snes, args.connect, args.password)
|
ctx = Context(args.snes, args.connect, args.password)
|
||||||
|
init_lookups(ctx)
|
||||||
|
|
||||||
input_task = asyncio.create_task(console_loop(ctx))
|
input_task = asyncio.create_task(console_loop(ctx))
|
||||||
|
|
||||||
|
|||||||
+38
-8
@@ -6,12 +6,15 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import shlex
|
import shlex
|
||||||
|
import ssl
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import websockets
|
import websockets
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
|
from BaseClasses import PotItem, PotFlags
|
||||||
import Items
|
import Items
|
||||||
import Regions
|
import Regions
|
||||||
|
import PotShuffle
|
||||||
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
|
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
|
||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
@@ -40,6 +43,9 @@ class Context:
|
|||||||
self.clients = []
|
self.clients = []
|
||||||
self.received_items = {}
|
self.received_items = {}
|
||||||
|
|
||||||
|
self.lookup_name_to_id = {}
|
||||||
|
self.lookup_id_to_name = {}
|
||||||
|
|
||||||
async def send_msgs(websocket, msgs):
|
async def send_msgs(websocket, msgs):
|
||||||
if not websocket or not websocket.open or websocket.closed:
|
if not websocket or not websocket.open or websocket.closed:
|
||||||
return
|
return
|
||||||
@@ -154,8 +160,7 @@ def send_new_items(ctx : Context):
|
|||||||
client.send_index = len(items)
|
client.send_index = len(items)
|
||||||
|
|
||||||
def forfeit_player(ctx : Context, team, slot):
|
def forfeit_player(ctx : Context, team, slot):
|
||||||
all_locations = {values[0] for values in Regions.location_table.values() if type(values[0]) is int}
|
all_locations = set(ctx.lookup_id_to_name.keys())
|
||||||
all_locations.update({values[1] for values in Regions.key_drop_data.values()})
|
|
||||||
notify_all(ctx, "%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1))
|
notify_all(ctx, "%s (Team #%d) has forfeited" % (ctx.player_names[(team, slot)], team + 1))
|
||||||
register_location_checks(ctx, team, slot, all_locations)
|
register_location_checks(ctx, team, slot, all_locations)
|
||||||
|
|
||||||
@@ -176,7 +181,8 @@ def register_location_checks(ctx : Context, team, slot, locations):
|
|||||||
recvd_items.append(new_item)
|
recvd_items.append(new_item)
|
||||||
if slot != target_player:
|
if slot != target_player:
|
||||||
broadcast_team(ctx, team, [['ItemSent', (slot, location, target_player, target_item)]])
|
broadcast_team(ctx, team, [['ItemSent', (slot, location, target_player, target_item)]])
|
||||||
logging.info('(Team #%d) %s sent %s to %s (%s)' % (team+1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), ctx.player_names[(team, target_player)], get_location_name_from_address(location)))
|
loc_name = get_location_name_from_address(ctx, location)
|
||||||
|
logging.info('(Team #%d) %s sent %s to %s (%s)' % (team+1, ctx.player_names[(team, slot)], get_item_name_from_id(target_item), ctx.player_names[(team, target_player)], loc_name))
|
||||||
found_items = True
|
found_items = True
|
||||||
send_new_items(ctx)
|
send_new_items(ctx)
|
||||||
|
|
||||||
@@ -249,11 +255,11 @@ async def process_client_cmd(ctx : Context, client : Client, cmd, args):
|
|||||||
return
|
return
|
||||||
locs = []
|
locs = []
|
||||||
for location in args:
|
for location in args:
|
||||||
if type(location) is not int or 0 >= location > len(Regions.lookup_id_to_name.keys()):
|
if type(location) is not int or 0 >= location > len(ctx.lookup_id_to_name.keys()):
|
||||||
await send_msgs(client.socket, [['InvalidArguments', 'LocationScouts']])
|
await send_msgs(client.socket, [['InvalidArguments', 'LocationScouts']])
|
||||||
return
|
return
|
||||||
loc_name = list(Regions.lookup_id_to_name.keys())[location - 1]
|
loc_name = list(ctx.lookup_id_to_name.keys())[location - 1]
|
||||||
target_item, target_player = ctx.locations[(Regions.lookup_name_to_id[loc_name], client.slot)]
|
target_item, target_player = ctx.locations[(ctx.lookup_name_to_id[loc_name], client.slot)]
|
||||||
|
|
||||||
replacements = {'SmallKey': 0xA2, 'BigKey': 0x9D, 'Compass': 0x8D, 'Map': 0x7D}
|
replacements = {'SmallKey': 0xA2, 'BigKey': 0x9D, 'Compass': 0x8D, 'Map': 0x7D}
|
||||||
item_type = [i[2] for i in Items.item_table.values() if type(i[3]) is int and i[3] == target_item]
|
item_type = [i[2] for i in Items.item_table.values() if type(i[3]) is int and i[3] == target_item]
|
||||||
@@ -339,6 +345,30 @@ async def console(ctx : Context):
|
|||||||
if command[0][0] != '/':
|
if command[0][0] != '/':
|
||||||
notify_all(ctx, '[Server]: ' + input)
|
notify_all(ctx, '[Server]: ' + input)
|
||||||
|
|
||||||
|
|
||||||
|
def init_lookups(ctx):
|
||||||
|
ctx.lookup_id_to_name = {x: y for x, y in Regions.lookup_id_to_name.items()}
|
||||||
|
ctx.lookup_name_to_id = {x: y for x, y in Regions.lookup_name_to_id.items()}
|
||||||
|
for location, datum in PotShuffle.key_drop_data.items():
|
||||||
|
type = datum[0]
|
||||||
|
if type == 'Drop':
|
||||||
|
location_id = datum[1][0]
|
||||||
|
ctx.lookup_name_to_id[location] = location_id
|
||||||
|
ctx.lookup_id_to_name[location_id] = location
|
||||||
|
for super_tile, pot_list in PotShuffle.vanilla_pots.items():
|
||||||
|
for pot_index, pot in enumerate(pot_list):
|
||||||
|
if pot.item != PotItem.Hole:
|
||||||
|
if pot.item == PotItem.Key:
|
||||||
|
loc_name = next(loc for loc, datum in PotShuffle.key_drop_data.items()
|
||||||
|
if datum[1] == super_tile)
|
||||||
|
else:
|
||||||
|
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||||
|
loc_name = f'{pot.room} {descriptor}'
|
||||||
|
location_id = Regions.pot_address(pot_index, super_tile)
|
||||||
|
ctx.lookup_name_to_id[loc_name] = location_id
|
||||||
|
ctx.lookup_id_to_name[location_id] = loc_name
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--host', default=None)
|
parser.add_argument('--host', default=None)
|
||||||
@@ -353,7 +383,7 @@ async def main():
|
|||||||
logging.basicConfig(format='[%(asctime)s] %(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
logging.basicConfig(format='[%(asctime)s] %(message)s', level=getattr(logging, args.loglevel.upper(), logging.INFO))
|
||||||
|
|
||||||
ctx = Context(args.host, args.port, args.password)
|
ctx = Context(args.host, args.port, args.password)
|
||||||
|
init_lookups(ctx)
|
||||||
ctx.data_filename = args.multidata
|
ctx.data_filename = args.multidata
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -376,7 +406,7 @@ async def main():
|
|||||||
logging.error('Failed to read multiworld data (%s)' % e)
|
logging.error('Failed to read multiworld data (%s)' % e)
|
||||||
return
|
return
|
||||||
|
|
||||||
ip = urllib.request.urlopen('https://v4.ident.me').read().decode('utf8') if not ctx.host else ctx.host
|
ip = urllib.request.urlopen('https://v4.ident.me', context=ssl._create_unverified_context()).read().decode('utf8') if not ctx.host else ctx.host
|
||||||
logging.info('Hosting game at %s:%d (%s)' % (ip, ctx.port, 'No password' if not ctx.password else 'Password: %s' % ctx.password))
|
logging.info('Hosting game at %s:%d (%s)' % (ip, ctx.port, 'No password' if not ctx.password else 'Password: %s' % ctx.password))
|
||||||
|
|
||||||
ctx.disable_save = args.disable_save
|
ctx.disable_save = args.disable_save
|
||||||
|
|||||||
+13
-5
@@ -30,6 +30,7 @@ def main():
|
|||||||
parser.add_argument('--create_spoiler', action='store_true')
|
parser.add_argument('--create_spoiler', action='store_true')
|
||||||
parser.add_argument('--no_race', action='store_true')
|
parser.add_argument('--no_race', action='store_true')
|
||||||
parser.add_argument('--suppress_rom', action='store_true')
|
parser.add_argument('--suppress_rom', action='store_true')
|
||||||
|
parser.add_argument('--suppress_meta', action='store_true')
|
||||||
parser.add_argument('--bps', action='store_true')
|
parser.add_argument('--bps', action='store_true')
|
||||||
parser.add_argument('--rom')
|
parser.add_argument('--rom')
|
||||||
parser.add_argument('--enemizercli')
|
parser.add_argument('--enemizercli')
|
||||||
@@ -65,12 +66,14 @@ def main():
|
|||||||
erargs.names = args.names
|
erargs.names = args.names
|
||||||
erargs.create_spoiler = args.create_spoiler
|
erargs.create_spoiler = args.create_spoiler
|
||||||
erargs.suppress_rom = args.suppress_rom
|
erargs.suppress_rom = args.suppress_rom
|
||||||
|
erargs.suppress_meta = args.suppress_meta
|
||||||
erargs.bps = args.bps
|
erargs.bps = args.bps
|
||||||
erargs.race = not args.no_race
|
erargs.race = not args.no_race
|
||||||
erargs.outputname = seedname
|
erargs.outputname = seedname
|
||||||
if args.outputpath:
|
if args.outputpath:
|
||||||
erargs.outputpath = args.outputpath
|
erargs.outputpath = args.outputpath
|
||||||
erargs.loglevel = args.loglevel
|
erargs.loglevel = args.loglevel
|
||||||
|
erargs.mystery = True
|
||||||
|
|
||||||
if args.rom:
|
if args.rom:
|
||||||
erargs.rom = args.rom
|
erargs.rom = args.rom
|
||||||
@@ -142,8 +145,8 @@ def roll_settings(weights):
|
|||||||
|
|
||||||
ret.algorithm = get_choice('algorithm')
|
ret.algorithm = get_choice('algorithm')
|
||||||
|
|
||||||
glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'owg': 'owglitches',
|
glitch_map = {'none': 'noglitches', 'no_logic': 'nologic', 'owglitches': 'owglitches',
|
||||||
'minorglitches': 'minorglitches'}
|
'owg': 'owglitches', 'minorglitches': 'minorglitches'}
|
||||||
glitches_required = get_choice('glitches_required')
|
glitches_required = get_choice('glitches_required')
|
||||||
if glitches_required is not None:
|
if glitches_required is not None:
|
||||||
if glitches_required not in glitch_map.keys():
|
if glitches_required not in glitch_map.keys():
|
||||||
@@ -179,6 +182,7 @@ def roll_settings(weights):
|
|||||||
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
|
ret.door_shuffle = door_shuffle if door_shuffle != 'none' else 'vanilla'
|
||||||
ret.intensity = get_choice('intensity')
|
ret.intensity = get_choice('intensity')
|
||||||
ret.experimental = get_choice('experimental') == 'on'
|
ret.experimental = get_choice('experimental') == 'on'
|
||||||
|
ret.collection_rate = get_choice('collection_rate') == 'on'
|
||||||
|
|
||||||
ret.dungeon_counters = get_choice('dungeon_counters') if 'dungeon_counters' in weights else 'default'
|
ret.dungeon_counters = get_choice('dungeon_counters') if 'dungeon_counters' in weights else 'default'
|
||||||
if ret.dungeon_counters == 'default':
|
if ret.dungeon_counters == 'default':
|
||||||
@@ -186,7 +190,10 @@ def roll_settings(weights):
|
|||||||
|
|
||||||
ret.pseudoboots = get_choice('pseudoboots') == 'on'
|
ret.pseudoboots = get_choice('pseudoboots') == 'on'
|
||||||
ret.shopsanity = get_choice('shopsanity') == 'on'
|
ret.shopsanity = get_choice('shopsanity') == 'on'
|
||||||
ret.keydropshuffle = get_choice('keydropshuffle') == 'on'
|
ret.dropshuffle = get_choice('dropshuffle') == 'on'
|
||||||
|
ret.pottery = get_choice('pottery') if 'pottery' in weights else 'none'
|
||||||
|
ret.colorizepots = get_choice('colorizepots') == 'on'
|
||||||
|
ret.shufflepots = get_choice('pot_shuffle') == 'on'
|
||||||
ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent'
|
ret.mixed_travel = get_choice('mixed_travel') if 'mixed_travel' in weights else 'prevent'
|
||||||
ret.standardize_palettes = get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize'
|
ret.standardize_palettes = get_choice('standardize_palettes') if 'standardize_palettes' in weights else 'standardize'
|
||||||
|
|
||||||
@@ -262,8 +269,6 @@ def roll_settings(weights):
|
|||||||
|
|
||||||
ret.enemy_health = get_choice('enemy_health')
|
ret.enemy_health = get_choice('enemy_health')
|
||||||
|
|
||||||
ret.shufflepots = get_choice('pot_shuffle') == 'on'
|
|
||||||
|
|
||||||
ret.beemizer = get_choice('beemizer') if 'beemizer' in weights else '0'
|
ret.beemizer = get_choice('beemizer') if 'beemizer' in weights else '0'
|
||||||
|
|
||||||
inventoryweights = weights.get('startinventory', {})
|
inventoryweights = weights.get('startinventory', {})
|
||||||
@@ -272,6 +277,8 @@ def roll_settings(weights):
|
|||||||
if get_choice(item, inventoryweights) == 'on':
|
if get_choice(item, inventoryweights) == 'on':
|
||||||
startitems.append(item)
|
startitems.append(item)
|
||||||
ret.startinventory = ','.join(startitems)
|
ret.startinventory = ','.join(startitems)
|
||||||
|
if len(startitems) > 0:
|
||||||
|
ret.usestartinventory = True
|
||||||
|
|
||||||
if 'rom' in weights:
|
if 'rom' in weights:
|
||||||
romweights = weights['rom']
|
romweights = weights['rom']
|
||||||
@@ -279,6 +286,7 @@ def roll_settings(weights):
|
|||||||
ret.disablemusic = get_choice('disablemusic', romweights) == 'on'
|
ret.disablemusic = get_choice('disablemusic', romweights) == 'on'
|
||||||
ret.quickswap = get_choice('quickswap', romweights) == 'on'
|
ret.quickswap = get_choice('quickswap', romweights) == 'on'
|
||||||
ret.reduce_flashing = get_choice('reduce_flashing', romweights) == 'on'
|
ret.reduce_flashing = get_choice('reduce_flashing', romweights) == 'on'
|
||||||
|
ret.msu_resume = get_choice('msu_resume', romweights) == 'on'
|
||||||
ret.fastmenu = get_choice('menuspeed', romweights)
|
ret.fastmenu = get_choice('menuspeed', romweights)
|
||||||
ret.heartcolor = get_choice('heartcolor', romweights)
|
ret.heartcolor = get_choice('heartcolor', romweights)
|
||||||
ret.heartbeep = get_choice('heartbeep', romweights)
|
ret.heartbeep = get_choice('heartbeep', romweights)
|
||||||
|
|||||||
+2
-2
@@ -727,7 +727,7 @@ OWTileRegions = bidict({
|
|||||||
|
|
||||||
'Zora Waterfall Area': 0x0f,
|
'Zora Waterfall Area': 0x0f,
|
||||||
'Zora Waterfall Water': 0x0f,
|
'Zora Waterfall Water': 0x0f,
|
||||||
'Waterfall of Wishing Cave': 0x0f,
|
'Zora Waterfall Entryway': 0x0f,
|
||||||
|
|
||||||
'Lost Woods Pass West Area': 0x10,
|
'Lost Woods Pass West Area': 0x10,
|
||||||
'Lost Woods Pass East Top Area': 0x10,
|
'Lost Woods Pass East Top Area': 0x10,
|
||||||
@@ -1253,7 +1253,7 @@ OWExitTypes = {
|
|||||||
'Mountain Entry Entrance Rock (West)',
|
'Mountain Entry Entrance Rock (West)',
|
||||||
'Mountain Entry Entrance Rock (East)',
|
'Mountain Entry Entrance Rock (East)',
|
||||||
'Zora Waterfall Water Entry',
|
'Zora Waterfall Water Entry',
|
||||||
'Waterfall of Wishing Cave Entry',
|
'Zora Waterfall Water Approach',
|
||||||
'Zora Waterfall Landing',
|
'Zora Waterfall Landing',
|
||||||
'Lost Woods Pass Hammer (North)',
|
'Lost Woods Pass Hammer (North)',
|
||||||
'Lost Woods Pass Hammer (South)',
|
'Lost Woods Pass Hammer (South)',
|
||||||
|
|||||||
+6
-9
@@ -6,7 +6,7 @@ from Regions import mark_dark_world_regions, mark_light_world_regions
|
|||||||
from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
|
from OWEdges import OWTileRegions, OWEdgeGroups, OWExitTypes, OpenStd, parallel_links, IsParallel
|
||||||
from Utils import bidict
|
from Utils import bidict
|
||||||
|
|
||||||
version_number = '0.2.7.3'
|
version_number = '0.2.8.0'
|
||||||
version_branch = ''
|
version_branch = ''
|
||||||
__version__ = '%s%s' % (version_number, version_branch)
|
__version__ = '%s%s' % (version_number, version_branch)
|
||||||
|
|
||||||
@@ -882,8 +882,7 @@ def build_sectors(world, player):
|
|||||||
# perform accessibility check on duplicate world
|
# perform accessibility check on duplicate world
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world)
|
base_world = copy_world(world, True)
|
||||||
world.key_logic = {}
|
|
||||||
|
|
||||||
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
|
# build lists of contiguous regions accessible with full inventory (excl portals/mirror/flute/entrances)
|
||||||
regions = list(OWTileRegions.copy().keys())
|
regions = list(OWTileRegions.copy().keys())
|
||||||
@@ -958,9 +957,8 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
|
|||||||
if build_copy_world:
|
if build_copy_world:
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world)
|
base_world = copy_world(world, True)
|
||||||
base_world.override_bomb_check = True
|
base_world.override_bomb_check = True
|
||||||
world.key_logic = {}
|
|
||||||
else:
|
else:
|
||||||
base_world = world
|
base_world = world
|
||||||
|
|
||||||
@@ -974,7 +972,7 @@ def build_accessible_region_list(world, start_region, player, build_copy_world=F
|
|||||||
return explored_regions
|
return explored_regions
|
||||||
|
|
||||||
def validate_layout(world, player):
|
def validate_layout(world, player):
|
||||||
if world.accessibility[player] == 'beatable':
|
if world.accessibility[player] == 'none':
|
||||||
return True
|
return True
|
||||||
|
|
||||||
entrance_connectors = {
|
entrance_connectors = {
|
||||||
@@ -1048,8 +1046,7 @@ def validate_layout(world, player):
|
|||||||
|
|
||||||
for p in range(1, world.players + 1):
|
for p in range(1, world.players + 1):
|
||||||
world.key_logic[p] = {}
|
world.key_logic[p] = {}
|
||||||
base_world = copy_world(world)
|
base_world = copy_world(world, True)
|
||||||
world.key_logic = {}
|
|
||||||
explored_regions = list()
|
explored_regions = list()
|
||||||
|
|
||||||
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]:
|
if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not world.shufflelinks[player]:
|
||||||
@@ -1152,7 +1149,7 @@ mandatory_connections = [# Intra-tile OW Connections
|
|||||||
('Zora Waterfall Landing', 'Zora Waterfall Area'),
|
('Zora Waterfall Landing', 'Zora Waterfall Area'),
|
||||||
('Zora Waterfall Water Drop', 'Zora Waterfall Water'), #flippers
|
('Zora Waterfall Water Drop', 'Zora Waterfall Water'), #flippers
|
||||||
('Zora Waterfall Water Entry', 'Zora Waterfall Water'), #flippers
|
('Zora Waterfall Water Entry', 'Zora Waterfall Water'), #flippers
|
||||||
('Waterfall of Wishing Cave Entry', 'Waterfall of Wishing Cave'), #flippers
|
('Zora Waterfall Water Approach', 'Zora Waterfall Entryway'), #flippers
|
||||||
('Lost Woods Pass Hammer (North)', 'Lost Woods Pass Portal Area'), #hammer
|
('Lost Woods Pass Hammer (North)', 'Lost Woods Pass Portal Area'), #hammer
|
||||||
('Lost Woods Pass Hammer (South)', 'Lost Woods Pass East Top Area'), #hammer
|
('Lost Woods Pass Hammer (South)', 'Lost Woods Pass East Top Area'), #hammer
|
||||||
('Lost Woods Pass Rock (North)', 'Lost Woods Pass East Bottom Area'), #mitts
|
('Lost Woods Pass Rock (North)', 'Lost Woods Pass East Bottom Area'), #mitts
|
||||||
|
|||||||
+1004
-264
File diff suppressed because it is too large
Load Diff
@@ -140,7 +140,7 @@ New flute spots are chosen at random with minimum bias.
|
|||||||
|
|
||||||
### Trinity
|
### Trinity
|
||||||
|
|
||||||
This goal gives you the choice between 3 goals, only one of which the player needs to complete: Defeat Ganon (no Aga2), Pulling Pedestal, or turn in TF pieces to Murahdulah. By default, you need to find 8 of 10 total TF pieces but this can be changed with a Custom Item Pool. It is recommended to set GT Entry to 7 crystals and Ganon to 5 crystals or Random crystals, although the player can flexibly change these settings as they seem fit.
|
This goal gives you the choice between 3 goals, only one of which the player needs to complete: Defeat Ganon (no Aga2), Pulling Pedestal, or turn in TF pieces to Murahdahla. By default, you need to find 8 of 10 total TF pieces but this can be changed with a Custom Item Pool. It is recommended to set GT Entry to 7 crystals and Ganon to 5 crystals or Random crystals, although the player can flexibly change these settings as they seem fit.
|
||||||
|
|
||||||
## New Entrance Shuffle Options (--shuffle)
|
## New Entrance Shuffle Options (--shuffle)
|
||||||
|
|
||||||
|
|||||||
+134
-12
@@ -1,7 +1,77 @@
|
|||||||
## New Features
|
## New Features
|
||||||
|
|
||||||
## Restricted Item Placement Algorithm
|
## Pottery Lottery and Key Drop Shuffle Changes
|
||||||
|
|
||||||
|
### Pottery
|
||||||
|
|
||||||
|
New pottery option that control which pots (and large blocks) are in the locations pool:
|
||||||
|
|
||||||
|
* None: No pots are in the pool, like normal randomizer
|
||||||
|
* Key Pots: The pots that have keys are in the pool. This is about half of the old keydropshuffle option
|
||||||
|
* Cave Pots: The pots that are not found in dungeons are in the pool. (Includes the large block in Spike Cave). Does
|
||||||
|
not include key pots.
|
||||||
|
* CaveKeys: Both non-dungeon pots and pots that used to have keys are in the pool.
|
||||||
|
* Reduced: Same as CaveKeys but also roughly a quarter of dungeon pots are added to the location pool picked at random. This is a dynamic mode so pots in the pool will be colored. Pots out of the pool will have vanilla contents.
|
||||||
|
* Clustered: LIke reduced but pot are grouped by logical sets and roughly 50% of pots are chosen from those group. This is a dynamic mode like the above.
|
||||||
|
* Nonempty: All pots that had some sort of objects under them are chosen to be in the location pool. This excludes most large blocks and some pots out of dungeons.
|
||||||
|
* Dungeon Pots: The pots that are in dungeons are in the pool. (Includes serveral large blocks)
|
||||||
|
* Lottery: All pots and large blocks are in the pool
|
||||||
|
|
||||||
|
By default, switches remain in their vanilla location (unless you turn on the legacy option below)
|
||||||
|
|
||||||
|
CLI `--pottery <option>` from `none, keys, cave, cavekeys, reduced, clustered, nonempty, dungeon, lottery`
|
||||||
|
|
||||||
|
Note for multiworld: due to the design of the pottery lottery, only 256 items for other players can be under pots in your world.
|
||||||
|
|
||||||
|
### Colorize Pots
|
||||||
|
|
||||||
|
If the pottery mode is dynamic, this option is forced to be on (clustered and reduced). It is allowed to be on in all other pottery modes. Exceptions include "none" where no pots would be colored, and "lottery" where all pots would be. This option colors the pots differently that have been chosen to be part of the location pool. If not specified, you are expected to remember the pottery setting you chose. Note that Mystery will colorize all pots if lottery is chosen randomly.
|
||||||
|
|
||||||
|
CLI `--colorizepots`
|
||||||
|
|
||||||
|
### Shuffle key drops
|
||||||
|
|
||||||
|
Enemies that drop keys can have their drop shuffled into the pool. This is the other half of the keydropshuffle option.
|
||||||
|
|
||||||
|
CLI `--dropshuffle`
|
||||||
|
|
||||||
|
#### Legacy options
|
||||||
|
|
||||||
|
"Drop and Pot Keys" or `--keydropshuffle` is still availabe for use. This simply sets the pottery to keys and turns dropshuffle on as well to have the same behavior as the old setting.
|
||||||
|
|
||||||
|
The old "Pot Shuffle" option is still available under "Pot Shuffle (Legacy)" or `--shufflepots` and works the same by shuffling all pots on a supertile. It works with the lottery option as well to move the switches to any valid pot on the supertile regardless of the pots chosen in the pottery mode. This may increase the number of pot locations slightly depending on the mode.
|
||||||
|
|
||||||
|
#### Tracking Notes
|
||||||
|
|
||||||
|
The sram locations for pots and sprite drops are now final, please reach out for assistance or investigate the rom changes if needed.
|
||||||
|
|
||||||
|
## New Options
|
||||||
|
|
||||||
|
### Collection Rate
|
||||||
|
|
||||||
|
You can set the collection rate counter on using the "Display Collection Rate" on the Game Options tab are using the CLI option `--collection_rate`. Mystery seeds will not display the total.
|
||||||
|
|
||||||
|
### Goal: Trinity
|
||||||
|
|
||||||
|
Triforces are placed behind Ganon, on the pedestal, and on Murahdahla with 8/10 triforce pieces required. Recommend to run with 4-5 Crystal requirement for Ganon. Automatically pre-opens the pyramid.
|
||||||
|
|
||||||
|
### Boss Shuffle: Unique
|
||||||
|
|
||||||
|
At least one boss each of the prize bosses will be present guarding the prizes. GT bosses can be anything.
|
||||||
|
|
||||||
|
### MSU Resume
|
||||||
|
|
||||||
|
Turns on msu resume support. Found on "Game Options" tab, the "Adjust/Patch" tab, or use the `--msu_resume` CLI option.
|
||||||
|
|
||||||
|
### BPS Patch
|
||||||
|
|
||||||
|
Creates a bps patch for the seed. Found on the "Generation Setup" tab called "Create BPS Patches" or `--bps`. Can turn off generating a rom using the existing "Create Patched ROM" option or `--suppress_rom`. There is an option on the Adjust/Patch tab to select a bps file to apply to the Base Rom selected on the Generation Setup tab using the Patch Rom button. Selected adjustments will be applied during patching.
|
||||||
|
|
||||||
|
## New Font
|
||||||
|
|
||||||
|
Font updated to support lowercase English. Lowercase vs. uppercase typos may exist. Note, you can use lowercase English letters on the file name.
|
||||||
|
|
||||||
|
## Restricted Item Placement Algorithm
|
||||||
|
|
||||||
The "Item Sorting" option or ```--algorithm``` has been updated with new placement algorithms. Older algorithms have been removed.
|
The "Item Sorting" option or ```--algorithm``` has been updated with new placement algorithms. Older algorithms have been removed.
|
||||||
|
|
||||||
@@ -13,10 +83,6 @@ The "Item Sorting" option or ```--algorithm``` has been updated with new placeme
|
|||||||
|
|
||||||
This one stays the same as before and is recommended for the most random distribution of items.
|
This one stays the same as before and is recommended for the most random distribution of items.
|
||||||
|
|
||||||
### Equitable
|
|
||||||
|
|
||||||
This one is currently under development and may not fill correctly. It is a new method that should allow item and key logic to interact. (Vanilla key placement in PoD is theoretically possible, but isn't yet.)
|
|
||||||
|
|
||||||
### Vanilla Fill
|
### Vanilla Fill
|
||||||
|
|
||||||
This fill attempts to place all items in their vanilla locations when possible. Obviously shuffling entrances or the dungeon interiors will often prevent items from being placed in their vanilla location. If the vanilla fill is not possible, then other locations are tried in sequence preferring "major" locations (see below), then heart piece locations, then the rest except for GT locations which are preferred last. Note the PoD small key that is normally found in the dark maze in vanilla is move to Harmless Hellway due to the placement algorithm limitation.
|
This fill attempts to place all items in their vanilla locations when possible. Obviously shuffling entrances or the dungeon interiors will often prevent items from being placed in their vanilla location. If the vanilla fill is not possible, then other locations are tried in sequence preferring "major" locations (see below), then heart piece locations, then the rest except for GT locations which are preferred last. Note the PoD small key that is normally found in the dark maze in vanilla is move to Harmless Hellway due to the placement algorithm limitation.
|
||||||
@@ -33,12 +99,12 @@ The fill attempts to place all major items in dungeons. It will overflow to the
|
|||||||
|
|
||||||
### District Restriction
|
### District Restriction
|
||||||
|
|
||||||
The world is divided up into different regions or districts. Each dungeon is it's own district. The overworld consists of the following districts:
|
The world is divided up into different regions or districts. Each dungeon is its own district. The overworld consists of the following districts:
|
||||||
|
|
||||||
Light world:
|
Light world:
|
||||||
|
|
||||||
* Kakariko (The main screen, blacksmith screen, and library/maze race screens)
|
* Kakariko (The main screen, blacksmith screen, and library/maze race screens)
|
||||||
* Northwest Hyrule (The lost woods and fortune teller all the way to the rive west of the potion shop)
|
* Northwest Hyrule (The lost woods and fortune teller screens all the way to the river west of the potion shop)
|
||||||
* Central Hyrule (Hyrule castle, Link's House, the marsh, and the haunted grove)
|
* Central Hyrule (Hyrule castle, Link's House, the marsh, and the haunted grove)
|
||||||
* Desert (From the thief to the main desert screen)
|
* Desert (From the thief to the main desert screen)
|
||||||
* Lake Hylia (Around the lake)
|
* Lake Hylia (Around the lake)
|
||||||
@@ -63,14 +129,13 @@ In multiworld, the districts chosen apply to all players.
|
|||||||
|
|
||||||
### CLI values:
|
### CLI values:
|
||||||
|
|
||||||
```balanced, equitable, vanilla_fill, major_only, dungeon_only, district```
|
```balanced, vanilla_fill, major_only, dungeon_only, district```
|
||||||
|
|
||||||
## New Hints
|
## New Hints
|
||||||
|
|
||||||
Based on the district algorithm above (whether it is enabled or not,) new hints can appear about that district or dungeon. For each district and dungeon, it is evaluated whether it contains vital items and how many. If it has not any vital item, items then it moves onto useful items. Useful items are generally safeties or convenience items: shields, mails, half magic, bottles, medallions that aren't required, etc. If it contains none of those and is an overworld district, then it check for a couple more things. First, if dungeons are shuffled, it looks to see if any are in the district, if so, one of those dungeons is picked for the hint. Then, if connectors are shuffled, it checks to see if you can get to unique region through a connector in that district. If none of the above apply, the district or dungeon is considered completely foolish. At least two "foolish" districts are chosen and the rest are random.
|
Based on the district algorithm above (whether it is enabled or not,) new hints can appear about that district or dungeon. For each district and dungeon, it is evaluated whether it contains vital items and how many. If it has not any vital item, items then it moves onto useful items. Useful items are generally safeties or convenience items: shields, mails, half magic, bottles, medallions that aren't required, etc. If it contains none of those and is an overworld district, then it checks for a couple more things. First, if dungeons are shuffled, it looks to see if any are in the district, if so, one of those dungeons is picked for the hint. Then, if connectors are shuffled, it checks to see if you can get to unique region through a connector in that district. If none of the above apply, the district or dungeon is considered completely foolish.
|
||||||
|
|
||||||
|
## Overworld Map shows Dungeon Entrances
|
||||||
### Overworld Map shows dungeon location
|
|
||||||
|
|
||||||
Option to move indicators on overworld map to reference dungeon location. The non-default options include indicators for Hyrule Castle, Agahnim's Tower, and Ganon's Tower.
|
Option to move indicators on overworld map to reference dungeon location. The non-default options include indicators for Hyrule Castle, Agahnim's Tower, and Ganon's Tower.
|
||||||
|
|
||||||
@@ -106,7 +171,9 @@ As before, the boss may have any item including any dungeon item that could occu
|
|||||||
|
|
||||||
##### mapcompass
|
##### mapcompass
|
||||||
|
|
||||||
The map and compass are logically required to defeat a boss. This prevents both of those from appearing on the dungeon boss. Note that this does affect item placement logic and the placement algorithm as maps and compasses are considered as required items to beat a boss.
|
~~The map and compass are logically required to defeat a boss. This prevents both of those from appearing on the dungeon boss. Note that this does affect item placement logic and the placement algorithm as maps and compasses are considered as required items to beat a boss.~~
|
||||||
|
|
||||||
|
Currently bugged, not recommended for use.
|
||||||
|
|
||||||
##### dungeon
|
##### dungeon
|
||||||
|
|
||||||
@@ -114,6 +181,61 @@ Same as above but both small keys and bigs keys of the dungeon are not allowed o
|
|||||||
|
|
||||||
## Notes and Bug Fixes
|
## Notes and Bug Fixes
|
||||||
|
|
||||||
|
#### Unstable
|
||||||
|
|
||||||
|
* 1.0.1.0
|
||||||
|
* Large features
|
||||||
|
* New pottery modes - see notes above
|
||||||
|
* Pot substitutions added for red rupees, 10 bomb packs, 3 bomb packs, and 10 arrows have been added. They use objects that can result from a tree pull or other drop. The 3 bomb pack becomes a 4 bomb pack and the 10 bomb pack becomes an 8 pack. These substitutions are repeatable like all other normal pot contents.
|
||||||
|
* Updated TFH to support up to 850 pieces
|
||||||
|
* New font support
|
||||||
|
* Trinity goal added
|
||||||
|
* Separated Collection Rate counter from experimental
|
||||||
|
* Added MSU Resume option
|
||||||
|
* Support for BPS patch creation and applying patches during adjustment
|
||||||
|
* New option for Boss Shuffle: Unique (Prize bosses will be one of each, but GT bosses can be anything)
|
||||||
|
* Logic Notes
|
||||||
|
* Skull X Room requires Boots or access to Skull Back Drop
|
||||||
|
* GT Falling Torches requires Boots to get over the falling tile gap (this is a stop-gap measure until more sophisticated crystal switch traversal is possible)
|
||||||
|
* Waterfall of Wishing logic in open. You must have flippers to exit the Waterfall (flippers also required in glitched modes as well)
|
||||||
|
* Fix for GT Crystal Conveyor not requiring Somaria/Bombs to get through
|
||||||
|
* Pedestal goal + vanilla swords places a random sword in the pool
|
||||||
|
* Added a few more places Links House shouldn't go when shuffled
|
||||||
|
* Small features
|
||||||
|
* Added a check for python package requirements before running code. GUI and console both for better error messages. Thanks to mtrethewey for the idea.
|
||||||
|
* Refactored spoiler to generate in stages for better error collection. A meta file will be generated additionally for mystery seeds. Some random settings moved later in the spoiler to have the meta section at the top not spoil certain things. (GT/Ganon requirements.) Thanks to codemann and OWR for most of this work.
|
||||||
|
* Updated tourney winners (included Doors Async League winners)
|
||||||
|
* Some textual changes for hints (capitalization standardization)
|
||||||
|
* Item will be highlighted in red if experimental is on. This will likely be removed.
|
||||||
|
* Reworked GT Trash Fill. Base rate is 0-75% of locations fill with 7 crystals entrance requirements. Triforce hunt is 75%-100% of locations. The 75% number will decrease based on the crystal entrance requirement. Dungeon_only algorithm caps it based on how many items need to be placed in dungeons. Cross dungeon shuffle will now work with the trash fill.
|
||||||
|
* Expanded Mystery logic options (e.g. owglitches)
|
||||||
|
* Updated indicators on keysanity menu for overworld map option
|
||||||
|
* Bug fixes:
|
||||||
|
* Fix for Zelda (or any follower) going to the maiden cell supertile and the boss is not Blind. The follower will not despawn unless the boss is Blind, then the maiden will spawn as normal.
|
||||||
|
* Bug with 0 GT crystals not opening GT
|
||||||
|
* Fixed a couple issues with dungeon counters and the DungeonCompletion field for autotracking
|
||||||
|
* Settings code fix
|
||||||
|
* Fix for forbidding certain dashable doors (it actually does something this time)
|
||||||
|
* Fix for major item algorithm and pottery
|
||||||
|
* Updated map display on keysanity menu to work better with overworld_map option
|
||||||
|
* Minor bug in crossed doors
|
||||||
|
* Fix for Multiworld forfeits, shops and pot items now included
|
||||||
|
* MultiServer fix for ssl certs and python
|
||||||
|
* forbid certain doors from being dashable when you either can't dash them open (but bombs would work) or you'd fall into a pit from the bonk recoil in OHKO
|
||||||
|
* Fixed a couple rain state issues
|
||||||
|
* Add major_only algorithm to settings code
|
||||||
|
* Fixes for Links House being at certain entrances (did not generate)
|
||||||
|
* Fix for vanilla_fill, it now prioritizes heart container placements
|
||||||
|
* Fix for dungeon counter showing up in AT/HC in crossed dungeon mode
|
||||||
|
* Fixed usestartinventory with mystery
|
||||||
|
* Added double click fix for install.py
|
||||||
|
* Fix for SFX shuffle
|
||||||
|
* Fix for districting + shopsanity
|
||||||
|
* Fix for multiworld progression balancing would place Nothing or Arrow items
|
||||||
|
* Fixed a bug with shopsanity + district algorithm where pre-placed potions messed up the placeholder count
|
||||||
|
* Fixed usestartinventory flag (can be use on a per player basis)
|
||||||
|
* Sprite selector fix for systems with SSL issues
|
||||||
|
* Fix for Standard ER where locations in rain state could be in logic
|
||||||
* 1.0.0.3
|
* 1.0.0.3
|
||||||
* overworld_map=map mode fixed. Location of dungeons with maps are not shown until map is retrieved. (Dungeon that do not have map like Castle Tower are simply never shown)
|
* overworld_map=map mode fixed. Location of dungeons with maps are not shown until map is retrieved. (Dungeon that do not have map like Castle Tower are simply never shown)
|
||||||
* Aga2 completion on overworld_map now tied to boss defeat flag instead of pyramid hole being opened (fast ganon fix)
|
* Aga2 completion on overworld_map now tied to boss defeat flag instead of pyramid hole being opened (fast ganon fix)
|
||||||
|
|||||||
+129
-81
@@ -1,6 +1,7 @@
|
|||||||
import collections
|
import collections
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from BaseClasses import Region, Location, Entrance, RegionType, Terrain, Shop, ShopType
|
from BaseClasses import Region, Location, Entrance, RegionType, Terrain, Shop, ShopType, LocationType, PotItem, PotFlags
|
||||||
|
from PotShuffle import key_drop_data, vanilla_pots, choose_pots, PotSecretTable
|
||||||
|
|
||||||
|
|
||||||
def create_regions(world, player):
|
def create_regions(world, player):
|
||||||
@@ -31,8 +32,8 @@ def create_regions(world, player):
|
|||||||
create_lw_region(player, 'Mountain Entry Entrance', None, ['Mountain Entry Entrance Rock (East)', 'Mountain Entry Entrance Ledge Drop', 'Old Man Cave (West)', 'Bumper Cave Entry Mirror Spot']),
|
create_lw_region(player, 'Mountain Entry Entrance', None, ['Mountain Entry Entrance Rock (East)', 'Mountain Entry Entrance Ledge Drop', 'Old Man Cave (West)', 'Bumper Cave Entry Mirror Spot']),
|
||||||
create_lw_region(player, 'Mountain Entry Ledge', None, ['Mountain Entry Ledge Drop', 'Death Mountain Return Cave (West)', 'Bumper Cave Ledge Mirror Spot'], 'a ledge in the foothills'),
|
create_lw_region(player, 'Mountain Entry Ledge', None, ['Mountain Entry Ledge Drop', 'Death Mountain Return Cave (West)', 'Bumper Cave Ledge Mirror Spot'], 'a ledge in the foothills'),
|
||||||
create_lw_region(player, 'Zora Waterfall Area', None, ['Zora Waterfall Water Entry', 'Catfish Mirror Spot', 'Zora Waterfall SE', 'Zora Waterfall NE']),
|
create_lw_region(player, 'Zora Waterfall Area', None, ['Zora Waterfall Water Entry', 'Catfish Mirror Spot', 'Zora Waterfall SE', 'Zora Waterfall NE']),
|
||||||
create_lw_region(player, 'Zora Waterfall Water', None, ['Waterfall of Wishing Cave Entry', 'Zora Waterfall Landing', 'Zora Whirlpool'], 'Light World', Terrain.Water),
|
create_lw_region(player, 'Zora Waterfall Water', None, ['Zora Waterfall Water Approach', 'Zora Waterfall Landing', 'Zora Whirlpool'], 'Light World', Terrain.Water),
|
||||||
create_lw_region(player, 'Waterfall of Wishing Cave', None, ['Zora Waterfall Water Drop', 'Waterfall of Wishing']),
|
create_lw_region(player, 'Zora Waterfall Entryway', None, ['Zora Waterfall Water Drop', 'Waterfall of Wishing']),
|
||||||
create_lw_region(player, 'Zoras Domain', ['King Zora', 'Zora\'s Ledge'], ['Zoras Domain SW']),
|
create_lw_region(player, 'Zoras Domain', ['King Zora', 'Zora\'s Ledge'], ['Zoras Domain SW']),
|
||||||
create_lw_region(player, 'Lost Woods Pass West Area', None, ['Skull Woods Pass West Mirror Spot', 'Lost Woods Pass NW', 'Lost Woods Pass SW']),
|
create_lw_region(player, 'Lost Woods Pass West Area', None, ['Skull Woods Pass West Mirror Spot', 'Lost Woods Pass NW', 'Lost Woods Pass SW']),
|
||||||
create_lw_region(player, 'Lost Woods Pass East Top Area', None, ['Skull Woods Pass East Top Mirror Spot', 'Lost Woods Pass Hammer (North)', 'Lost Woods Pass NE']),
|
create_lw_region(player, 'Lost Woods Pass East Top Area', None, ['Skull Woods Pass East Top Mirror Spot', 'Lost Woods Pass Hammer (North)', 'Lost Woods Pass NE']),
|
||||||
@@ -233,14 +234,16 @@ def create_regions(world, player):
|
|||||||
create_cave_region(player, 'Old Man Cave Ledge', 'a connector', None, ['Old Man Cave Exit (West)', 'Old Man Cave Dropdown']),
|
create_cave_region(player, 'Old Man Cave Ledge', 'a connector', None, ['Old Man Cave Exit (West)', 'Old Man Cave Dropdown']),
|
||||||
create_cave_region(player, 'Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
|
create_cave_region(player, 'Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
|
||||||
create_cave_region(player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']),
|
create_cave_region(player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']),
|
||||||
create_cave_region(player, 'Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']),
|
create_cave_region(player, 'Death Mountain Return Cave (left)', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave E']),
|
||||||
|
create_cave_region(player, 'Death Mountain Return Cave (right)', 'a connector', None, ['Death Mountain Return Cave Exit (East)', 'Death Mountain Return Cave W']),
|
||||||
create_cave_region(player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']),
|
create_cave_region(player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']),
|
||||||
create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']),
|
create_cave_region(player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']),
|
||||||
create_cave_region(player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
|
create_cave_region(player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
|
||||||
create_cave_region(player, 'Hookshot Fairy', 'fairies deep in a cave'),
|
create_cave_region(player, 'Hookshot Fairy', 'fairies deep in a cave'),
|
||||||
create_cave_region(player, 'Paradox Cave Front', 'a connector', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']),
|
create_cave_region(player, 'Paradox Cave Front', 'a connector', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']),
|
||||||
create_cave_region(player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle',
|
create_cave_region(player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left', 'Paradox Cave Lower - Left', 'Paradox Cave Lower - Right', 'Paradox Cave Lower - Far Right', 'Paradox Cave Lower - Middle'],
|
||||||
'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right'], ['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']),
|
['Paradox Cave Push Block', 'Paradox Cave Bomb Jump', 'Paradox Cave Chest Area NE']),
|
||||||
|
create_cave_region(player, 'Paradox Cave Bomb Area', 'a connector', ['Paradox Cave Upper - Left', 'Paradox Cave Upper - Right']),
|
||||||
create_cave_region(player, 'Paradox Cave', 'a connector', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']),
|
create_cave_region(player, 'Paradox Cave', 'a connector', None, ['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']),
|
||||||
create_cave_region(player, 'Light World Death Mountain Shop', 'a common shop', ['Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right']),
|
create_cave_region(player, 'Light World Death Mountain Shop', 'a common shop', ['Paradox Shop - Left', 'Paradox Shop - Middle', 'Paradox Shop - Right']),
|
||||||
create_cave_region(player, 'Fairy Ascension Cave (Bottom)', 'a connector', None, ['Fairy Ascension Cave Climb', 'Fairy Ascension Cave Exit (Bottom)']),
|
create_cave_region(player, 'Fairy Ascension Cave (Bottom)', 'a connector', None, ['Fairy Ascension Cave Climb', 'Fairy Ascension Cave Exit (Bottom)']),
|
||||||
@@ -257,11 +260,13 @@ def create_regions(world, player):
|
|||||||
create_cave_region(player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']),
|
create_cave_region(player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']),
|
||||||
create_cave_region(player, 'North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']),
|
create_cave_region(player, 'North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']),
|
||||||
create_cave_region(player, 'Potion Shop', 'the potion shop', ['Potion Shop', 'Potion Shop - Left', 'Potion Shop - Middle', 'Potion Shop - Right']),
|
create_cave_region(player, 'Potion Shop', 'the potion shop', ['Potion Shop', 'Potion Shop - Left', 'Potion Shop - Middle', 'Potion Shop - Right']),
|
||||||
create_cave_region(player, 'Kakariko Well (top)', 'a drop\'s exit', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
create_cave_region(player, 'Kakariko Well (top)', 'a drop', ['Kakariko Well - Left', 'Kakariko Well - Middle', 'Kakariko Well - Right',
|
||||||
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
|
'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)', 'Kakariko Well (top to back)']),
|
||||||
|
create_cave_region(player, 'Kakariko Well (back)', 'a drop', ['Kakariko Well - Top']),
|
||||||
create_cave_region(player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
create_cave_region(player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
||||||
create_cave_region(player, 'Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top", "Blind\'s Hideout - Left",
|
create_cave_region(player, 'Blinds Hideout', 'a bounty of five items', ["Blind's Hideout - Left", "Blind's Hideout - Right",
|
||||||
"Blind\'s Hideout - Right", "Blind\'s Hideout - Far Left", "Blind\'s Hideout - Far Right"]),
|
"Blind's Hideout - Far Left", "Blind's Hideout - Far Right"], ['Blinds Hideout N']),
|
||||||
|
create_cave_region(player, 'Blinds Hideout (Top)', 'a bounty of five items', ["Blind's Hideout - Top"]),
|
||||||
create_cave_region(player, 'Elder House', 'a connector', None, ['Elder House Exit (East)', 'Elder House Exit (West)']),
|
create_cave_region(player, 'Elder House', 'a connector', None, ['Elder House Exit (East)', 'Elder House Exit (West)']),
|
||||||
create_cave_region(player, 'Snitch Lady (West)', 'a boring house'),
|
create_cave_region(player, 'Snitch Lady (West)', 'a boring house'),
|
||||||
create_cave_region(player, 'Snitch Lady (East)', 'a boring house'),
|
create_cave_region(player, 'Snitch Lady (East)', 'a boring house'),
|
||||||
@@ -276,7 +281,7 @@ def create_regions(world, player):
|
|||||||
create_cave_region(player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']),
|
create_cave_region(player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']),
|
||||||
create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith'], ['Missing Smith']),
|
create_cave_region(player, 'Blacksmiths Hut', 'the smith', ['Blacksmith'], ['Missing Smith']),
|
||||||
create_cave_region(player, 'Missing Smith', None, ['Missing Smith']),
|
create_cave_region(player, 'Missing Smith', None, ['Missing Smith']),
|
||||||
create_cave_region(player, 'Bat Cave (right)', 'a drop\'s exit', ['Magic Bat'], ['Bat Cave Door']),
|
create_cave_region(player, 'Bat Cave (right)', 'a drop', ['Magic Bat'], ['Bat Cave Door']),
|
||||||
create_cave_region(player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
create_cave_region(player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
||||||
create_cave_region(player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']),
|
create_cave_region(player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']),
|
||||||
create_cave_region(player, 'Library', 'the library', ['Library']),
|
create_cave_region(player, 'Library', 'the library', ['Library']),
|
||||||
@@ -305,8 +310,10 @@ def create_regions(world, player):
|
|||||||
create_cave_region(player, 'Dark World Lumberjack Shop', 'a common shop', ['Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right']),
|
create_cave_region(player, 'Dark World Lumberjack Shop', 'a common shop', ['Dark Lumberjack Shop - Left', 'Dark Lumberjack Shop - Middle', 'Dark Lumberjack Shop - Right']),
|
||||||
create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']),
|
create_cave_region(player, 'Spike Cave', 'Spike Cave', ['Spike Cave']),
|
||||||
create_cave_region(player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'),
|
create_cave_region(player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'),
|
||||||
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'],
|
create_cave_region(player, 'Hookshot Cave (Front)', 'a connector', None,
|
||||||
['Hookshot Cave Front to Middle', 'Hookshot Cave Front Exit']),
|
['Hookshot Cave Front to Middle', 'Hookshot Cave Front Exit', 'Hookshot Cave Bonk Path', 'Hookshot Cave Hook Path']),
|
||||||
|
create_cave_region(player, 'Hookshot Cave (Bonk Islands)', 'a connector', ['Hookshot Cave - Bottom Right']),
|
||||||
|
create_cave_region(player, 'Hookshot Cave (Hook Islands)', 'a connector', ['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Left']),
|
||||||
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 (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_cave_region(player, 'Hookshot Cave (Middle)', 'a connector', None, ['Hookshot Cave Middle to Back', 'Hookshot Cave Middle to Front']),
|
||||||
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 (Top)', 'a connector', ['Superbunny Cave - Top', 'Superbunny Cave - Bottom'], ['Superbunny Cave Exit (Top)']),
|
||||||
@@ -502,7 +509,8 @@ def create_dungeon_regions(world, player):
|
|||||||
create_dungeon_region(player, 'Hera Startile Wide - Crystal', 'Tower of Hera', None, ['Hera Startile Wide Crystal Exit']),
|
create_dungeon_region(player, 'Hera Startile Wide - Crystal', 'Tower of Hera', None, ['Hera Startile Wide Crystal Exit']),
|
||||||
create_dungeon_region(player, 'Hera 4F', 'Tower of Hera', ['Tower of Hera - Compass Chest'], ['Hera 4F Down Stairs', 'Hera 4F Up Stairs', 'Hera Big Chest Hook Path', 'Hera 4F Holes']),
|
create_dungeon_region(player, 'Hera 4F', 'Tower of Hera', ['Tower of Hera - Compass Chest'], ['Hera 4F Down Stairs', 'Hera 4F Up Stairs', 'Hera Big Chest Hook Path', 'Hera 4F Holes']),
|
||||||
create_dungeon_region(player, 'Hera Big Chest Landing', 'Tower of Hera', ['Tower of Hera - Big Chest'], ['Hera Big Chest Landing Exit', 'Hera Big Chest Landing Holes']),
|
create_dungeon_region(player, 'Hera Big Chest Landing', 'Tower of Hera', ['Tower of Hera - Big Chest'], ['Hera Big Chest Landing Exit', 'Hera Big Chest Landing Holes']),
|
||||||
create_dungeon_region(player, 'Hera 5F', 'Tower of Hera', None, ['Hera 5F Down Stairs', 'Hera 5F Up Stairs', 'Hera 5F Star Hole', 'Hera 5F Pothole Chain', 'Hera 5F Normal Holes']),
|
create_dungeon_region(player, 'Hera 5F', 'Tower of Hera', None, ['Hera 5F Down Stairs', 'Hera 5F Up Stairs', 'Hera 5F Star Hole', 'Hera 5F Pothole Chain', 'Hera 5F Normal Holes', 'Hera 5F Orange Path']),
|
||||||
|
create_dungeon_region(player, 'Hera 5F Pot Block', 'Tower of Hera', None),
|
||||||
create_dungeon_region(player, 'Hera Fairies', 'Tower of Hera', None, ['Hera Fairies\' Warp']),
|
create_dungeon_region(player, 'Hera Fairies', 'Tower of Hera', None, ['Hera Fairies\' Warp']),
|
||||||
create_dungeon_region(player, 'Hera Boss', 'Tower of Hera', ['Tower of Hera - Boss', 'Tower of Hera - Prize'], ['Hera Boss Down Stairs', 'Hera Boss Outer Hole', 'Hera Boss Inner Hole']),
|
create_dungeon_region(player, 'Hera Boss', 'Tower of Hera', ['Tower of Hera - Boss', 'Tower of Hera - Prize'], ['Hera Boss Down Stairs', 'Hera Boss Outer Hole', 'Hera Boss Inner Hole']),
|
||||||
|
|
||||||
@@ -590,7 +598,8 @@ def create_dungeon_regions(world, player):
|
|||||||
create_dungeon_region(player, 'Swamp Trench 1 Key Ledge', 'Swamp Palace', None, ['Swamp Trench 1 Key Ledge Dry', 'Swamp Trench 1 Key Approach', 'Swamp Trench 1 Key Ledge Depart', 'Swamp Trench 1 Key Ledge NW']),
|
create_dungeon_region(player, 'Swamp Trench 1 Key Ledge', 'Swamp Palace', None, ['Swamp Trench 1 Key Ledge Dry', 'Swamp Trench 1 Key Approach', 'Swamp Trench 1 Key Ledge Depart', 'Swamp Trench 1 Key Ledge NW']),
|
||||||
create_dungeon_region(player, 'Swamp Trench 1 Departure', 'Swamp Palace', None, ['Swamp Trench 1 Departure Dry', 'Swamp Trench 1 Departure Approach', 'Swamp Trench 1 Departure Key', 'Swamp Trench 1 Departure WS']),
|
create_dungeon_region(player, 'Swamp Trench 1 Departure', 'Swamp Palace', None, ['Swamp Trench 1 Departure Dry', 'Swamp Trench 1 Departure Approach', 'Swamp Trench 1 Departure Key', 'Swamp Trench 1 Departure WS']),
|
||||||
create_dungeon_region(player, 'Swamp Hammer Switch', 'Swamp Palace', ['Trench 1 Switch'], ['Swamp Hammer Switch SW', 'Swamp Hammer Switch WN']),
|
create_dungeon_region(player, 'Swamp Hammer Switch', 'Swamp Palace', ['Trench 1 Switch'], ['Swamp Hammer Switch SW', 'Swamp Hammer Switch WN']),
|
||||||
create_dungeon_region(player, 'Swamp Hub', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Hookshot Pot Key'], ['Swamp Hub ES', 'Swamp Hub S', 'Swamp Hub WS', 'Swamp Hub WN', 'Swamp Hub Hook Path']),
|
create_dungeon_region(player, 'Swamp Hub', 'Swamp Palace', ['Swamp Palace - Big Chest'], ['Swamp Hub ES', 'Swamp Hub S', 'Swamp Hub WS', 'Swamp Hub WN', 'Swamp Hub Hook Path', 'Swamp Hub Side Hook Path']),
|
||||||
|
create_dungeon_region(player, 'Swamp Hub Side Ledges', 'Swamp Palace', ['Swamp Palace - Hookshot Pot Key']),
|
||||||
create_dungeon_region(player, 'Swamp Hub Dead Ledge', 'Swamp Palace', None, ['Swamp Hub Dead Ledge EN']),
|
create_dungeon_region(player, 'Swamp Hub Dead Ledge', 'Swamp Palace', None, ['Swamp Hub Dead Ledge EN']),
|
||||||
create_dungeon_region(player, 'Swamp Hub North Ledge', 'Swamp Palace', None, ['Swamp Hub North Ledge N', 'Swamp Hub North Ledge Drop Down']),
|
create_dungeon_region(player, 'Swamp Hub North Ledge', 'Swamp Palace', None, ['Swamp Hub North Ledge N', 'Swamp Hub North Ledge Drop Down']),
|
||||||
create_dungeon_region(player, 'Swamp Donut Top', 'Swamp Palace', None, ['Swamp Donut Top N', 'Swamp Donut Top SE']),
|
create_dungeon_region(player, 'Swamp Donut Top', 'Swamp Palace', None, ['Swamp Donut Top N', 'Swamp Donut Top SE']),
|
||||||
@@ -645,8 +654,9 @@ def create_dungeon_regions(world, player):
|
|||||||
create_dungeon_region(player, 'Skull Big Key', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Big Key SW', 'Skull Big Key EN']),
|
create_dungeon_region(player, 'Skull Big Key', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Big Key SW', 'Skull Big Key EN']),
|
||||||
create_dungeon_region(player, 'Skull Lone Pot', 'Skull Woods', None, ['Skull Lone Pot WN']),
|
create_dungeon_region(player, 'Skull Lone Pot', 'Skull Woods', None, ['Skull Lone Pot WN']),
|
||||||
create_dungeon_region(player, 'Skull Small Hall', 'Skull Woods', None, ['Skull Small Hall ES', 'Skull Small Hall WS']),
|
create_dungeon_region(player, 'Skull Small Hall', 'Skull Woods', None, ['Skull Small Hall ES', 'Skull Small Hall WS']),
|
||||||
create_dungeon_region(player, 'Skull Back Drop', 'Skull Woods', None, ['Skull Back Drop Star Path']),
|
create_dungeon_region(player, 'Skull Back Drop', 'Skull Woods', ['Skull Star Tile'], ['Skull Back Drop Star Path']),
|
||||||
create_dungeon_region(player, 'Skull 2 West Lobby', 'Skull Woods', ['Skull Woods - West Lobby Pot Key'], ['Skull 2 West Lobby ES', 'Skull 2 West Lobby NW', 'Skull 2 West Lobby S']),
|
create_dungeon_region(player, 'Skull 2 West Lobby', 'Skull Woods', ['Skull Woods - West Lobby Pot Key'], ['Skull 2 West Lobby ES', 'Skull 2 West Lobby Pits', 'Skull 2 West Lobby S']),
|
||||||
|
create_dungeon_region(player, 'Skull 2 West Lobby Ledge', 'Skull Woods', None, ['Skull 2 West Lobby NW', 'Skull 2 West Lobby Ledge Pits']),
|
||||||
create_dungeon_region(player, 'Skull X Room', 'Skull Woods', None, ['Skull X Room SW']),
|
create_dungeon_region(player, 'Skull X Room', 'Skull Woods', None, ['Skull X Room SW']),
|
||||||
create_dungeon_region(player, 'Skull 3 Lobby', 'Skull Woods', None, ['Skull 3 Lobby NW', 'Skull 3 Lobby EN', 'Skull 3 Lobby SW']),
|
create_dungeon_region(player, 'Skull 3 Lobby', 'Skull Woods', None, ['Skull 3 Lobby NW', 'Skull 3 Lobby EN', 'Skull 3 Lobby SW']),
|
||||||
create_dungeon_region(player, 'Skull East Bridge', 'Skull Woods', None, ['Skull East Bridge WN', 'Skull East Bridge WS']),
|
create_dungeon_region(player, 'Skull East Bridge', 'Skull Woods', None, ['Skull East Bridge WN', 'Skull East Bridge WS']),
|
||||||
@@ -677,7 +687,8 @@ def create_dungeon_regions(world, player):
|
|||||||
create_dungeon_region(player, 'Thieves Hellway S Crystal', 'Thieves\' Town', None, ['Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway Crystal ES']),
|
create_dungeon_region(player, 'Thieves Hellway S Crystal', 'Thieves\' Town', None, ['Thieves Hellway Crystal Orange Barrier', 'Thieves Hellway Crystal ES']),
|
||||||
create_dungeon_region(player, 'Thieves Triple Bypass', 'Thieves\' Town', None, ['Thieves Triple Bypass WN', 'Thieves Triple Bypass EN', 'Thieves Triple Bypass SE']),
|
create_dungeon_region(player, 'Thieves Triple Bypass', 'Thieves\' Town', None, ['Thieves Triple Bypass WN', 'Thieves Triple Bypass EN', 'Thieves Triple Bypass SE']),
|
||||||
create_dungeon_region(player, 'Thieves Spike Switch', 'Thieves\' Town', ['Thieves\' Town - Spike Switch Pot Key'], ['Thieves Spike Switch SW', 'Thieves Spike Switch Up Stairs']),
|
create_dungeon_region(player, 'Thieves Spike Switch', 'Thieves\' Town', ['Thieves\' Town - Spike Switch Pot Key'], ['Thieves Spike Switch SW', 'Thieves Spike Switch Up Stairs']),
|
||||||
create_dungeon_region(player, 'Thieves Attic', 'Thieves\' Town', None, ['Thieves Attic Down Stairs', 'Thieves Attic ES', 'Thieves Attic Orange Barrier']),
|
create_dungeon_region(player, 'Thieves Attic', 'Thieves\' Town', None, ['Thieves Attic Down Stairs', 'Thieves Attic ES', 'Thieves Attic Orange Barrier', 'Thieves Attic Blue Barrier']),
|
||||||
|
create_dungeon_region(player, 'Thieves Attic Switch', 'Thieves\' Town', None, ['Thieves Attic Switch Blue Barrier']),
|
||||||
create_dungeon_region(player, 'Thieves Attic Hint', 'Thieves\' Town', None, ['Thieves Attic Hint Orange Barrier']),
|
create_dungeon_region(player, 'Thieves Attic Hint', 'Thieves\' Town', None, ['Thieves Attic Hint Orange Barrier']),
|
||||||
create_dungeon_region(player, 'Thieves Cricket Hall Left', 'Thieves\' Town', None, ['Thieves Cricket Hall Left WS', 'Thieves Cricket Hall Left Edge']),
|
create_dungeon_region(player, 'Thieves Cricket Hall Left', 'Thieves\' Town', None, ['Thieves Cricket Hall Left WS', 'Thieves Cricket Hall Left Edge']),
|
||||||
create_dungeon_region(player, 'Thieves Cricket Hall Right', 'Thieves\' Town', None, ['Thieves Cricket Hall Right Edge', 'Thieves Cricket Hall Right ES']),
|
create_dungeon_region(player, 'Thieves Cricket Hall Right', 'Thieves\' Town', None, ['Thieves Cricket Hall Right Edge', 'Thieves Cricket Hall Right ES']),
|
||||||
@@ -805,7 +816,8 @@ def create_dungeon_regions(world, player):
|
|||||||
create_dungeon_region(player, 'TR Main Lobby', 'Turtle Rock', None, ['TR Main Lobby Gap', 'TR Main Lobby SE']),
|
create_dungeon_region(player, 'TR Main Lobby', 'Turtle Rock', None, ['TR Main Lobby Gap', 'TR Main Lobby SE']),
|
||||||
create_dungeon_region(player, 'TR Lobby Ledge', 'Turtle Rock', None, ['TR Lobby Ledge NE', 'TR Lobby Ledge Gap']),
|
create_dungeon_region(player, 'TR Lobby Ledge', 'Turtle Rock', None, ['TR Lobby Ledge NE', 'TR Lobby Ledge Gap']),
|
||||||
create_dungeon_region(player, 'TR Compass Room', 'Turtle Rock', ['Turtle Rock - Compass Chest'], ['TR Compass Room NW']),
|
create_dungeon_region(player, 'TR Compass Room', 'Turtle Rock', ['Turtle Rock - Compass Chest'], ['TR Compass Room NW']),
|
||||||
create_dungeon_region(player, 'TR Hub', 'Turtle Rock', None, ['TR Hub SW', 'TR Hub SE', 'TR Hub ES', 'TR Hub EN', 'TR Hub NW', 'TR Hub NE']),
|
create_dungeon_region(player, 'TR Hub', 'Turtle Rock', None, ['TR Hub SW', 'TR Hub SE', 'TR Hub ES', 'TR Hub EN', 'TR Hub NW', 'TR Hub NE', 'TR Hub Path']),
|
||||||
|
create_dungeon_region(player, 'TR Hub Ledges', 'Turtle Rock', None, ['TR Hub Ledges Path']),
|
||||||
create_dungeon_region(player, 'TR Torches Ledge', 'Turtle Rock', None, ['TR Torches Ledge WS']),
|
create_dungeon_region(player, 'TR Torches Ledge', 'Turtle Rock', None, ['TR Torches Ledge WS']),
|
||||||
create_dungeon_region(player, 'TR Torches', 'Turtle Rock', None, ['TR Torches WN', 'TR Torches NW']),
|
create_dungeon_region(player, 'TR Torches', 'Turtle Rock', None, ['TR Torches WN', 'TR Torches NW']),
|
||||||
create_dungeon_region(player, 'TR Roller Room', 'Turtle Rock', ['Turtle Rock - Roller Room - Left', 'Turtle Rock - Roller Room - Right'], ['TR Roller Room SW']),
|
create_dungeon_region(player, 'TR Roller Room', 'Turtle Rock', ['Turtle Rock - Roller Room - Left', 'Turtle Rock - Roller Room - Right'], ['TR Roller Room SW']),
|
||||||
@@ -842,7 +854,8 @@ def create_dungeon_regions(world, player):
|
|||||||
create_dungeon_region(player, 'TR Crystaroller Chest', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['TR Crystaroller Chest to Middle Barrier - Blue']),
|
create_dungeon_region(player, 'TR Crystaroller Chest', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['TR Crystaroller Chest to Middle Barrier - Blue']),
|
||||||
create_dungeon_region(player, 'TR Crystaroller Middle - Ranged Crystal', 'Turtle Rock', None, ['TR Crystaroller Middle Ranged Crystal Exit']),
|
create_dungeon_region(player, 'TR Crystaroller Middle - Ranged Crystal', 'Turtle Rock', None, ['TR Crystaroller Middle Ranged Crystal Exit']),
|
||||||
create_dungeon_region(player, 'TR Crystaroller Bottom - Ranged Crystal', 'Turtle Rock', None, ['TR Crystaroller Bottom Ranged Crystal Exit']),
|
create_dungeon_region(player, 'TR Crystaroller Bottom - Ranged Crystal', 'Turtle Rock', None, ['TR Crystaroller Bottom Ranged Crystal Exit']),
|
||||||
create_dungeon_region(player, 'TR Dark Ride', 'Turtle Rock', None, ['TR Dark Ride Up Stairs', 'TR Dark Ride SW']),
|
create_dungeon_region(player, 'TR Dark Ride', 'Turtle Rock', None, ['TR Dark Ride Up Stairs', 'TR Dark Ride SW', 'TR Dark Ride Path']),
|
||||||
|
create_dungeon_region(player, 'TR Dark Ride Ledges', 'Turtle Rock', None, ['TR Dark Ride Ledges Path']),
|
||||||
create_dungeon_region(player, 'TR Dash Bridge', 'Turtle Rock', None, ['TR Dash Bridge NW', 'TR Dash Bridge SW', 'TR Dash Bridge WS']),
|
create_dungeon_region(player, 'TR Dash Bridge', 'Turtle Rock', None, ['TR Dash Bridge NW', 'TR Dash Bridge SW', 'TR Dash Bridge WS']),
|
||||||
create_dungeon_region(player, 'TR Eye Bridge', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
|
create_dungeon_region(player, 'TR Eye Bridge', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
|
||||||
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
|
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
|
||||||
@@ -852,7 +865,8 @@ def create_dungeon_regions(world, player):
|
|||||||
create_dungeon_region(player, 'TR Crystal Maze Interior', 'Turtle Rock', None, ['TR Crystal Maze Interior to End Barrier - Blue', 'TR Crystal Maze Interior to Start Barrier - Blue', 'TR Crystal Maze Interior to Start Bypass', 'TR Crystal Maze Interior to End Bypass']),
|
create_dungeon_region(player, 'TR Crystal Maze Interior', 'Turtle Rock', None, ['TR Crystal Maze Interior to End Barrier - Blue', 'TR Crystal Maze Interior to Start Barrier - Blue', 'TR Crystal Maze Interior to Start Bypass', 'TR Crystal Maze Interior to End Bypass']),
|
||||||
create_dungeon_region(player, 'TR Crystal Maze End', 'Turtle Rock', None, ['TR Crystal Maze North Stairs', 'TR Crystal Maze End to Interior Barrier - Blue', 'TR Crystal Maze End to Ranged Crystal']),
|
create_dungeon_region(player, 'TR Crystal Maze End', 'Turtle Rock', None, ['TR Crystal Maze North Stairs', 'TR Crystal Maze End to Interior Barrier - Blue', 'TR Crystal Maze End to Ranged Crystal']),
|
||||||
create_dungeon_region(player, 'TR Crystal Maze End - Ranged Crystal', 'Turtle Rock', None, ['TR Crystal Maze End Ranged Crystal Exit']),
|
create_dungeon_region(player, 'TR Crystal Maze End - Ranged Crystal', 'Turtle Rock', None, ['TR Crystal Maze End Ranged Crystal Exit']),
|
||||||
create_dungeon_region(player, 'TR Final Abyss', 'Turtle Rock', None, ['TR Final Abyss South Stairs', 'TR Final Abyss NW']),
|
create_dungeon_region(player, 'TR Final Abyss Balcony', 'Turtle Rock', None, ['TR Final Abyss South Stairs', 'TR Final Abyss Balcony Path']),
|
||||||
|
create_dungeon_region(player, 'TR Final Abyss Ledge', 'Turtle Rock', None, ['TR Final Abyss NW', 'TR Final Abyss Ledge Path']),
|
||||||
create_dungeon_region(player, 'TR Boss', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize'], ['TR Boss SW']),
|
create_dungeon_region(player, 'TR Boss', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize'], ['TR Boss SW']),
|
||||||
|
|
||||||
# gt
|
# gt
|
||||||
@@ -877,9 +891,10 @@ def create_dungeon_regions(world, player):
|
|||||||
create_dungeon_region(player, 'GT Invisible Bridges', 'Ganon\'s Tower', None, ['GT Invisible Bridges WS']),
|
create_dungeon_region(player, 'GT Invisible Bridges', 'Ganon\'s Tower', None, ['GT Invisible Bridges WS']),
|
||||||
create_dungeon_region(player, 'GT Invisible Catwalk', 'Ganon\'s Tower', None, ['GT Invisible Catwalk ES', 'GT Invisible Catwalk WS', 'GT Invisible Catwalk NW', 'GT Invisible Catwalk NE']),
|
create_dungeon_region(player, 'GT Invisible Catwalk', 'Ganon\'s Tower', None, ['GT Invisible Catwalk ES', 'GT Invisible Catwalk WS', 'GT Invisible Catwalk NW', 'GT Invisible Catwalk NE']),
|
||||||
create_dungeon_region(player, 'GT Conveyor Cross', 'Ganon\'s Tower', ['Ganons Tower - Conveyor Cross Pot Key'], ['GT Conveyor Cross EN', 'GT Conveyor Cross WN']),
|
create_dungeon_region(player, 'GT Conveyor Cross', 'Ganon\'s Tower', ['Ganons Tower - Conveyor Cross Pot Key'], ['GT Conveyor Cross EN', 'GT Conveyor Cross WN']),
|
||||||
create_dungeon_region(player, 'GT Hookshot East Platform', 'Ganon\'s Tower', None, ['GT Hookshot EN', 'GT Hookshot East-North Path', 'GT Hookshot East-South Path']),
|
create_dungeon_region(player, 'GT Hookshot East Platform', 'Ganon\'s Tower', None, ['GT Hookshot EN', 'GT Hookshot East-Mid Path']),
|
||||||
create_dungeon_region(player, 'GT Hookshot North Platform', 'Ganon\'s Tower', None, ['GT Hookshot NW', 'GT Hookshot North-East Path', 'GT Hookshot North-South Path']),
|
create_dungeon_region(player, 'GT Hookshot Mid Platform', 'Ganon\'s Tower', None, ['GT Hookshot Mid-East Path', 'GT Hookshot Mid-South Path', 'GT Hookshot Mid-North Path']),
|
||||||
create_dungeon_region(player, 'GT Hookshot South Platform', 'Ganon\'s Tower', None, ['GT Hookshot ES', 'GT Hookshot South-East Path', 'GT Hookshot South-North Path', 'GT Hookshot Platform Blue Barrier', 'GT Hookshot Platform Barrier Bypass']),
|
create_dungeon_region(player, 'GT Hookshot North Platform', 'Ganon\'s Tower', None, ['GT Hookshot NW', 'GT Hookshot North-Mid Path']),
|
||||||
|
create_dungeon_region(player, 'GT Hookshot South Platform', 'Ganon\'s Tower', None, ['GT Hookshot ES', 'GT Hookshot South-Mid Path', 'GT Hookshot Platform Blue Barrier', 'GT Hookshot Platform Barrier Bypass']),
|
||||||
create_dungeon_region(player, 'GT Hookshot South Entry', 'Ganon\'s Tower', None, ['GT Hookshot SW', 'GT Hookshot Entry Blue Barrier', 'GT Hookshot South Entry to Ranged Crystal']),
|
create_dungeon_region(player, 'GT Hookshot South Entry', 'Ganon\'s Tower', None, ['GT Hookshot SW', 'GT Hookshot Entry Blue Barrier', 'GT Hookshot South Entry to Ranged Crystal']),
|
||||||
create_dungeon_region(player, 'GT Hookshot South Entry - Ranged Crystal', 'Ganon\'s Tower', None, ['GT HookShot South Entry Ranged Crystal Exit']),
|
create_dungeon_region(player, 'GT Hookshot South Entry - Ranged Crystal', 'Ganon\'s Tower', None, ['GT HookShot South Entry Ranged Crystal Exit']),
|
||||||
create_dungeon_region(player, 'GT Map Room', 'Ganon\'s Tower', ['Ganons Tower - Map Chest'], ['GT Map Room WS']),
|
create_dungeon_region(player, 'GT Map Room', 'Ganon\'s Tower', ['Ganons Tower - Map Chest'], ['GT Map Room WS']),
|
||||||
@@ -1145,42 +1160,111 @@ def create_shops(world, player):
|
|||||||
|
|
||||||
|
|
||||||
def adjust_locations(world, player):
|
def adjust_locations(world, player):
|
||||||
if world.keydropshuffle[player]:
|
# handle pots
|
||||||
for location in key_drop_data.keys():
|
world.pot_contents[player] = PotSecretTable()
|
||||||
|
for location, datum in key_drop_data.items():
|
||||||
loc = world.get_location(location, player)
|
loc = world.get_location(location, player)
|
||||||
|
drop_location = 'Drop' == datum[0]
|
||||||
|
if drop_location:
|
||||||
|
loc.type = LocationType.Drop
|
||||||
|
snes_address, room, sprite_idx = datum[1]
|
||||||
|
loc.address = snes_address
|
||||||
|
else:
|
||||||
|
loc.type = LocationType.Pot
|
||||||
|
pot, pot_index = next((p, i) for i, p in enumerate(vanilla_pots[datum[1]]) if p.item == PotItem.Key)
|
||||||
|
pot = pot.copy()
|
||||||
|
loc.address = pot_address(pot_index, datum[1])
|
||||||
|
loc.pot = pot
|
||||||
|
pot.location = loc
|
||||||
|
if (not world.dropshuffle[player] and drop_location)\
|
||||||
|
or (not drop_location and world.pottery[player] in ['none', 'cave']):
|
||||||
|
loc.skip = True
|
||||||
|
else:
|
||||||
key_item = loc.item
|
key_item = loc.item
|
||||||
key_item.location = None
|
key_item.location = None
|
||||||
|
|
||||||
loc.forced_item = None
|
loc.forced_item = None
|
||||||
loc.item = None
|
loc.item = None
|
||||||
loc.event = False
|
loc.event = False
|
||||||
loc.address = key_drop_data[location][1]
|
item_dungeon = key_item.dungeon
|
||||||
loc.player_address = key_drop_data[location][0]
|
|
||||||
|
|
||||||
item_dungeon = key_item.name.split('(')[1][:-1]
|
|
||||||
item_dungeon = 'Hyrule Castle' if item_dungeon == 'Escape' else item_dungeon
|
|
||||||
dungeon = world.get_dungeon(item_dungeon, player)
|
dungeon = world.get_dungeon(item_dungeon, player)
|
||||||
if key_item.smallkey and not world.retro[player]:
|
if key_item.smallkey and not world.retro[player]:
|
||||||
dungeon.small_keys.append(key_item)
|
dungeon.small_keys.append(key_item)
|
||||||
elif key_item.bigkey:
|
elif key_item.bigkey:
|
||||||
dungeon.big_key = key_item
|
dungeon.big_key = key_item
|
||||||
|
world.pot_pool[player] = choose_pots(world, player)
|
||||||
|
for super_tile, pot_list in vanilla_pots.items():
|
||||||
|
for pot_index, pot_orig in enumerate(pot_list):
|
||||||
|
if pot_orig.item == PotItem.Key:
|
||||||
|
loc = next(location for location, datum in key_drop_data.items() if datum[1] == super_tile)
|
||||||
|
pot = world.get_location(loc, player).pot
|
||||||
|
else:
|
||||||
|
pot = pot_orig.copy()
|
||||||
|
world.pot_contents[player].room_map[super_tile].append(pot)
|
||||||
|
|
||||||
|
if valid_pot_location(pot, world.pot_pool[player], world, player):
|
||||||
|
create_pot_location(pot, pot_index, super_tile, world, player)
|
||||||
if world.shopsanity[player]:
|
if world.shopsanity[player]:
|
||||||
index = 0
|
index = 0
|
||||||
for shop, location_list in shop_to_location_table.items():
|
for shop, location_list in shop_to_location_table.items():
|
||||||
for location in location_list:
|
for location in location_list:
|
||||||
world.get_location(location, player).address = 0x400000 + index
|
loc = world.get_location(location, player)
|
||||||
|
loc.address = 0x400000 + index
|
||||||
|
loc.type = LocationType.Shop
|
||||||
# player address? it is in the shop table
|
# player address? it is in the shop table
|
||||||
index += 1
|
index += 1
|
||||||
# unreal events:
|
# unreal events:
|
||||||
for l in ['Ganon', 'Agahnim 1', 'Agahnim 2', 'Dark Blacksmith Ruins', 'Middle Aged Man',
|
for l in ['Ganon', 'Agahnim 1', 'Agahnim 2', 'Dark Blacksmith Ruins', 'Middle Aged Man',
|
||||||
'Frog', 'Missing Smith', 'Floodgate', 'Trench 1 Switch', 'Trench 2 Switch', 'Swamp Drain',
|
'Frog', 'Missing Smith', 'Floodgate', 'Trench 1 Switch', 'Trench 2 Switch', 'Swamp Drain',
|
||||||
'Attic Cracked Floor', 'Suspicious Maiden', 'Revealing Light', 'Big Bomb', 'Pyramid Crack',
|
'Attic Cracked Floor', 'Suspicious Maiden', 'Revealing Light', 'Big Bomb', 'Pyramid Crack',
|
||||||
'Ice Block Drop', 'Zelda Pickup', 'Zelda Drop Off']:
|
'Ice Block Drop', 'Zelda Pickup', 'Zelda Drop Off', 'Skull Star Tile']:
|
||||||
location = world.get_location_unsafe(l, player)
|
location = world.get_location_unsafe(l, player)
|
||||||
if location:
|
if location:
|
||||||
|
location.type = LocationType.Logical
|
||||||
location.real = False
|
location.real = False
|
||||||
|
if l not in ['Ganon', 'Agahnim 1', 'Agahnim 2']:
|
||||||
|
location.skip = True
|
||||||
|
|
||||||
|
|
||||||
|
def valid_pot_location(pot, pot_set, world, player):
|
||||||
|
if world.pottery[player] == 'lottery':
|
||||||
|
return True
|
||||||
|
if world.pottery[player] == 'nonempty' and pot.item != PotItem.Nothing:
|
||||||
|
return True
|
||||||
|
if world.pottery[player] in ['reduced', 'clustered'] and pot in pot_set:
|
||||||
|
return True
|
||||||
|
if world.pottery[player] == 'dungeon' and world.get_region(pot.room, player).type == RegionType.Dungeon:
|
||||||
|
return True
|
||||||
|
if world.pottery[player] in ['cave', 'cavekeys'] and world.get_region(pot.room, player).type == RegionType.Cave:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def create_pot_location(pot, pot_index, super_tile, world, player):
|
||||||
|
if (pot.item not in [PotItem.Key, PotItem.Hole]
|
||||||
|
and (pot.item != PotItem.Switch or (world.potshuffle[player]
|
||||||
|
and world.pottery[player] not in ['none', 'cave', 'keys', 'cavekeys']))):
|
||||||
|
address = pot_address(pot_index, super_tile)
|
||||||
|
region = pot.room
|
||||||
|
parent = world.get_region(region, player)
|
||||||
|
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||||
|
hint_text = ('under a block' if pot.flags & PotFlags.Block else 'in a pot')
|
||||||
|
modifier = parent.hint_text not in {'a storyteller', 'fairies deep in a cave', 'a spiky hint',
|
||||||
|
'a bounty of five items', 'the sick kid', 'Sahasrahla'}
|
||||||
|
hint_text = f'{hint_text} {"in" if modifier else "near"} {parent.hint_text}'
|
||||||
|
pot_location = Location(player, f'{pot.room} {descriptor}', address, hint_text=hint_text,
|
||||||
|
parent=parent)
|
||||||
|
world.dynamic_locations.append(pot_location)
|
||||||
|
pot_location.pot = pot
|
||||||
|
pot.location = pot_location
|
||||||
|
|
||||||
|
pot_location.type = LocationType.Pot
|
||||||
|
parent.locations.append(pot_location)
|
||||||
|
|
||||||
|
|
||||||
|
def pot_address(pot_index, super_tile):
|
||||||
|
return 0x7f6018 + super_tile * 2 + (pot_index << 24)
|
||||||
|
|
||||||
|
|
||||||
# (type, room_id, shopkeeper, custom, locked, [items])
|
# (type, room_id, shopkeeper, custom, locked, [items])
|
||||||
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
# item = (item, price, max=0, replacement=None, replacement_price=0)
|
||||||
@@ -1232,42 +1316,6 @@ shop_table_by_location_id = {0x400000+cnt: x for cnt, x in enumerate(flat_normal
|
|||||||
shop_table_by_location_id = {**shop_table_by_location_id, **{0x400020+cnt: x for cnt, x in enumerate(flat_retro_shops)}}
|
shop_table_by_location_id = {**shop_table_by_location_id, **{0x400020+cnt: x for cnt, x in enumerate(flat_retro_shops)}}
|
||||||
shop_table_by_location = {y: x for x, y in shop_table_by_location_id.items()}
|
shop_table_by_location = {y: x for x, y in shop_table_by_location_id.items()}
|
||||||
|
|
||||||
key_drop_data = {
|
|
||||||
'Hyrule Castle - Map Guard Key Drop': [0x140036, 0x140037, 'in Hyrule Castle', 'Small Key (Escape)'],
|
|
||||||
'Hyrule Castle - Boomerang Guard Key Drop': [0x140033, 0x140034, 'in Hyrule Castle', 'Small Key (Escape)'],
|
|
||||||
'Hyrule Castle - Key Rat Key Drop': [0x14000c, 0x14000d, 'in Hyrule Castle', 'Small Key (Escape)'],
|
|
||||||
'Hyrule Castle - Big Key Drop': [0x14003c, 0x14003d, 'in Hyrule Castle', 'Big Key (Escape)'],
|
|
||||||
'Eastern Palace - Dark Square Pot Key': [0x14005a, 0x14005b, 'in Eastern Palace', 'Small Key (Eastern Palace)'],
|
|
||||||
'Eastern Palace - Dark Eyegore Key Drop': [0x140048, 0x140049, 'in Eastern Palace', 'Small Key (Eastern Palace)'],
|
|
||||||
'Desert Palace - Desert Tiles 1 Pot Key': [0x140030, 0x140031, 'in Desert Palace', 'Small Key (Desert Palace)'],
|
|
||||||
'Desert Palace - Beamos Hall Pot Key': [0x14002a, 0x14002b, 'in Desert Palace', 'Small Key (Desert Palace)'],
|
|
||||||
'Desert Palace - Desert Tiles 2 Pot Key': [0x140027, 0x140028, 'in Desert Palace', 'Small Key (Desert Palace)'],
|
|
||||||
'Castle Tower - Dark Archer Key Drop': [0x140060, 0x140061, 'in Castle Tower', 'Small Key (Agahnims Tower)'],
|
|
||||||
'Castle Tower - Circle of Pots Key Drop': [0x140051, 0x140052, 'in Castle Tower', 'Small Key (Agahnims Tower)'],
|
|
||||||
'Swamp Palace - Pot Row Pot Key': [0x140018, 0x140019, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
|
||||||
'Swamp Palace - Trench 1 Pot Key': [0x140015, 0x140016, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
|
||||||
'Swamp Palace - Hookshot Pot Key': [0x140012, 0x140013, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
|
||||||
'Swamp Palace - Trench 2 Pot Key': [0x14000f, 0x140010, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
|
||||||
'Swamp Palace - Waterway Pot Key': [0x140009, 0x14000a, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
|
||||||
'Skull Woods - West Lobby Pot Key': [0x14002d, 0x14002e, 'in Skull Woods', 'Small Key (Skull Woods)'],
|
|
||||||
'Skull Woods - Spike Corner Key Drop': [0x14001b, 0x14001c, 'near Mothula', 'Small Key (Skull Woods)'],
|
|
||||||
"Thieves' Town - Hallway Pot Key": [0x14005d, 0x14005e, "in Thieves Town", 'Small Key (Thieves Town)'],
|
|
||||||
"Thieves' Town - Spike Switch Pot Key": [0x14004e, 0x14004f, "in Thieves Town", 'Small Key (Thieves Town)'],
|
|
||||||
'Ice Palace - Jelly Key Drop': [0x140003, 0x140004, 'in Ice Palace', 'Small Key (Ice Palace)'],
|
|
||||||
'Ice Palace - Conveyor Key Drop': [0x140021, 0x140022, 'in Ice Palace', 'Small Key (Ice Palace)'],
|
|
||||||
'Ice Palace - Hammer Block Key Drop': [0x140024, 0x140025, 'in Ice Palace', 'Small Key (Ice Palace)'],
|
|
||||||
'Ice Palace - Many Pots Pot Key': [0x140045, 0x140046, 'in Ice Palace', 'Small Key (Ice Palace)'],
|
|
||||||
'Misery Mire - Spikes Pot Key': [0x140054, 0x140055 , 'in Misery Mire', 'Small Key (Misery Mire)'],
|
|
||||||
'Misery Mire - Fishbone Pot Key': [0x14004b, 0x14004c, 'in forgotten Mire', 'Small Key (Misery Mire)'],
|
|
||||||
'Misery Mire - Conveyor Crystal Key Drop': [0x140063, 0x140064 , 'in Misery Mire', 'Small Key (Misery Mire)'],
|
|
||||||
'Turtle Rock - Pokey 1 Key Drop': [0x140057, 0x140058, 'in Turtle Rock', 'Small Key (Turtle Rock)'],
|
|
||||||
'Turtle Rock - Pokey 2 Key Drop': [0x140006, 0x140007, 'in Turtle Rock', 'Small Key (Turtle Rock)'],
|
|
||||||
'Ganons Tower - Conveyor Cross Pot Key': [0x14003f, 0x140040, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
|
||||||
'Ganons Tower - Double Switch Pot Key': [0x140042, 0x140043, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
|
||||||
'Ganons Tower - Conveyor Star Pits Pot Key': [0x140039, 0x14003a, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
|
||||||
'Ganons Tower - Mini Helmasaur Key Drop': [0x14001e, 0x14001f, "atop Ganon's Tower", 'Small Key (Ganons Tower)']
|
|
||||||
}
|
|
||||||
|
|
||||||
dungeon_events = [
|
dungeon_events = [
|
||||||
'Trench 1 Switch',
|
'Trench 1 Switch',
|
||||||
'Trench 2 Switch',
|
'Trench 2 Switch',
|
||||||
@@ -1276,6 +1324,7 @@ dungeon_events = [
|
|||||||
'Suspicious Maiden',
|
'Suspicious Maiden',
|
||||||
'Revealing Light',
|
'Revealing Light',
|
||||||
'Ice Block Drop',
|
'Ice Block Drop',
|
||||||
|
'Skull Star Tile',
|
||||||
'Zelda Pickup',
|
'Zelda Pickup',
|
||||||
'Zelda Drop Off'
|
'Zelda Drop Off'
|
||||||
]
|
]
|
||||||
@@ -1517,18 +1566,19 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
|||||||
'Suspicious Maiden': (None, None, False, None),
|
'Suspicious Maiden': (None, None, False, None),
|
||||||
'Revealing Light': (None, None, False, None),
|
'Revealing Light': (None, None, False, None),
|
||||||
'Ice Block Drop': (None, None, False, None),
|
'Ice Block Drop': (None, None, False, None),
|
||||||
|
'Skull Star Tile': (None, None, False, None),
|
||||||
'Zelda Pickup': (None, None, False, None),
|
'Zelda Pickup': (None, None, False, None),
|
||||||
'Zelda Drop Off': (None, None, False, None),
|
'Zelda Drop Off': (None, None, False, None),
|
||||||
'Eastern Palace - Prize': ([0x1209D, 0x53E76, 0x53E77, 0x180052, 0x180070, 0xC6FE], None, True, 'Eastern Palace'),
|
'Eastern Palace - Prize': ([0x1209D, 0x53E76, 0x53E77, 0x180052, 0x180070, 0xC6FE, 0x186FE2], None, True, 'Eastern Palace'),
|
||||||
'Desert Palace - Prize': ([0x1209E, 0x53E7A, 0x53E7B, 0x180053, 0x180072, 0xC6FF], None, True, 'Desert Palace'),
|
'Desert Palace - Prize': ([0x1209E, 0x53E7A, 0x53E7B, 0x180053, 0x180072, 0xC6FF, 0x186FE3], None, True, 'Desert Palace'),
|
||||||
'Tower of Hera - Prize': ([0x120A5, 0x53E78, 0x53E79, 0x18005A, 0x180071, 0xC706], None, True, 'Tower of Hera'),
|
'Tower of Hera - Prize': ([0x120A5, 0x53E78, 0x53E79, 0x18005A, 0x180071, 0xC706, 0x186FEA], None, True, 'Tower of Hera'),
|
||||||
'Palace of Darkness - Prize': ([0x120A1, 0x53E7C, 0x53E7D, 0x180056, 0x180073, 0xC702], None, True, 'Palace of Darkness'),
|
'Palace of Darkness - Prize': ([0x120A1, 0x53E7C, 0x53E7D, 0x180056, 0x180073, 0xC702, 0x186FE6], None, True, 'Palace of Darkness'),
|
||||||
'Swamp Palace - Prize': ([0x120A0, 0x53E88, 0x53E89, 0x180055, 0x180079, 0xC701], None, True, 'Swamp Palace'),
|
'Swamp Palace - Prize': ([0x120A0, 0x53E88, 0x53E89, 0x180055, 0x180079, 0xC701, 0x186FE5], None, True, 'Swamp Palace'),
|
||||||
'Thieves\' Town - Prize': ([0x120A6, 0x53E82, 0x53E83, 0x18005B, 0x180076, 0xC707], None, True, 'Thieves\' Town'),
|
'Thieves\' Town - Prize': ([0x120A6, 0x53E82, 0x53E83, 0x18005B, 0x180076, 0xC707, 0x186FEB], None, True, 'Thieves Town'),
|
||||||
'Skull Woods - Prize': ([0x120A3, 0x53E7E, 0x53E7F, 0x180058, 0x180074, 0xC704], None, True, 'Skull Woods'),
|
'Skull Woods - Prize': ([0x120A3, 0x53E7E, 0x53E7F, 0x180058, 0x180074, 0xC704, 0x186FE8], None, True, 'Skull Woods'),
|
||||||
'Ice Palace - Prize': ([0x120A4, 0x53E86, 0x53E87, 0x180059, 0x180078, 0xC705], None, True, 'Ice Palace'),
|
'Ice Palace - Prize': ([0x120A4, 0x53E86, 0x53E87, 0x180059, 0x180078, 0xC705, 0x186FE9], None, True, 'Ice Palace'),
|
||||||
'Misery Mire - Prize': ([0x120A2, 0x53E84, 0x53E85, 0x180057, 0x180077, 0xC703], None, True, 'Misery Mire'),
|
'Misery Mire - Prize': ([0x120A2, 0x53E84, 0x53E85, 0x180057, 0x180077, 0xC703, 0x186FE7], None, True, 'Misery Mire'),
|
||||||
'Turtle Rock - Prize': ([0x120A7, 0x53E80, 0x53E81, 0x18005C, 0x180075, 0xC708], None, True, 'Turtle Rock'),
|
'Turtle Rock - Prize': ([0x120A7, 0x53E80, 0x53E81, 0x18005C, 0x180075, 0xC708, 0x186FEC], None, True, 'Turtle Rock'),
|
||||||
'Kakariko Shop - Left': (None, None, False, 'for sale in Kakariko'),
|
'Kakariko Shop - Left': (None, None, False, 'for sale in Kakariko'),
|
||||||
'Kakariko Shop - Middle': (None, None, False, 'for sale in Kakariko'),
|
'Kakariko Shop - Middle': (None, None, False, 'for sale in Kakariko'),
|
||||||
'Kakariko Shop - Right': (None, None, False, 'for sale in Kakariko'),
|
'Kakariko Shop - Right': (None, None, False, 'for sale in Kakariko'),
|
||||||
@@ -1564,8 +1614,6 @@ location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
|||||||
}
|
}
|
||||||
|
|
||||||
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
lookup_id_to_name = {data[0]: name for name, data in location_table.items() if type(data[0]) == int}
|
||||||
lookup_id_to_name = {**lookup_id_to_name, **{data[1]: name for name, data in key_drop_data.items()}}
|
|
||||||
lookup_id_to_name.update(shop_table_by_location_id)
|
lookup_id_to_name.update(shop_table_by_location_id)
|
||||||
lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int}
|
lookup_name_to_id = {name: data[0] for name, data in location_table.items() if type(data[0]) == int}
|
||||||
lookup_name_to_id = {**lookup_name_to_id, **{name: data[1] for name, data in key_drop_data.items()}}
|
|
||||||
lookup_name_to_id.update(shop_table_by_location)
|
lookup_name_to_id.update(shop_table_by_location)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import bisect
|
import bisect
|
||||||
|
import collections
|
||||||
import io
|
import io
|
||||||
import itertools
|
|
||||||
import json
|
import json
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
@@ -15,7 +15,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
raise Exception('Could not load BPS module')
|
raise Exception('Could not load BPS module')
|
||||||
|
|
||||||
from BaseClasses import CollectionState, ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, PotItem
|
from BaseClasses import ShopType, Region, Location, OWEdge, Door, DoorType, RegionType, LocationType, Item
|
||||||
from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals
|
from DoorShuffle import compass_data, DROptions, boss_indicator, dungeon_portals
|
||||||
from Dungeons import dungeon_music_addresses, dungeon_table
|
from Dungeons import dungeon_music_addresses, dungeon_table
|
||||||
from Regions import location_table, shop_to_location_table, retro_shops
|
from Regions import location_table, shop_to_location_table, retro_shops
|
||||||
@@ -23,17 +23,22 @@ from RoomData import DoorKind
|
|||||||
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
|
from Text import MultiByteTextMapper, CompressedTextMapper, text_addresses, Credits, TextTable
|
||||||
from Text import Uncle_texts, Ganon1_texts, Ganon_Phase_3_No_Silvers_texts, TavernMan_texts, Sahasrahla2_texts
|
from Text import Uncle_texts, Ganon1_texts, Ganon_Phase_3_No_Silvers_texts, TavernMan_texts, Sahasrahla2_texts
|
||||||
from Text import Triforce_texts, Blind_texts, BombShop2_texts, junk_texts
|
from Text import Triforce_texts, Blind_texts, BombShop2_texts, junk_texts
|
||||||
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts, LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts, Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
|
from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths_texts, DeathMountain_texts
|
||||||
|
from Text import LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts
|
||||||
|
from Text import Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
|
||||||
from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc
|
from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc
|
||||||
from Items import ItemFactory
|
from Items import ItemFactory
|
||||||
from EntranceShuffle import door_addresses, exit_ids, ow_prize_table
|
from EntranceShuffle import door_addresses, exit_ids, ow_prize_table
|
||||||
from OverworldShuffle import default_flute_connections, flute_data
|
from OverworldShuffle import default_flute_connections, flute_data
|
||||||
|
from InitialSram import InitialSram
|
||||||
|
|
||||||
from source.classes.SFX import randomize_sfx
|
from source.classes.SFX import randomize_sfx
|
||||||
|
from source.item.FillUtil import valid_pot_items
|
||||||
|
from source.dungeon.RoomList import Room0127
|
||||||
|
|
||||||
|
|
||||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||||
RANDOMIZERBASEHASH = 'fc6f1d6ba782d08ac92600c31cc7ee43'
|
RANDOMIZERBASEHASH = '210e4631353e3d094f01bf91562844a5'
|
||||||
|
|
||||||
|
|
||||||
class JsonRom(object):
|
class JsonRom(object):
|
||||||
@@ -44,6 +49,7 @@ class JsonRom(object):
|
|||||||
self.orig_buffer = None
|
self.orig_buffer = None
|
||||||
self.patches = {}
|
self.patches = {}
|
||||||
self.addresses = []
|
self.addresses = []
|
||||||
|
self.initial_sram = InitialSram()
|
||||||
|
|
||||||
def write_byte(self, address, value):
|
def write_byte(self, address, value):
|
||||||
self.write_bytes(address, [value])
|
self.write_bytes(address, [value])
|
||||||
@@ -73,6 +79,9 @@ class JsonRom(object):
|
|||||||
del self.patches[str(intervalstart)]
|
del self.patches[str(intervalstart)]
|
||||||
del self.addresses[pos]
|
del self.addresses[pos]
|
||||||
|
|
||||||
|
def write_initial_sram(self):
|
||||||
|
self.write_bytes(0x183000, self.initial_sram.get_initial_sram())
|
||||||
|
|
||||||
def write_to_file(self, file):
|
def write_to_file(self, file):
|
||||||
with open(file, 'w') as stream:
|
with open(file, 'w') as stream:
|
||||||
json.dump([self.patches], stream)
|
json.dump([self.patches], stream)
|
||||||
@@ -90,6 +99,7 @@ class LocalRom(object):
|
|||||||
self.hash = hash
|
self.hash = hash
|
||||||
self.orig_buffer = None
|
self.orig_buffer = None
|
||||||
self.file = file
|
self.file = file
|
||||||
|
self.initial_sram = InitialSram()
|
||||||
self.has_smc_header = False
|
self.has_smc_header = False
|
||||||
if not os.path.isfile(file):
|
if not os.path.isfile(file):
|
||||||
raise RuntimeError("Could not find valid local base rom for patching at expected path %s." % file)
|
raise RuntimeError("Could not find valid local base rom for patching at expected path %s." % file)
|
||||||
@@ -105,6 +115,9 @@ class LocalRom(object):
|
|||||||
def write_bytes(self, startaddress, values):
|
def write_bytes(self, startaddress, values):
|
||||||
self.buffer[startaddress:startaddress + len(values)] = values
|
self.buffer[startaddress:startaddress + len(values)] = values
|
||||||
|
|
||||||
|
def write_initial_sram(self):
|
||||||
|
self.write_bytes(0x183000, self.initial_sram.get_initial_sram())
|
||||||
|
|
||||||
def write_to_file(self, file):
|
def write_to_file(self, file):
|
||||||
with open(file, 'wb') as outfile:
|
with open(file, 'wb') as outfile:
|
||||||
outfile.write(self.buffer)
|
outfile.write(self.buffer)
|
||||||
@@ -117,6 +130,13 @@ class LocalRom(object):
|
|||||||
ret.write_bytes(int(address), values)
|
ret.write_bytes(int(address), values)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def verify_base_rom(self):
|
||||||
|
# verify correct checksum of baserom
|
||||||
|
basemd5 = hashlib.md5()
|
||||||
|
basemd5.update(self.buffer)
|
||||||
|
if JAP10HASH != basemd5.hexdigest():
|
||||||
|
raise RuntimeError('Supplied Base Rom does not match known MD5 for JAP(1.0) release.')
|
||||||
|
|
||||||
def patch_base_rom(self):
|
def patch_base_rom(self):
|
||||||
# verify correct checksum of baserom
|
# verify correct checksum of baserom
|
||||||
basemd5 = hashlib.md5()
|
basemd5 = hashlib.md5()
|
||||||
@@ -163,7 +183,6 @@ class LocalRom(object):
|
|||||||
with open(local_path('data/base2current.json'), 'w') as fp:
|
with open(local_path('data/base2current.json'), 'w') as fp:
|
||||||
json.dump(patches, fp, separators=(',', ':'))
|
json.dump(patches, fp, separators=(',', ':'))
|
||||||
|
|
||||||
|
|
||||||
def write_crc(self):
|
def write_crc(self):
|
||||||
crc = (sum(self.buffer[:0x7FDC] + self.buffer[0x7FE0:]) + 0x01FE) & 0xFFFF
|
crc = (sum(self.buffer[:0x7FDC] + self.buffer[0x7FE0:]) + 0x01FE) & 0xFFFF
|
||||||
inv = crc ^ 0xFFFF
|
inv = crc ^ 0xFFFF
|
||||||
@@ -335,6 +354,9 @@ def patch_enemizer(world, player, rom, local_rom, enemizercli, random_sprite_on_
|
|||||||
0xad, 0x3, 0x4, 0x29, 0x20, 0xf0, 0x1d])
|
0xad, 0x3, 0x4, 0x29, 0x20, 0xf0, 0x1d])
|
||||||
rom.write_byte(0x200101, 0) # Do not close boss room door on entry.
|
rom.write_byte(0x200101, 0) # Do not close boss room door on entry.
|
||||||
rom.write_byte(0x1B0101, 0) # Do not close boss room door on entry. (for Ijwu's enemizer)
|
rom.write_byte(0x1B0101, 0) # Do not close boss room door on entry. (for Ijwu's enemizer)
|
||||||
|
else:
|
||||||
|
rom.write_byte(0x04DE83, 0xB3) # maiden is now something else
|
||||||
|
|
||||||
|
|
||||||
if random_sprite_on_hit:
|
if random_sprite_on_hit:
|
||||||
_populate_sprite_table()
|
_populate_sprite_table()
|
||||||
@@ -549,6 +571,21 @@ class Sprite(object):
|
|||||||
return array_chunk(palette_as_colors, 15)
|
return array_chunk(palette_as_colors, 15)
|
||||||
|
|
||||||
|
|
||||||
|
def handle_native_dungeon(location, itemid):
|
||||||
|
# Keys in their native dungeon should use the original item code for keys
|
||||||
|
if location.parent_region.dungeon:
|
||||||
|
if location.parent_region.dungeon.name == location.item.dungeon:
|
||||||
|
if location.item.bigkey:
|
||||||
|
return 0x32
|
||||||
|
if location.item.smallkey:
|
||||||
|
return 0x24
|
||||||
|
if location.item.map:
|
||||||
|
return 0x33
|
||||||
|
if location.item.compass:
|
||||||
|
return 0x25
|
||||||
|
return itemid
|
||||||
|
|
||||||
|
|
||||||
def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
||||||
random.seed(world.rom_seeds[player])
|
random.seed(world.rom_seeds[player])
|
||||||
|
|
||||||
@@ -560,28 +597,46 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
distinguished_prog_bow_loc.item.code = 0x65
|
distinguished_prog_bow_loc.item.code = 0x65
|
||||||
|
|
||||||
# patch items
|
# patch items
|
||||||
|
pot_mw_index = 0
|
||||||
for location in world.get_locations():
|
for location in world.get_locations():
|
||||||
if location.player != player:
|
if location.player != player:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
itemid = location.item.code if location.item is not None else 0x5A
|
itemid = location.item.code if location.item is not None else 0x5A
|
||||||
|
if location.type == LocationType.Pot:
|
||||||
|
if location.item.name in valid_pot_items and location.item.player == player:
|
||||||
|
location.pot.item = valid_pot_items[location.item.name]
|
||||||
|
else:
|
||||||
|
code = handle_native_dungeon(location, itemid)
|
||||||
|
standing_item_flag = 0x80
|
||||||
|
if location.item.player != player:
|
||||||
|
standing_item_flag |= 0x40
|
||||||
|
rom.write_byte(0x142800 + pot_mw_index * 2, code)
|
||||||
|
rom.write_byte(0x142800 + pot_mw_index * 2 + 1, location.item.player)
|
||||||
|
code = pot_mw_index
|
||||||
|
pot_mw_index += 1
|
||||||
|
location.pot.indicator = standing_item_flag
|
||||||
|
location.pot.standing_item_code = code
|
||||||
|
continue
|
||||||
|
elif location.type == LocationType.Drop:
|
||||||
|
if location.item.player != player:
|
||||||
|
code = 0xF9
|
||||||
|
else:
|
||||||
|
code = 0xF8
|
||||||
|
sprite_pointer = snes_to_pc(location.address)
|
||||||
|
rom.write_byte(sprite_pointer, handle_native_dungeon(location, itemid))
|
||||||
|
if code == 0xF9:
|
||||||
|
rom.write_byte(sprite_pointer+1, location.item.player)
|
||||||
|
else:
|
||||||
|
rom.write_byte(sprite_pointer+1, 0)
|
||||||
|
rom.write_byte(sprite_pointer+2, code)
|
||||||
|
continue
|
||||||
if location.address is None or (type(location.address) is int and location.address >= 0x400000):
|
if location.address is None or (type(location.address) is int and location.address >= 0x400000):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if not location.crystal:
|
if not location.crystal:
|
||||||
if location.item is not None:
|
if location.item is not None:
|
||||||
# Keys in their native dungeon should use the original item code for keys
|
# Keys in their native dungeon should use the original item code for keys
|
||||||
if location.parent_region.dungeon:
|
itemid = handle_native_dungeon(location, itemid)
|
||||||
if location.parent_region.dungeon.is_dungeon_item(location.item):
|
|
||||||
if location.item.bigkey:
|
|
||||||
itemid = 0x32
|
|
||||||
if location.item.smallkey:
|
|
||||||
itemid = 0x24
|
|
||||||
if location.item.map:
|
|
||||||
itemid = 0x33
|
|
||||||
if location.item.compass:
|
|
||||||
itemid = 0x25
|
|
||||||
if world.remote_items[player]:
|
if world.remote_items[player]:
|
||||||
itemid = list(location_table.keys()).index(location.name) + 1
|
itemid = list(location_table.keys()).index(location.name) + 1
|
||||||
assert itemid < 0x100
|
assert itemid < 0x100
|
||||||
@@ -609,8 +664,25 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
if world.mapshuffle[player]:
|
if world.mapshuffle[player]:
|
||||||
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
rom.write_byte(0x155C9, random.choice([0x11, 0x16])) # Randomize GT music too with map shuffle
|
||||||
|
|
||||||
|
if world.pottery[player] not in ['none']:
|
||||||
|
rom.write_bytes(snes_to_pc(0x1F8375), int32_as_bytes(0x2A8000))
|
||||||
|
# make hammer pegs use different tiles
|
||||||
|
Room0127.write_to_rom(snes_to_pc(0x2A8000), rom)
|
||||||
|
|
||||||
if world.pot_contents[player]:
|
if world.pot_contents[player]:
|
||||||
write_pots_to_rom(rom, world.pot_contents[player])
|
colorize_pots = is_mystery or (world.pottery[player] not in ['vanilla', 'lottery']
|
||||||
|
and (world.colorizepots[player]
|
||||||
|
or world.pottery[player] in ['reduced', 'clustered']))
|
||||||
|
if world.pot_contents[player].size() > 0x2800:
|
||||||
|
raise Exception('Pot table is too big for current area')
|
||||||
|
world.pot_contents[player].write_pot_data_to_rom(rom, colorize_pots)
|
||||||
|
# fix for swamp drains if necessary
|
||||||
|
swamp1location = world.get_location('Swamp Palace - Trench 1 Pot Key', player)
|
||||||
|
if not swamp1location.pot.indicator:
|
||||||
|
rom.write_byte(0x142A53, 0)
|
||||||
|
swamp2location = world.get_location('Swamp Palace - Trench 2 Pot Key', player)
|
||||||
|
if not swamp2location.pot.indicator:
|
||||||
|
rom.write_byte(0x142A54, 0)
|
||||||
|
|
||||||
# patch flute spots
|
# patch flute spots
|
||||||
owFlags = 0
|
owFlags = 0
|
||||||
@@ -775,7 +847,7 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
|
dr_flags = DROptions.Eternal_Mini_Bosses if world.doorShuffle[player] == 'vanilla' else DROptions.Town_Portal
|
||||||
if world.doorShuffle[player] == 'crossed':
|
if world.doorShuffle[player] == 'crossed':
|
||||||
dr_flags |= DROptions.Map_Info
|
dr_flags |= DROptions.Map_Info
|
||||||
if world.experimental[player] and world.goal[player] not in ['triforcehunt', 'trinity']:
|
if world.collection_rate[player] and world.goal[player] not in ['triforcehunt', 'trinity']:
|
||||||
dr_flags |= DROptions.Debug
|
dr_flags |= DROptions.Debug
|
||||||
if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\
|
if world.doorShuffle[player] == 'crossed' and world.logic[player] != 'nologic'\
|
||||||
and world.mixed_travel[player] == 'prevent':
|
and world.mixed_travel[player] == 'prevent':
|
||||||
@@ -799,14 +871,20 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
if world.logic[player] != 'nologic':
|
if world.logic[player] != 'nologic':
|
||||||
dr_flags |= DROptions.Fix_EG
|
dr_flags |= DROptions.Fix_EG
|
||||||
|
|
||||||
|
my_locations = world.get_filled_locations(player)
|
||||||
|
valid_locations = [l for l in my_locations if ((l.type == LocationType.Pot and not l.forced_item)
|
||||||
|
or (l.type == LocationType.Drop and not l.forced_item)
|
||||||
|
or (l.type == LocationType.Normal and not l.forced_item)
|
||||||
|
or (l.type == LocationType.Shop and world.shopsanity[player]))]
|
||||||
|
valid_loc_by_dungeon = valid_dungeon_locations(valid_locations)
|
||||||
|
|
||||||
# fix hc big key problems (map and compass too)
|
# fix hc big key problems (map and compass too)
|
||||||
if world.doorShuffle[player] == 'crossed' or world.keydropshuffle[player]:
|
if world.doorShuffle[player] == 'crossed' or world.dropshuffle[player] or world.pottery[player] not in ['none', 'cave']:
|
||||||
rom.write_byte(0x151f1, 2)
|
rom.write_byte(0x151f1, 2)
|
||||||
rom.write_byte(0x15270, 2)
|
rom.write_byte(0x15270, 2)
|
||||||
sanctuary = world.get_region('Sanctuary', player)
|
sanctuary = world.get_region('Sanctuary', player)
|
||||||
rom.write_byte(0x1597b, sanctuary.dungeon.dungeon_id*2)
|
rom.write_byte(0x1597b, sanctuary.dungeon.dungeon_id*2)
|
||||||
update_compasses(rom, world, player)
|
update_compasses(rom, valid_loc_by_dungeon, world, player)
|
||||||
|
|
||||||
def should_be_bunny(region, mode):
|
def should_be_bunny(region, mode):
|
||||||
if mode != 'inverted':
|
if mode != 'inverted':
|
||||||
@@ -839,9 +917,12 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops) # not currently used
|
rom.write_byte(0x13f020+offset, layout.max_chests + layout.max_drops) # not currently used
|
||||||
rom.write_byte(0x13f030+offset, layout.max_chests)
|
rom.write_byte(0x13f030+offset, layout.max_chests)
|
||||||
builder = world.dungeon_layouts[player][name]
|
builder = world.dungeon_layouts[player][name]
|
||||||
rom.write_byte(0x13f080+offset, builder.location_cnt % 10)
|
valid_cnt = len(valid_loc_by_dungeon[name])
|
||||||
rom.write_byte(0x13f090+offset, builder.location_cnt // 10)
|
if valid_cnt > 99:
|
||||||
rom.write_byte(0x13f0a0+offset, builder.location_cnt)
|
logging.getLogger('').warning(f'{name} exceeds 99 in locations ({valid_cnt})')
|
||||||
|
rom.write_byte(0x13f080+offset, valid_cnt % 10)
|
||||||
|
rom.write_byte(0x13f090+offset, valid_cnt // 10)
|
||||||
|
rom.write_byte(0x13f0a0+offset, valid_cnt)
|
||||||
bk_status = 1 if builder.bk_required else 0
|
bk_status = 1 if builder.bk_required else 0
|
||||||
bk_status = 2 if builder.bk_provided else bk_status
|
bk_status = 2 if builder.bk_provided else bk_status
|
||||||
rom.write_byte(0x13f040+offset*2, bk_status)
|
rom.write_byte(0x13f040+offset*2, bk_status)
|
||||||
@@ -924,33 +1005,53 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
# bot: $7A is 1, 7B is 2, etc so 7D=4, 82=9 (zero unknown...)
|
# bot: $7A is 1, 7B is 2, etc so 7D=4, 82=9 (zero unknown...)
|
||||||
return 0x53+int(num), 0x79+int(num)
|
return 0x53+int(num), 0x79+int(num)
|
||||||
|
|
||||||
credits_total = 216
|
credits_total = len(valid_locations)
|
||||||
if world.keydropshuffle[player]:
|
|
||||||
credits_total += 33
|
|
||||||
if world.shopsanity[player]:
|
|
||||||
credits_total += 32
|
|
||||||
if world.retro[player]:
|
|
||||||
credits_total += 9 if world.shopsanity[player] else 5
|
|
||||||
|
|
||||||
if world.keydropshuffle[player]:
|
if world.dropshuffle[player] or world.pottery[player] != 'none':
|
||||||
rom.write_byte(0x140000, 1)
|
rom.write_byte(0x142A50, 1) # StandingItemsOn
|
||||||
multiClientFlags = ((0x1 if world.keydropshuffle[player] else 0) | (0x2 if world.shopsanity[player] else 0)
|
multiClientFlags = ((0x1 if world.dropshuffle[player] else 0)
|
||||||
| (0x4 if world.retro[player] else 0))
|
| (0x2 if world.shopsanity[player] else 0)
|
||||||
rom.write_byte(0x140001, multiClientFlags)
|
| (0x4 if world.retro[player] else 0)
|
||||||
|
| (0x8 if world.pottery[player] != 'none' else 0)
|
||||||
|
| (0x10 if is_mystery else 0))
|
||||||
|
rom.write_byte(0x142A51, multiClientFlags)
|
||||||
|
# StandingItemCounterMask
|
||||||
|
rom.write_byte(0x142A55, ((0x1 if world.pottery[player] not in ['none', 'cave'] else 0)
|
||||||
|
| (0x2 if world.dropshuffle[player] else 0)))
|
||||||
|
if world.pottery[player] not in ['none', 'keys']:
|
||||||
|
# Cuccos should not prevent kill rooms from opening
|
||||||
|
rom.write_byte(snes_to_pc(0x0DB457), 0x40)
|
||||||
|
rom.write_byte(snes_to_pc(0x28AA56), 0 if world.pottery[player] == 'none' else 1)
|
||||||
|
|
||||||
write_int16(rom, 0x187010, credits_total) # dynamic credits
|
write_int16(rom, 0x187010, credits_total) # dynamic credits
|
||||||
if credits_total != 216:
|
if credits_total != 216:
|
||||||
# collection rate address (hi):
|
# collection rate address (hi):
|
||||||
cr_address = 0x238057
|
cr_address = 0x238055
|
||||||
cr_pc = cr_address - 0x120000 # convert to pc
|
cr_pc = snes_to_pc(cr_address) # convert to pc
|
||||||
|
first_top, first_bot = credits_digit((credits_total // 100) % 10)
|
||||||
mid_top, mid_bot = credits_digit((credits_total // 10) % 10)
|
mid_top, mid_bot = credits_digit((credits_total // 10) % 10)
|
||||||
last_top, last_bot = credits_digit(credits_total % 10)
|
last_top, last_bot = credits_digit(credits_total % 10)
|
||||||
|
if credits_total >= 1000:
|
||||||
|
thousands_top, thousands_bot = credits_digit((credits_total // 1000) % 10)
|
||||||
|
rom.write_byte(cr_pc, 0xa2) # slash
|
||||||
|
rom.write_byte(cr_pc+1, thousands_top)
|
||||||
|
rom.write_byte(cr_pc+0x1e, 0xc2) # slash
|
||||||
|
rom.write_byte(cr_pc+0x1f, thousands_bot)
|
||||||
|
# modify stat config
|
||||||
|
stat_address = 0x23B969
|
||||||
|
stat_pc = snes_to_pc(stat_address)
|
||||||
|
rom.write_byte(stat_pc, 0xa9) # change to pos 21 (from b1)
|
||||||
|
rom.write_byte(stat_pc+2, 0xc0) # change to 12 bits (from a0)
|
||||||
|
rom.write_byte(stat_pc+3, 0x80) # change to four digits (from 60)
|
||||||
|
|
||||||
# top half
|
# top half
|
||||||
rom.write_byte(cr_pc+0x1, mid_top)
|
rom.write_byte(cr_pc+2, first_top)
|
||||||
rom.write_byte(cr_pc+0x2, last_top)
|
rom.write_byte(cr_pc+3, mid_top)
|
||||||
|
rom.write_byte(cr_pc+4, last_top)
|
||||||
# bottom half
|
# bottom half
|
||||||
rom.write_byte(cr_pc+0x1f, mid_bot)
|
rom.write_byte(cr_pc+0x20, first_bot)
|
||||||
rom.write_byte(cr_pc+0x20, last_bot)
|
rom.write_byte(cr_pc+0x21, mid_bot)
|
||||||
|
rom.write_byte(cr_pc+0x22, last_bot)
|
||||||
|
|
||||||
# patch medallion requirements
|
# patch medallion requirements
|
||||||
if world.required_medallions[player][0] == 'Bombos':
|
if world.required_medallions[player][0] == 'Bombos':
|
||||||
@@ -976,9 +1077,9 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
|
|
||||||
# set open mode:
|
# set open mode:
|
||||||
if world.mode[player] in ['open', 'inverted']:
|
if world.mode[player] in ['open', 'inverted']:
|
||||||
rom.write_byte(0x180032, 0x01) # open mode
|
init_open_mode_sram(rom)
|
||||||
elif world.mode[player] == 'standard':
|
elif world.mode[player] == 'standard':
|
||||||
rom.write_byte(0x180032, 0x00) # standard mode
|
init_standard_mode_sram(rom)
|
||||||
|
|
||||||
set_inverted_mode(world, player, rom, inverted_buffer)
|
set_inverted_mode(world, player, rom, inverted_buffer)
|
||||||
|
|
||||||
@@ -1205,10 +1306,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
|
|
||||||
# set swordless mode settings
|
# set swordless mode settings
|
||||||
rom.write_byte(0x18003F, 0x01 if world.swords[player] == 'swordless' else 0x00) # hammer can harm ganon
|
rom.write_byte(0x18003F, 0x01 if world.swords[player] == 'swordless' else 0x00) # hammer can harm ganon
|
||||||
rom.write_byte(0x180040, 0x01 if world.swords[player] == 'swordless' else 0x00) # open curtains
|
|
||||||
rom.write_byte(0x180041, 0x01 if world.swords[player] == 'swordless' else 0x00) # swordless medallions
|
rom.write_byte(0x180041, 0x01 if world.swords[player] == 'swordless' else 0x00) # swordless medallions
|
||||||
rom.write_byte(0x180043, 0xFF if world.swords[player] == 'swordless' else 0x00) # starting sword for link
|
|
||||||
rom.write_byte(0x180044, 0x01 if world.swords[player] == 'swordless' else 0x00) # hammer activates tablets
|
rom.write_byte(0x180044, 0x01 if world.swords[player] == 'swordless' else 0x00) # hammer activates tablets
|
||||||
|
if world.swords[player] == 'swordless':
|
||||||
|
rom.initial_sram.set_swordless_curtains() # open curtains
|
||||||
|
|
||||||
# set up clocks for timed modes
|
# set up clocks for timed modes
|
||||||
if world.shuffle[player] == 'vanilla':
|
if world.shuffle[player] == 'vanilla':
|
||||||
@@ -1224,45 +1325,46 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
write_int32(rom, 0x180200, 0) # red clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180200, 0) # red clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x180204, 0) # blue clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180204, 0) # blue clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x180208, 0) # green clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180208, 0) # green clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x18020C, 0) # starting time (in frames, sint32)
|
rom.initial_sram.set_starting_timer(0) # starting time (in frames, sint32)
|
||||||
elif world.clock_mode == 'ohko':
|
elif world.clock_mode == 'ohko':
|
||||||
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
|
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
|
||||||
write_int32(rom, 0x180200, 0) # red clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180200, 0) # red clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x180204, 0) # blue clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180204, 0) # blue clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x180208, 0) # green clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180208, 0) # green clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x18020C, 0) # starting time (in frames, sint32)
|
rom.initial_sram.set_starting_timer(0) # starting time (in frames, sint32)
|
||||||
elif world.clock_mode == 'countdown-ohko':
|
elif world.clock_mode == 'countdown-ohko':
|
||||||
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
|
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
|
||||||
write_int32(rom, 0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||||
if world.difficulty_adjustments[player] == 'normal':
|
if world.difficulty_adjustments == 'normal':
|
||||||
write_int32(rom, 0x18020C, (10 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32)
|
rom.initial_sram.set_starting_timer((10 + ERtimeincrease) * 60) # starting time (in seconds)
|
||||||
else:
|
else:
|
||||||
write_int32(rom, 0x18020C, int((5 + ERtimeincrease / 2) * 60 * 60)) # starting time (in frames, sint32)
|
rom.initial_sram.set_starting_timer(int((5 + ERtimeincrease / 2) * 60)) # starting time (in seconds)
|
||||||
if world.clock_mode == 'stopwatch':
|
if world.clock_mode == 'stopwatch':
|
||||||
rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
|
rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
|
||||||
write_int32(rom, 0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x18020C, 0) # starting time (in frames, sint32)
|
rom.initial_sram.set_starting_timer(0) # starting time (in frames, sint32)
|
||||||
if world.clock_mode == 'countdown':
|
if world.clock_mode == 'countdown':
|
||||||
rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
|
rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
|
||||||
write_int32(rom, 0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||||
write_int32(rom, 0x18020C, (40 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32)
|
rom.initial_sram.set_starting_timer((40 + ERtimeincrease) * 60) # starting time (in seconds)
|
||||||
|
|
||||||
# set up goals for treasure hunt
|
# set up goals for treasure hunt
|
||||||
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28])
|
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon[player] == 'Triforce Piece' else [0x0D, 0x28])
|
||||||
if world.goal[player] in ['triforcehunt', 'trinity']:
|
if world.goal[player] in ['triforcehunt', 'trinity']:
|
||||||
rom.write_byte(0x180167, int(world.treasure_hunt_count[player]) % 256)
|
rom.write_bytes(0x180167, int16_as_bytes(world.treasure_hunt_count[player]))
|
||||||
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
|
rom.write_byte(0x180194, 1) # Must turn in triforced pieces (instant win not enabled)
|
||||||
|
|
||||||
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
|
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
|
||||||
|
|
||||||
gametype = 0x04 # item
|
gametype = 0x04 # item
|
||||||
if world.shuffle[player] != 'vanilla' or world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player]:
|
if (world.shuffle[player] != 'vanilla' or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
|
||||||
|
or world.pottery[player] != 'none'):
|
||||||
gametype |= 0x02 # entrance/door
|
gametype |= 0x02 # entrance/door
|
||||||
if enemized:
|
if enemized:
|
||||||
gametype |= 0x01 # enemizer
|
gametype |= 0x01 # enemizer
|
||||||
@@ -1282,14 +1384,16 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted
|
rom.write_byte(0x180169, 0x02) # lock aga/ganon tower door with crystals in inverted
|
||||||
rom.write_byte(0x180171, 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death
|
rom.write_byte(0x180171, 0x01 if world.ganon_at_pyramid[player] else 0x00) # Enable respawning on pyramid after ganon death
|
||||||
rom.write_byte(0x180173, 0x01) # Bob is enabled
|
rom.write_byte(0x180173, 0x01) # Bob is enabled
|
||||||
rom.write_byte(0x180168, 0x08) # Spike Cave Damage
|
rom.write_byte(0x180195, 0x08) # Spike Cave Damage
|
||||||
rom.write_bytes(0x18016B, [0x04, 0x02, 0x01]) # Set spike cave and MM spike room Byrna usage
|
rom.write_bytes(0x18016B, [0x04, 0x02, 0x01]) # Set spike cave and MM spike room Byrna usage
|
||||||
rom.write_bytes(0x18016E, [0x04, 0x08, 0x10]) # Set spike cave and MM spike room Cape usage
|
rom.write_bytes(0x18016E, [0x04, 0x08, 0x10]) # Set spike cave and MM spike room Cape usage
|
||||||
rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest
|
rom.write_bytes(0x50563, [0x3F, 0x14]) # disable below ganon chest
|
||||||
rom.write_byte(0x50599, 0x00) # disable below ganon chest
|
rom.write_byte(0x50599, 0x00) # disable below ganon chest
|
||||||
rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest
|
rom.write_bytes(0xE9A5, [0x7E, 0x00, 0x24]) # disable below ganon chest
|
||||||
rom.write_byte(0x18008B, 0x01 if world.is_pyramid_open(player) else 0x00) # pre-open Pyramid Hole
|
if world.open_pyramid[player]:
|
||||||
rom.write_byte(0x18008C, 0x01 if world.crystals_needed_for_gt[player] == 0 else 0x00) # GT pre-opened if crystal requirement is 0
|
rom.initial_sram.pre_open_pyramid_hole()
|
||||||
|
if world.crystals_needed_for_gt[player] == 0:
|
||||||
|
rom.initial_sram.pre_open_ganons_tower()
|
||||||
rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped
|
rom.write_byte(0x18008F, 0x01 if world.is_atgt_swapped(player) else 0x00) # AT/GT swapped
|
||||||
rom.write_byte(0xF5D73, 0xF0) # bees are catchable
|
rom.write_byte(0xF5D73, 0xF0) # bees are catchable
|
||||||
rom.write_byte(0xF5F10, 0xF0) # bees are catchable
|
rom.write_byte(0xF5F10, 0xF0) # bees are catchable
|
||||||
@@ -1299,168 +1403,12 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00)
|
rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00)
|
||||||
rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles
|
rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles
|
||||||
|
|
||||||
# starting overworld flags
|
|
||||||
assert len(world.initial_overworld_flags[player]) == 0x80 and all([i < 0x100 for i in world.initial_overworld_flags[player]])
|
|
||||||
rom.write_bytes(0x183280, world.initial_overworld_flags[player])
|
|
||||||
|
|
||||||
# Starting equipment
|
# Starting equipment
|
||||||
if world.pseudoboots[player]:
|
if world.pseudoboots[player]:
|
||||||
rom.write_byte(0x18008E, 0x01)
|
rom.write_byte(0x18008E, 0x01)
|
||||||
|
rom.initial_sram.set_starting_equipment(world, player)
|
||||||
equip = [0] * (0x340 + 0x4F)
|
rom.write_byte(0x180034, 10 if not world.bombbag[player] else 0) # starting max bombs
|
||||||
equip[0x36C] = 0x18
|
rom.write_byte(0x180035, 30) # starting max arrows
|
||||||
equip[0x36D] = 0x18
|
|
||||||
equip[0x379] = 0x68
|
|
||||||
if world.bombbag[player]:
|
|
||||||
starting_max_bombs = 0
|
|
||||||
else:
|
|
||||||
starting_max_bombs = 10
|
|
||||||
starting_max_arrows = 30
|
|
||||||
|
|
||||||
startingstate = CollectionState(world)
|
|
||||||
|
|
||||||
if startingstate.has('Bow', player):
|
|
||||||
equip[0x340] = 3 if startingstate.has('Silver Arrows', player) else 1
|
|
||||||
equip[0x38E] |= 0x20 # progressive flag to get the correct hint in all cases
|
|
||||||
if not world.retro[player]:
|
|
||||||
equip[0x38E] |= 0x80
|
|
||||||
if startingstate.has('Silver Arrows', player):
|
|
||||||
equip[0x38E] |= 0x40
|
|
||||||
|
|
||||||
if startingstate.has('Titans Mitts', player):
|
|
||||||
equip[0x354] = 2
|
|
||||||
elif startingstate.has('Power Glove', player):
|
|
||||||
equip[0x354] = 1
|
|
||||||
|
|
||||||
if startingstate.has('Golden Sword', player):
|
|
||||||
equip[0x359] = 4
|
|
||||||
elif startingstate.has('Tempered Sword', player):
|
|
||||||
equip[0x359] = 3
|
|
||||||
elif startingstate.has('Master Sword', player):
|
|
||||||
equip[0x359] = 2
|
|
||||||
elif startingstate.has('Fighter Sword', player):
|
|
||||||
equip[0x359] = 1
|
|
||||||
|
|
||||||
if startingstate.has('Mirror Shield', player):
|
|
||||||
equip[0x35A] = 3
|
|
||||||
elif startingstate.has('Red Shield', player):
|
|
||||||
equip[0x35A] = 2
|
|
||||||
elif startingstate.has('Blue Shield', player):
|
|
||||||
equip[0x35A] = 1
|
|
||||||
|
|
||||||
if startingstate.has('Red Mail', player):
|
|
||||||
equip[0x35B] = 2
|
|
||||||
elif startingstate.has('Blue Mail', player):
|
|
||||||
equip[0x35B] = 1
|
|
||||||
|
|
||||||
if startingstate.has('Magic Upgrade (1/4)', player):
|
|
||||||
equip[0x37B] = 2
|
|
||||||
equip[0x36E] = 0x80
|
|
||||||
elif startingstate.has('Magic Upgrade (1/2)', player):
|
|
||||||
equip[0x37B] = 1
|
|
||||||
equip[0x36E] = 0x80
|
|
||||||
|
|
||||||
for item in world.precollected_items:
|
|
||||||
if item.player != player:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if item.name in ['Bow', 'Silver Arrows', 'Progressive Bow', 'Progressive Bow (Alt)',
|
|
||||||
'Titans Mitts', 'Power Glove', 'Progressive Glove',
|
|
||||||
'Golden Sword', 'Tempered Sword', 'Master Sword', 'Fighter Sword', 'Progressive Sword',
|
|
||||||
'Mirror Shield', 'Red Shield', 'Blue Shield', 'Progressive Shield',
|
|
||||||
'Red Mail', 'Blue Mail', 'Progressive Armor',
|
|
||||||
'Magic Upgrade (1/4)', 'Magic Upgrade (1/2)']:
|
|
||||||
continue
|
|
||||||
|
|
||||||
set_table = {'Book of Mudora': (0x34E, 1), 'Hammer': (0x34B, 1), 'Bug Catching Net': (0x34D, 1), 'Hookshot': (0x342, 1), 'Magic Mirror': (0x353, 2),
|
|
||||||
'Cape': (0x352, 1), 'Lamp': (0x34A, 1), 'Moon Pearl': (0x357, 1), 'Cane of Somaria': (0x350, 1), 'Cane of Byrna': (0x351, 1),
|
|
||||||
'Fire Rod': (0x345, 1), 'Ice Rod': (0x346, 1), 'Bombos': (0x347, 1), 'Ether': (0x348, 1), 'Quake': (0x349, 1)}
|
|
||||||
or_table = {'Green Pendant': (0x374, 0x04), 'Red Pendant': (0x374, 0x01), 'Blue Pendant': (0x374, 0x02),
|
|
||||||
'Crystal 1': (0x37A, 0x02), 'Crystal 2': (0x37A, 0x10), 'Crystal 3': (0x37A, 0x40), 'Crystal 4': (0x37A, 0x20),
|
|
||||||
'Crystal 5': (0x37A, 0x04), 'Crystal 6': (0x37A, 0x01), 'Crystal 7': (0x37A, 0x08),
|
|
||||||
'Big Key (Eastern Palace)': (0x367, 0x20), 'Compass (Eastern Palace)': (0x365, 0x20), 'Map (Eastern Palace)': (0x369, 0x20),
|
|
||||||
'Big Key (Desert Palace)': (0x367, 0x10), 'Compass (Desert Palace)': (0x365, 0x10), 'Map (Desert Palace)': (0x369, 0x10),
|
|
||||||
'Big Key (Tower of Hera)': (0x366, 0x20), 'Compass (Tower of Hera)': (0x364, 0x20), 'Map (Tower of Hera)': (0x368, 0x20),
|
|
||||||
'Big Key (Escape)': (0x367, 0xC0), 'Compass (Escape)': (0x365, 0xC0), 'Map (Escape)': (0x369, 0xC0),
|
|
||||||
'Big Key (Agahnims Tower)': (0x367, 0x08), 'Compass (Agahnims Tower)': (0x365, 0x08), 'Map (Agahnims Tower)': (0x369, 0x08),
|
|
||||||
'Big Key (Palace of Darkness)': (0x367, 0x02), 'Compass (Palace of Darkness)': (0x365, 0x02), 'Map (Palace of Darkness)': (0x369, 0x02),
|
|
||||||
'Big Key (Thieves Town)': (0x366, 0x10), 'Compass (Thieves Town)': (0x364, 0x10), 'Map (Thieves Town)': (0x368, 0x10),
|
|
||||||
'Big Key (Skull Woods)': (0x366, 0x80), 'Compass (Skull Woods)': (0x364, 0x80), 'Map (Skull Woods)': (0x368, 0x80),
|
|
||||||
'Big Key (Swamp Palace)': (0x367, 0x04), 'Compass (Swamp Palace)': (0x365, 0x04), 'Map (Swamp Palace)': (0x369, 0x04),
|
|
||||||
'Big Key (Ice Palace)': (0x366, 0x40), 'Compass (Ice Palace)': (0x364, 0x40), 'Map (Ice Palace)': (0x368, 0x40),
|
|
||||||
'Big Key (Misery Mire)': (0x367, 0x01), 'Compass (Misery Mire)': (0x365, 0x01), 'Map (Misery Mire)': (0x369, 0x01),
|
|
||||||
'Big Key (Turtle Rock)': (0x366, 0x08), 'Compass (Turtle Rock)': (0x364, 0x08), 'Map (Turtle Rock)': (0x368, 0x08),
|
|
||||||
'Big Key (Ganons Tower)': (0x366, 0x04), 'Compass (Ganons Tower)': (0x364, 0x04), 'Map (Ganons Tower)': (0x368, 0x04)}
|
|
||||||
set_or_table = {'Flippers': (0x356, 1, 0x379, 0x02),'Pegasus Boots': (0x355, 1, 0x379, 0x04),
|
|
||||||
'Shovel': (0x34C, 1, 0x38C, 0x04), 'Ocarina': (0x34C, 3, 0x38C, 0x01), 'Ocarina (Activated)': (0x34C, 3, 0x38C, 0x01),
|
|
||||||
'Mushroom': (0x344, 1, 0x38C, 0x20 | 0x08), 'Magic Powder': (0x344, 2, 0x38C, 0x10),
|
|
||||||
'Blue Boomerang': (0x341, 1, 0x38C, 0x80), 'Red Boomerang': (0x341, 2, 0x38C, 0x40)}
|
|
||||||
keys = {'Small Key (Eastern Palace)': [0x37E], 'Small Key (Desert Palace)': [0x37F],
|
|
||||||
'Small Key (Tower of Hera)': [0x386],
|
|
||||||
'Small Key (Agahnims Tower)': [0x380], 'Small Key (Palace of Darkness)': [0x382],
|
|
||||||
'Small Key (Thieves Town)': [0x387],
|
|
||||||
'Small Key (Skull Woods)': [0x384], 'Small Key (Swamp Palace)': [0x381],
|
|
||||||
'Small Key (Ice Palace)': [0x385],
|
|
||||||
'Small Key (Misery Mire)': [0x383], 'Small Key (Turtle Rock)': [0x388],
|
|
||||||
'Small Key (Ganons Tower)': [0x389],
|
|
||||||
'Small Key (Universal)': [0x38B], 'Small Key (Escape)': [0x37C, 0x37D]}
|
|
||||||
bottles = {'Bottle': 2, 'Bottle (Red Potion)': 3, 'Bottle (Green Potion)': 4, 'Bottle (Blue Potion)': 5,
|
|
||||||
'Bottle (Fairy)': 6, 'Bottle (Bee)': 7, 'Bottle (Good Bee)': 8}
|
|
||||||
rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100, 'Rupees (300)': 300}
|
|
||||||
bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10}
|
|
||||||
arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10}
|
|
||||||
bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10}
|
|
||||||
arrows = {'Single Arrow': 1, 'Arrows (10)': 10}
|
|
||||||
|
|
||||||
if item.name in set_table:
|
|
||||||
equip[set_table[item.name][0]] = set_table[item.name][1]
|
|
||||||
elif item.name in or_table:
|
|
||||||
equip[or_table[item.name][0]] |= or_table[item.name][1]
|
|
||||||
elif item.name in set_or_table:
|
|
||||||
equip[set_or_table[item.name][0]] = set_or_table[item.name][1]
|
|
||||||
equip[set_or_table[item.name][2]] |= set_or_table[item.name][3]
|
|
||||||
elif item.name in keys:
|
|
||||||
for address in keys[item.name]:
|
|
||||||
equip[address] = min(equip[address] + 1, 99)
|
|
||||||
elif item.name in bottles:
|
|
||||||
if equip[0x34F] < world.difficulty_requirements[player].progressive_bottle_limit:
|
|
||||||
equip[0x35C + equip[0x34F]] = bottles[item.name]
|
|
||||||
equip[0x34F] += 1
|
|
||||||
elif item.name in rupees:
|
|
||||||
equip[0x360:0x362] = list(min(equip[0x360] + (equip[0x361] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False))
|
|
||||||
equip[0x362:0x364] = list(min(equip[0x362] + (equip[0x363] << 8) + rupees[item.name], 9999).to_bytes(2, byteorder='little', signed=False))
|
|
||||||
elif item.name in bomb_caps:
|
|
||||||
starting_max_bombs = min(starting_max_bombs + bomb_caps[item.name], 50)
|
|
||||||
elif item.name in arrow_caps:
|
|
||||||
starting_max_arrows = min(starting_max_arrows + arrow_caps[item.name], 70)
|
|
||||||
elif item.name in bombs:
|
|
||||||
equip[0x343] += bombs[item.name]
|
|
||||||
elif item.name in arrows:
|
|
||||||
if world.retro[player]:
|
|
||||||
equip[0x38E] |= 0x80
|
|
||||||
equip[0x377] = 1
|
|
||||||
else:
|
|
||||||
equip[0x377] += arrows[item.name]
|
|
||||||
elif item.name in ['Piece of Heart', 'Boss Heart Container', 'Sanctuary Heart Container']:
|
|
||||||
if item.name == 'Piece of Heart':
|
|
||||||
equip[0x36B] = (equip[0x36B] + 1) % 4
|
|
||||||
if item.name != 'Piece of Heart' or equip[0x36B] == 0:
|
|
||||||
equip[0x36C] = min(equip[0x36C] + 0x08, 0xA0)
|
|
||||||
equip[0x36D] = min(equip[0x36D] + 0x08, 0xA0)
|
|
||||||
else:
|
|
||||||
raise RuntimeError(f'Unsupported item in starting equipment: {item.name}')
|
|
||||||
|
|
||||||
equip[0x343] = min(equip[0x343], starting_max_bombs)
|
|
||||||
rom.write_byte(0x180034, starting_max_bombs)
|
|
||||||
equip[0x377] = min(equip[0x377], starting_max_arrows)
|
|
||||||
rom.write_byte(0x180035, starting_max_arrows)
|
|
||||||
rom.write_bytes(0x180046, equip[0x360:0x362])
|
|
||||||
if equip[0x359]:
|
|
||||||
rom.write_byte(0x180043, equip[0x359])
|
|
||||||
|
|
||||||
assert equip[:0x340] == [0] * 0x340
|
|
||||||
rom.write_bytes(0x183340, equip[0x340:])
|
|
||||||
rom.write_bytes(0x271A6, equip[0x340:0x340+60])
|
|
||||||
|
|
||||||
rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode
|
rom.write_byte(0x18004A, 0x00 if world.mode[player] != 'inverted' else 0x01) # Inverted mode
|
||||||
rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier
|
rom.write_byte(0x18005D, 0x00) # Hammer always breaks barrier
|
||||||
@@ -1523,7 +1471,8 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_byte(0x18003C, 0x00)
|
rom.write_byte(0x18003C, 0x00)
|
||||||
elif world.dungeon_counters[player] == 'on':
|
elif world.dungeon_counters[player] == 'on':
|
||||||
compass_mode = 0x02 # always on
|
compass_mode = 0x02 # always on
|
||||||
elif world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dungeon_counters[player] == 'pickup':
|
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
|
||||||
|
or world.dungeon_counters[player] == 'pickup' or world.pottery[player] not in ['none', 'cave']):
|
||||||
compass_mode = 0x01 # show on pickup
|
compass_mode = 0x01 # show on pickup
|
||||||
if (world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default') \
|
if (world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default') \
|
||||||
or (world.owMixed[player] and not (world.shuffle[player] != 'vanilla' and world.overworld_map[player] == 'default')):
|
or (world.owMixed[player] and not (world.shuffle[player] != 'vanilla' and world.overworld_map[player] == 'default')):
|
||||||
@@ -1576,10 +1525,11 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_byte(0x53EA6+ow_map_index, world_indicator)
|
rom.write_byte(0x53EA6+ow_map_index, world_indicator)
|
||||||
# in crossed doors - flip the compass exists flags
|
# in crossed doors - flip the compass exists flags
|
||||||
if world.doorShuffle[player] == 'crossed':
|
if world.doorShuffle[player] == 'crossed':
|
||||||
|
for dungeon, portal_list in dungeon_portals.items():
|
||||||
|
ow_map_index = dungeon_table[dungeon].map_index
|
||||||
exists_flag = any(x for x in world.get_dungeon(dungeon, player).dungeon_items if x.type == 'Compass')
|
exists_flag = any(x for x in world.get_dungeon(dungeon, player).dungeon_items if x.type == 'Compass')
|
||||||
rom.write_byte(0x53E96+ow_map_index, 0x1 if exists_flag else 0x0)
|
rom.write_byte(0x53E96+ow_map_index, 0x1 if exists_flag else 0x0)
|
||||||
|
|
||||||
|
|
||||||
rom.write_byte(0x18003C, compass_mode)
|
rom.write_byte(0x18003C, compass_mode)
|
||||||
|
|
||||||
# Bitfield - enable free items to show up in menu
|
# Bitfield - enable free items to show up in menu
|
||||||
@@ -1590,9 +1540,10 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
# b - Big Key
|
# b - Big Key
|
||||||
# a - Small Key
|
# a - Small Key
|
||||||
#
|
#
|
||||||
|
enable_menu_map_check = world.overworld_map[player] != 'default' and world.shuffle[player] != 'none'
|
||||||
rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] else 0x00)
|
rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] else 0x00)
|
||||||
| (0x02 if world.bigkeyshuffle[player] else 0x00)
|
| (0x02 if world.bigkeyshuffle[player] else 0x00)
|
||||||
| (0x04 if world.mapshuffle[player] else 0x00)
|
| (0x04 if world.mapshuffle[player] or enable_menu_map_check else 0x00)
|
||||||
| (0x08 if world.compassshuffle[player] else 0x00))) # free roaming items in menu
|
| (0x08 if world.compassshuffle[player] else 0x00))) # free roaming items in menu
|
||||||
|
|
||||||
# Map reveals
|
# Map reveals
|
||||||
@@ -1720,13 +1671,17 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False):
|
|||||||
rom.write_byte(0xFED31, 0x0E) # preopen bombable exit
|
rom.write_byte(0xFED31, 0x0E) # preopen bombable exit
|
||||||
rom.write_byte(0xFEE41, 0x0E) # preopen bombable exit
|
rom.write_byte(0xFEE41, 0x0E) # preopen bombable exit
|
||||||
|
|
||||||
if world.doorShuffle[player] != 'vanilla' or world.keydropshuffle[player]:
|
if (world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player]
|
||||||
|
or world.pottery[player] != 'none'):
|
||||||
for room in world.rooms:
|
for room in world.rooms:
|
||||||
if room.player == player and room.modified:
|
if room.player == player and room.modified:
|
||||||
rom.write_bytes(room.address(), room.rom_data())
|
rom.write_bytes(room.address(), room.rom_data())
|
||||||
|
|
||||||
write_strings(rom, world, player, team)
|
write_strings(rom, world, player, team)
|
||||||
|
|
||||||
|
# write initial sram
|
||||||
|
rom.write_initial_sram()
|
||||||
|
|
||||||
rom.write_byte(0x18636C, 1 if world.remote_items[player] else 0)
|
rom.write_byte(0x18636C, 1 if world.remote_items[player] else 0)
|
||||||
|
|
||||||
# set rom name
|
# set rom name
|
||||||
@@ -1831,13 +1786,15 @@ def hud_format_text(text):
|
|||||||
|
|
||||||
|
|
||||||
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite,
|
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite,
|
||||||
ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx):
|
ow_palettes, uw_palettes, reduce_flashing, shuffle_sfx, msu_resume):
|
||||||
|
|
||||||
if not os.path.exists("data/sprites/official/001.link.1.zspr") and rom.orig_buffer:
|
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],
|
dump_zspr(rom.orig_buffer[0x80000:0x87000], rom.orig_buffer[0xdd308:0xdd380],
|
||||||
rom.orig_buffer[0xdedf5:0xdedf9], "data/sprites/official/001.link.1.zspr", "Nintendo", "Link")
|
rom.orig_buffer[0xdedf5:0xdedf9], "data/sprites/official/001.link.1.zspr", "Nintendo", "Link")
|
||||||
|
|
||||||
# todo: implement a flag for msu resume delay
|
if msu_resume:
|
||||||
|
rom.write_bytes(0x18021D, [0x8, 0x7])
|
||||||
|
else:
|
||||||
rom.write_bytes(0x18021D, [0, 0]) # default to off for now
|
rom.write_bytes(0x18021D, [0, 0]) # default to off for now
|
||||||
|
|
||||||
if sprite and not isinstance(sprite, Sprite):
|
if sprite and not isinstance(sprite, Sprite):
|
||||||
@@ -2151,6 +2108,8 @@ def write_strings(rom, world, player, team):
|
|||||||
else:
|
else:
|
||||||
if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon:
|
if isinstance(dest, Region) and dest.type == RegionType.Dungeon and dest.dungeon:
|
||||||
hint = dest.dungeon.name
|
hint = dest.dungeon.name
|
||||||
|
elif isinstance(dest, Item) and world.experimental[player]:
|
||||||
|
hint = f'{{C:RED}}{dest.hint_text}{{C:WHITE}}' if dest.hint_text else 'something'
|
||||||
else:
|
else:
|
||||||
hint = dest.hint_text if dest.hint_text else "something"
|
hint = dest.hint_text if dest.hint_text else "something"
|
||||||
if dest.player != player:
|
if dest.player != player:
|
||||||
@@ -2262,7 +2221,7 @@ def write_strings(rom, world, player, team):
|
|||||||
else:
|
else:
|
||||||
second_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item)
|
second_item = hint_text(world.get_location('Swamp Palace - West Chest', player).item)
|
||||||
first_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item)
|
first_item = hint_text(world.get_location('Swamp Palace - Big Key Chest', player).item)
|
||||||
this_hint = ('The westmost chests in Swamp Palace contain ' + first_item + ' and ' + second_item + '.')
|
this_hint = f'The westmost chests in Swamp Palace contain {first_item} and {second_item}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
elif location == 'Mire Left':
|
elif location == 'Mire Left':
|
||||||
if random.randint(0, 1) == 0:
|
if random.randint(0, 1) == 0:
|
||||||
@@ -2271,34 +2230,43 @@ def write_strings(rom, world, player, team):
|
|||||||
else:
|
else:
|
||||||
second_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item)
|
second_item = hint_text(world.get_location('Misery Mire - Compass Chest', player).item)
|
||||||
first_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item)
|
first_item = hint_text(world.get_location('Misery Mire - Big Key Chest', player).item)
|
||||||
this_hint = ('The westmost chests in Misery Mire contain ' + first_item + ' and ' + second_item + '.')
|
this_hint = f'The westmost chests in Misery Mire contain {first_item} and {second_item}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
elif location == 'Tower of Hera - Big Key Chest':
|
elif location == 'Tower of Hera - Big Key Chest':
|
||||||
this_hint = 'Waiting in the Tower of Hera basement leads to ' + hint_text(world.get_location(location, player).item) + '.'
|
item = hint_text(world.get_location(location, player).item)
|
||||||
|
this_hint = f'Waiting in the Tower of Hera basement leads to {item}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
elif location == 'Ganons Tower - Big Chest':
|
elif location == 'Ganons Tower - Big Chest':
|
||||||
this_hint = 'The big chest in Ganon\'s Tower contains ' + hint_text(world.get_location(location, player).item) + '.'
|
item = hint_text(world.get_location(location, player).item)
|
||||||
|
this_hint = f'The big chest in Ganon\'s Tower contains {item}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
elif location == 'Thieves\' Town - Big Chest':
|
elif location == 'Thieves\' Town - Big Chest':
|
||||||
this_hint = 'The big chest in Thieves\' Town contains ' + hint_text(world.get_location(location, player).item) + '.'
|
item = hint_text(world.get_location(location, player).item)
|
||||||
|
this_hint = f'The big chest in Thieves\' Town contains {item}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
elif location == 'Ice Palace - Big Chest':
|
elif location == 'Ice Palace - Big Chest':
|
||||||
this_hint = 'The big chest in Ice Palace contains ' + hint_text(world.get_location(location, player).item) + '.'
|
item = hint_text(world.get_location(location, player).item)
|
||||||
|
this_hint = f'The big chest in Ice Palace contains {item}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
elif location == 'Eastern Palace - Big Key Chest':
|
elif location == 'Eastern Palace - Big Key Chest':
|
||||||
this_hint = 'The antifairy guarded chest in Eastern Palace contains ' + hint_text(world.get_location(location, player).item) + '.'
|
item = hint_text(world.get_location(location, player).item)
|
||||||
|
this_hint = f'The antifairy guarded chest in Eastern Palace contains {item}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
elif location == 'Sahasrahla':
|
elif location == 'Sahasrahla':
|
||||||
this_hint = 'Sahasrahla seeks a green pendant for ' + hint_text(world.get_location(location, player).item) + '.'
|
item = hint_text(world.get_location(location, player).item)
|
||||||
|
this_hint = f'Sahasrahla seeks a green pendant for {item}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
elif location == 'Graveyard Cave':
|
elif location == 'Graveyard Cave':
|
||||||
this_hint = 'The cave north of the graveyard contains ' + hint_text(world.get_location(location, player).item) + '.'
|
item = hint_text(world.get_location(location, player).item)
|
||||||
|
this_hint = f'The cave north of the graveyard contains {item}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
else:
|
else:
|
||||||
this_hint = location + ' contains ' + hint_text(world.get_location(location, player).item) + '.'
|
this_hint = f'{location} contains {hint_text(world.get_location(location, player).item)}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
|
|
||||||
# Lastly we write hints to show where certain interesting items are. It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless of how many exist. This supports many settings well.
|
# Lastly we write hints to show where certain interesting items are.
|
||||||
|
# It is done the way it is to re-use the silver code and also to give one hint per each type of item regardless
|
||||||
|
# of how many exist. This supports many settings well.
|
||||||
items_to_hint = RelevantItems.copy()
|
items_to_hint = RelevantItems.copy()
|
||||||
flute_item = 'Ocarina'
|
flute_item = 'Ocarina'
|
||||||
if world.is_tile_swapped(0x18, player):
|
if world.is_tile_swapped(0x18, player):
|
||||||
@@ -2326,8 +2294,10 @@ def write_strings(rom, world, player, team):
|
|||||||
this_location = world.find_items_not_key_only(this_item, player)
|
this_location = world.find_items_not_key_only(this_item, player)
|
||||||
random.shuffle(this_location)
|
random.shuffle(this_location)
|
||||||
if this_location:
|
if this_location:
|
||||||
this_hint = this_location[0].item.hint_text + ' can be found ' + hint_text(this_location[0]) + '.'
|
item_name = this_location[0].item.hint_text
|
||||||
this_hint = this_hint[0].upper() + this_hint[1:]
|
item_name = item_name[0].upper() + item_name[1:]
|
||||||
|
item_format = f'{{C:RED}}{item_name}{{C:WHITE}}' if world.experimental[player] else item_name
|
||||||
|
this_hint = f'{item_format} can be found {hint_text(this_location[0])}.'
|
||||||
tt[hint_locations.pop(0)] = this_hint
|
tt[hint_locations.pop(0)] = this_hint
|
||||||
hint_count -= 1
|
hint_count -= 1
|
||||||
|
|
||||||
@@ -2381,7 +2351,8 @@ def write_strings(rom, world, player, team):
|
|||||||
elif hint_type == 'path':
|
elif hint_type == 'path':
|
||||||
if item_count == 1:
|
if item_count == 1:
|
||||||
the_item = text_for_item(next(iter(choice_set)), world, player, team)
|
the_item = text_for_item(next(iter(choice_set)), world, player, team)
|
||||||
hint_candidates.append((hint_type, f'{name} conceals {the_item}'))
|
item_format = f'{{C:RED}}{the_item}{{C:WHITE}}' if world.experimental[player] else the_item
|
||||||
|
hint_candidates.append((hint_type, f'{name} conceals only {item_format}'))
|
||||||
else:
|
else:
|
||||||
hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items'))
|
hint_candidates.append((hint_type, f'{name} conceals {item_count} {item_type} items'))
|
||||||
district_hints = min(len(hint_candidates), len(hint_locations))
|
district_hints = min(len(hint_candidates), len(hint_locations))
|
||||||
@@ -2468,7 +2439,7 @@ def write_strings(rom, world, player, team):
|
|||||||
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
|
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
|
||||||
tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!'
|
tt['sign_ganon'] = 'You need to get to the pedestal... Ganon is invincible!'
|
||||||
else:
|
else:
|
||||||
if world.goal[player] in ['trinity']:
|
if world.goal[player] == 'trinity':
|
||||||
trinity_crystal_text = ('%d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else '%d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player]
|
trinity_crystal_text = ('%d crystal to beat Ganon.' if world.crystals_needed_for_ganon[player] == 1 else '%d crystals to beat Ganon.') % world.crystals_needed_for_ganon[player]
|
||||||
tt['sign_ganon'] = 'Three ways to victory! %s Get to it!' % trinity_crystal_text
|
tt['sign_ganon'] = 'Three ways to victory! %s Get to it!' % trinity_crystal_text
|
||||||
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player])
|
tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\ninvisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\nhidden in a hollow tree. If you bring\n%d triforce pieces, I can reassemble it." % int(world.treasure_hunt_count[player])
|
||||||
@@ -2569,6 +2540,16 @@ def text_for_item(item, world, player, team):
|
|||||||
else:
|
else:
|
||||||
return f'{item.hint_text} for {world.player_names[item.player][team]}'
|
return f'{item.hint_text} for {world.player_names[item.player][team]}'
|
||||||
|
|
||||||
|
def init_open_mode_sram(rom):
|
||||||
|
rom.initial_sram.pre_open_castle_gate()
|
||||||
|
rom.initial_sram.set_progress_indicator(0x02)
|
||||||
|
rom.initial_sram.set_progress_flags(0x14)
|
||||||
|
rom.initial_sram.set_starting_entrance(0x01)
|
||||||
|
|
||||||
|
def init_standard_mode_sram(rom):
|
||||||
|
rom.initial_sram.set_progress_indicator(0x00)
|
||||||
|
rom.initial_sram.set_progress_flags(0x0)
|
||||||
|
rom.initial_sram.set_starting_entrance(0x00)
|
||||||
|
|
||||||
def set_inverted_mode(world, player, rom, inverted_buffer):
|
def set_inverted_mode(world, player, rom, inverted_buffer):
|
||||||
if world.mode[player] == 'inverted':
|
if world.mode[player] == 'inverted':
|
||||||
@@ -2780,7 +2761,7 @@ def patch_shuffled_dark_sanc(world, rom, player):
|
|||||||
dark_sanc_entrance = str([i for i in dark_sanc.entrances if i.parent_region.name != 'Menu'][0].name)
|
dark_sanc_entrance = str([i for i in dark_sanc.entrances if i.parent_region.name != 'Menu'][0].name)
|
||||||
room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = door_addresses[dark_sanc_entrance][1]
|
room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = door_addresses[dark_sanc_entrance][1]
|
||||||
door_index = door_addresses[str(dark_sanc_entrance)][0]
|
door_index = door_addresses[str(dark_sanc_entrance)][0]
|
||||||
world.initial_overworld_flags[player][ow_area] |= door_addresses[dark_sanc_entrance][2]
|
rom.initial_sram.pre_set_overworld_flag(ow_area, door_addresses[dark_sanc_entrance][2])
|
||||||
|
|
||||||
rom.write_byte(0x180241, 0x01)
|
rom.write_byte(0x180241, 0x01)
|
||||||
rom.write_byte(0x180248, door_index + 1)
|
rom.write_byte(0x180248, door_index + 1)
|
||||||
@@ -2795,7 +2776,7 @@ def patch_shuffled_bomb_shop(world, rom, player):
|
|||||||
bomb_shop_entrance = str([i for i in bomb_shop.entrances if i.parent_region.name != 'Menu'][0].name)
|
bomb_shop_entrance = str([i for i in bomb_shop.entrances if i.parent_region.name != 'Menu'][0].name)
|
||||||
room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = door_addresses[bomb_shop_entrance][1]
|
room_id, ow_area, vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x, unknown_1, unknown_2, door_1, door_2 = door_addresses[bomb_shop_entrance][1]
|
||||||
door_index = door_addresses[str(bomb_shop_entrance)][0]
|
door_index = door_addresses[str(bomb_shop_entrance)][0]
|
||||||
world.initial_overworld_flags[player][ow_area] |= door_addresses[bomb_shop_entrance][2]
|
rom.initial_sram.pre_set_overworld_flag(ow_area, door_addresses[bomb_shop_entrance][2])
|
||||||
|
|
||||||
rom.write_byte(0x180240, 0x02)
|
rom.write_byte(0x180240, 0x02)
|
||||||
rom.write_byte(0x180247, door_index + 1)
|
rom.write_byte(0x180247, door_index + 1)
|
||||||
@@ -2806,12 +2787,23 @@ def patch_shuffled_bomb_shop(world, rom, player):
|
|||||||
write_int16(rom, snes_to_pc(0x02D996), door_1)
|
write_int16(rom, snes_to_pc(0x02D996), door_1)
|
||||||
|
|
||||||
|
|
||||||
def update_compasses(rom, world, player):
|
def valid_dungeon_locations(valid_locations):
|
||||||
|
dungeon_locations = collections.defaultdict(set)
|
||||||
|
for l in valid_locations:
|
||||||
|
if l.parent_region.dungeon:
|
||||||
|
dungeon_locations[l.parent_region.dungeon.name].add(l)
|
||||||
|
return dungeon_locations
|
||||||
|
|
||||||
|
|
||||||
|
def update_compasses(rom, dungeon_locations, world, player):
|
||||||
layouts = world.dungeon_layouts[player]
|
layouts = world.dungeon_layouts[player]
|
||||||
provided_dungeon = False
|
provided_dungeon = False
|
||||||
for name, builder in layouts.items():
|
for name, builder in layouts.items():
|
||||||
dungeon_id = compass_data[name][4]
|
dungeon_id = compass_data[name][4]
|
||||||
rom.write_byte(0x187000 + dungeon_id//2, builder.location_cnt)
|
dungeon_count = len(dungeon_locations[name])
|
||||||
|
if dungeon_count > 255:
|
||||||
|
logging.getLogger('').warning(f'{name} has more locations than 255. Need 16-bit compass counts')
|
||||||
|
rom.write_byte(0x187000 + dungeon_id//2, dungeon_count % 256)
|
||||||
if builder.bk_provided:
|
if builder.bk_provided:
|
||||||
if provided_dungeon:
|
if provided_dungeon:
|
||||||
logging.getLogger('').warning('Multiple dungeons have forced BKs! Compass code might need updating?')
|
logging.getLogger('').warning('Multiple dungeons have forced BKs! Compass code might need updating?')
|
||||||
@@ -3085,29 +3077,3 @@ hash_alphabet = [
|
|||||||
"Lamp", "Hammer", "Shovel", "Ocarina", "Bug Net", "Book", "Bottle", "Potion", "Cane", "Cape", "Mirror", "Boots",
|
"Lamp", "Hammer", "Shovel", "Ocarina", "Bug Net", "Book", "Bottle", "Potion", "Cane", "Cape", "Mirror", "Boots",
|
||||||
"Gloves", "Flippers", "Pearl", "Shield", "Tunic", "Heart", "Map", "Compass", "Key"
|
"Gloves", "Flippers", "Pearl", "Shield", "Tunic", "Heart", "Map", "Compass", "Key"
|
||||||
]
|
]
|
||||||
|
|
||||||
pot_item_room_table_lookup = 0xDB67
|
|
||||||
|
|
||||||
###
|
|
||||||
# Pointer to pot location and contents for each non-empty pot in a supertile
|
|
||||||
# Format: [(x, y, item)] FF FF (Note: x,y are bit packed to include layer)
|
|
||||||
pot_item_table = 0xDDE7
|
|
||||||
pot_item_table_end = 0xE6B0
|
|
||||||
|
|
||||||
def write_pots_to_rom(rom, pot_contents):
|
|
||||||
n = pot_item_table
|
|
||||||
rom.write_bytes(n, [0xFF,0xFF])
|
|
||||||
n += 2
|
|
||||||
for i in range(0x140):
|
|
||||||
if i in pot_contents:
|
|
||||||
pots = [pot for pot in pot_contents[i] if pot.item != PotItem.Nothing]
|
|
||||||
if len(pots) > 0:
|
|
||||||
write_int16(rom, pot_item_room_table_lookup + 2*i, n)
|
|
||||||
rom.write_bytes(n, list(itertools.chain.from_iterable(((pot.x, pot.y, pot.item) for pot in pots))))
|
|
||||||
n += 3*len(pots) + 2
|
|
||||||
rom.write_bytes(n - 2, [0xFF,0xFF])
|
|
||||||
else:
|
|
||||||
write_int16(rom, pot_item_room_table_lookup + 2*i, n-2)
|
|
||||||
else:
|
|
||||||
write_int16(rom, pot_item_room_table_lookup + 2*i, n-2)
|
|
||||||
assert n <= pot_item_table_end
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import logging
|
|||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
import OverworldGlitchRules
|
import OverworldGlitchRules
|
||||||
from BaseClasses import CollectionState, RegionType, DoorType, Entrance, CrystalBarrier, KeyRuleType
|
from BaseClasses import CollectionState, RegionType, DoorType, Entrance, CrystalBarrier, KeyRuleType, LocationType
|
||||||
|
from BaseClasses import PotFlags
|
||||||
from Dungeons import dungeon_table
|
from Dungeons import dungeon_table
|
||||||
from RoomData import DoorKind
|
from RoomData import DoorKind
|
||||||
from OverworldGlitchRules import overworld_glitches_rules
|
from OverworldGlitchRules import overworld_glitches_rules
|
||||||
@@ -33,6 +34,7 @@ def set_rules(world, player):
|
|||||||
raise NotImplementedError('Not implemented yet')
|
raise NotImplementedError('Not implemented yet')
|
||||||
|
|
||||||
bomb_rules(world, player)
|
bomb_rules(world, player)
|
||||||
|
pot_rules(world, player)
|
||||||
|
|
||||||
if world.logic[player] == 'noglitches':
|
if world.logic[player] == 'noglitches':
|
||||||
no_glitches_rules(world, player)
|
no_glitches_rules(world, player)
|
||||||
@@ -56,9 +58,10 @@ def set_rules(world, player):
|
|||||||
elif world.goal[player] == 'ganon':
|
elif world.goal[player] == 'ganon':
|
||||||
# require aga2 to beat ganon
|
# require aga2 to beat ganon
|
||||||
add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player))
|
add_rule(world.get_location('Ganon', player), lambda state: state.has('Beat Agahnim 2', player))
|
||||||
if world.goal[player] in ['triforcehunt', 'trinity']:
|
elif world.goal[player] in ['triforcehunt', 'trinity']:
|
||||||
if ('Murahdahla', player) in world._location_cache:
|
for location in world.get_region('Hyrule Castle Courtyard', player).locations:
|
||||||
add_rule(world.get_location('Murahdahla', player), lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
|
if location.name == 'Murahdahla':
|
||||||
|
add_rule(location, lambda state: state.item_count('Triforce Piece', player) + state.item_count('Power Star', player) >= int(state.world.treasure_hunt_count[player]))
|
||||||
|
|
||||||
# if swamp and dam have not been moved we require mirror for swamp palace
|
# if swamp and dam have not been moved we require mirror for swamp palace
|
||||||
if not world.swamp_patch_required[player]:
|
if not world.swamp_patch_required[player]:
|
||||||
@@ -213,6 +216,9 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_location('Hookshot Cave - Bottom Right', player), lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player))
|
set_rule(world.get_location('Hookshot Cave - Bottom Right', player), lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player))
|
||||||
set_rule(world.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player))
|
||||||
|
|
||||||
|
set_rule(world.get_entrance('Hookshot Cave Bonk Path', player), lambda state: state.has('Hookshot', player) or state.has('Pegasus Boots', player))
|
||||||
|
set_rule(world.get_entrance('Hookshot Cave Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||||
|
|
||||||
# Start of door rando rules
|
# Start of door rando rules
|
||||||
# TODO: Do these need to flag off when door rando is off? - some of them, yes
|
# TODO: Do these need to flag off when door rando is off? - some of them, yes
|
||||||
|
|
||||||
@@ -277,6 +283,7 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('Swamp Trench 1 Departure Key', player), lambda state: state.has('Flippers', player) and state.has('Trench 1 Filled', player))
|
set_rule(world.get_entrance('Swamp Trench 1 Departure Key', player), lambda state: state.has('Flippers', player) and state.has('Trench 1 Filled', player))
|
||||||
set_rule(world.get_location('Trench 1 Switch', player), lambda state: state.has('Hammer', player))
|
set_rule(world.get_location('Trench 1 Switch', player), lambda state: state.has('Hammer', player))
|
||||||
set_rule(world.get_entrance('Swamp Hub Hook Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('Swamp Hub Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||||
|
set_rule(world.get_entrance('Swamp Hub Side Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('Swamp Trench 2 Pots Dry', player), lambda state: not state.has('Trench 2 Filled', player))
|
set_rule(world.get_entrance('Swamp Trench 2 Pots Dry', player), lambda state: not state.has('Trench 2 Filled', player))
|
||||||
set_rule(world.get_entrance('Swamp Trench 2 Pots Wet', player), lambda state: state.has('Flippers', player) and state.has('Trench 2 Filled', player))
|
set_rule(world.get_entrance('Swamp Trench 2 Pots Wet', player), lambda state: state.has('Flippers', player) and state.has('Trench 2 Filled', player))
|
||||||
@@ -300,6 +307,8 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('Skull Big Chest Hookpath', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player))
|
set_rule(world.get_entrance('Skull Torch Room WN', player), lambda state: state.has('Fire Rod', player))
|
||||||
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player))
|
set_rule(world.get_entrance('Skull Vines NW', player), lambda state: state.has_sword(player))
|
||||||
|
set_rule(world.get_entrance('Skull 2 West Lobby Pits', player), lambda state: state.has_Boots(player) or state.has('Hidden Pits', player))
|
||||||
|
set_rule(world.get_entrance('Skull 2 West Lobby Ledge Pits', player), lambda state: state.has('Hidden Pits', player))
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Boss', player))
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Skull Woods - Prize', player))
|
||||||
|
|
||||||
@@ -312,8 +321,9 @@ def global_rules(world, player):
|
|||||||
# I think these rules are unnecessary now - testing needed
|
# I think these rules are unnecessary now - testing needed
|
||||||
# for location in ['Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']:
|
# for location in ['Thieves\' Town - Blind\'s Cell', 'Thieves\' Town - Boss']:
|
||||||
# forbid_item(world.get_location(location, player), 'Big Key (Thieves Town)', player)
|
# forbid_item(world.get_location(location, player), 'Big Key (Thieves Town)', player)
|
||||||
for location in ['Suspicious Maiden', 'Thieves\' Town - Blind\'s Cell']:
|
# forbid_item(world.get_location('Thieves\' Town - Blind\'s Cell', player), 'Big Key (Thieves Town)', player)
|
||||||
set_rule(world.get_location(location, player), lambda state: state.has('Big Key (Thieves Town)', player))
|
# for location in ['Suspicious Maiden', 'Thieves\' Town - Blind\'s Cell']:
|
||||||
|
# set_rule(world.get_location(location, player), lambda state: state.has('Big Key (Thieves Town)', player))
|
||||||
set_rule(world.get_location('Revealing Light', player), lambda state: state.has('Shining Light', player) and state.has('Maiden Rescued', player))
|
set_rule(world.get_location('Revealing Light', player), lambda state: state.has('Shining Light', player) and state.has('Maiden Rescued', player))
|
||||||
set_rule(world.get_location('Thieves\' Town - Boss', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Boss', player).parent_region.dungeon.boss.can_defeat(state))
|
set_rule(world.get_location('Thieves\' Town - Boss', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Boss', player).parent_region.dungeon.boss.can_defeat(state))
|
||||||
set_rule(world.get_location('Thieves\' Town - Prize', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
set_rule(world.get_location('Thieves\' Town - Prize', player), lambda state: state.has('Maiden Unmasked', player) and world.get_location('Thieves\' Town - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
||||||
@@ -345,6 +355,9 @@ def global_rules(world, player):
|
|||||||
state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player)) # need to defeat wizzrobes, bombs don't work ...
|
state.has('Hammer', player) or state.has('Cane of Somaria', player) or state.can_shoot_arrows(player)) # need to defeat wizzrobes, bombs don't work ...
|
||||||
# byrna could work with sufficient magic
|
# byrna could work with sufficient magic
|
||||||
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||||
|
loc = world.get_location('Misery Mire - Spikes Pot Key', player)
|
||||||
|
if loc.pot.x == 48 and loc.pot.y == 28: # pot shuffled to spike area
|
||||||
|
set_rule(loc, lambda state: (state.world.can_take_damage and state.has_hearts(player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||||
set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('Mire Left Bridge Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('Mire Tile Room NW', player), lambda state: state.has_fire_source(player))
|
||||||
set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('Mire Attic Hint Hole', player), lambda state: state.has_fire_source(player))
|
||||||
@@ -361,13 +374,19 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('TR Hub EN', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('TR Hub EN', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
set_rule(world.get_entrance('TR Hub NW', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('TR Hub NW', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
set_rule(world.get_entrance('TR Hub NE', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('TR Hub NE', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
|
set_rule(world.get_entrance('TR Hub Path', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
|
set_rule(world.get_entrance('TR Hub Ledges Path', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
|
set_rule(world.get_entrance('TR Torches NW', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
|
||||||
set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
|
set_rule(world.get_entrance('TR Big Chest Entrance Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player))
|
set_rule(world.get_entrance('TR Big Chest Gap', player), lambda state: state.has('Cane of Somaria', player) or state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('TR Dark Ride Up Stairs', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
set_rule(world.get_entrance('TR Dark Ride SW', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('TR Dark Ride SW', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
set_rule(world.get_entrance('TR Final Abyss South Stairs', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('TR Dark Ride Path', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
set_rule(world.get_entrance('TR Final Abyss NW', player), lambda state: state.has('Cane of Somaria', player))
|
set_rule(world.get_entrance('TR Dark Ride Ledges Path', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
|
for location in world.get_region('TR Dark Ride Ledges', player).locations:
|
||||||
|
set_rule(location, lambda state: state.has('Cane of Somaria', player))
|
||||||
|
set_rule(world.get_entrance('TR Final Abyss Balcony Path', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
|
set_rule(world.get_entrance('TR Final Abyss Ledge Path', player), lambda state: state.has('Cane of Somaria', player))
|
||||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
||||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
||||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
||||||
@@ -381,12 +400,12 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('GT Conveyor Cross EN', player), lambda state: state.has('Hookshot', player))
|
||||||
if not world.get_door('GT Speed Torch SE', player).entranceFlag:
|
if not world.get_door('GT Speed Torch SE', player).entranceFlag:
|
||||||
set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player))
|
set_rule(world.get_entrance('GT Speed Torch SE', player), lambda state: state.has('Fire Rod', player))
|
||||||
set_rule(world.get_entrance('GT Hookshot East-North Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('GT Hookshot South-Mid Path', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('GT Hookshot South-East Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('GT Hookshot Mid-North Path', player), lambda state: state.has('Hookshot', player))
|
||||||
set_rule(world.get_entrance('GT Hookshot South-North Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('GT Hookshot East-Mid Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('GT Hookshot East-South Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
set_rule(world.get_entrance('GT Hookshot North-Mid Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('GT Hookshot North-East Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
set_rule(world.get_entrance('GT Hookshot Mid-South Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('GT Hookshot North-South Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
set_rule(world.get_entrance('GT Hookshot Mid-East Path', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('GT Firesnake Room Hook Path', player), lambda state: state.has('Hookshot', player))
|
set_rule(world.get_entrance('GT Firesnake Room Hook Path', player), lambda state: state.has('Hookshot', player))
|
||||||
|
|
||||||
# I am tempted to stick an invincibility rule for getting across falling bridge
|
# I am tempted to stick an invincibility rule for getting across falling bridge
|
||||||
@@ -415,6 +434,9 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
set_rule(world.get_entrance('GT Lanmolas 2 NW', player), lambda state: world.get_region('GT Lanmolas 2', player).dungeon.bosses['middle'].can_defeat(state))
|
||||||
set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('GT Torch Cross ES', player), lambda state: state.has_fire_source(player))
|
||||||
set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player))
|
set_rule(world.get_entrance('GT Falling Torches NE', player), lambda state: state.has_fire_source(player))
|
||||||
|
# todo: the following only applies to crystal state propagation from this supertile
|
||||||
|
# you can also reset the supertile, but I'm not sure how to model that
|
||||||
|
set_rule(world.get_entrance('GT Falling Torches Down Ladder', player), lambda state: state.has_Boots(player))
|
||||||
set_rule(world.get_entrance('GT Moldorm Gap', player), lambda state: state.has('Hookshot', player) and world.get_region('GT Moldorm', player).dungeon.bosses['top'].can_defeat(state))
|
set_rule(world.get_entrance('GT Moldorm Gap', player), lambda state: state.has('Hookshot', player) and world.get_region('GT Moldorm', player).dungeon.bosses['top'].can_defeat(state))
|
||||||
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
|
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
|
||||||
|
|
||||||
@@ -423,6 +445,8 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player))
|
set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player))
|
||||||
else:
|
else:
|
||||||
set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player))
|
set_rule(world.get_entrance('Thieves Attic ES', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player))
|
||||||
|
set_rule(world.get_entrance('Thieves Attic Orange Barrier', player), lambda state: state.can_reach_orange(world.get_region('Thieves Attic', player), player))
|
||||||
|
set_rule(world.get_entrance('Thieves Attic Blue Barrier', player), lambda state: state.can_reach_blue(world.get_region('Thieves Attic', player), player))
|
||||||
|
|
||||||
set_rule(world.get_entrance('Hera Lobby to Front Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('Hera Lobby', player), player))
|
set_rule(world.get_entrance('Hera Lobby to Front Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('Hera Lobby', player), player))
|
||||||
set_rule(world.get_entrance('Hera Front to Lobby Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('Hera Front', player), player))
|
set_rule(world.get_entrance('Hera Front to Lobby Barrier - Blue', player), lambda state: state.can_reach_blue(world.get_region('Hera Front', player), player))
|
||||||
@@ -446,6 +470,7 @@ def global_rules(world, player):
|
|||||||
set_rule(world.get_entrance('Hera Basement Cage to Crystal', player), lambda state: state.can_hit_crystal(player))
|
set_rule(world.get_entrance('Hera Basement Cage to Crystal', player), lambda state: state.can_hit_crystal(player))
|
||||||
set_rule(world.get_entrance('Hera Tridorm to Crystal', player), lambda state: state.can_hit_crystal(player))
|
set_rule(world.get_entrance('Hera Tridorm to Crystal', player), lambda state: state.can_hit_crystal(player))
|
||||||
set_rule(world.get_entrance('Hera Startile Wide to Crystal', player), lambda state: state.can_hit_crystal(player))
|
set_rule(world.get_entrance('Hera Startile Wide to Crystal', player), lambda state: state.can_hit_crystal(player))
|
||||||
|
set_rule(world.get_entrance('Hera 5F Orange Path', player), lambda state: state.can_reach_orange(world.get_region('Hera 5F', player), player))
|
||||||
|
|
||||||
set_rule(world.get_entrance('PoD Arena North to Landing Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('PoD Arena North', player), player))
|
set_rule(world.get_entrance('PoD Arena North to Landing Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('PoD Arena North', player), player))
|
||||||
set_rule(world.get_entrance('PoD Arena Landing to North Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('PoD Arena Landing', player), player))
|
set_rule(world.get_entrance('PoD Arena Landing to North Barrier - Orange', player), lambda state: state.can_reach_orange(world.get_region('PoD Arena Landing', player), player))
|
||||||
@@ -615,10 +640,13 @@ def global_rules(world, player):
|
|||||||
|
|
||||||
|
|
||||||
def bomb_rules(world, player):
|
def bomb_rules(world, player):
|
||||||
|
# todo: kak well, pod hint (bonkable pots), hookshot pot, spike cave pots
|
||||||
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.
|
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', 'Light Hype Fairy', 'Mini Moldorm Cave',
|
bombable_doors = ['Ice Rod Cave', 'Light World Bomb Hut', 'Light World Death Mountain Shop', 'Mini Moldorm Cave',
|
||||||
'Hookshot Cave Back to Middle', 'Hookshot Cave Front to Middle', 'Hookshot Cave Middle to Front','Hookshot Cave Middle to Back',
|
'Hookshot Cave Back to Middle', 'Hookshot Cave Front to Middle', 'Hookshot Cave Middle to Front',
|
||||||
'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery', 'Light Hype Fairy']
|
'Hookshot Cave Middle to Back', 'Dark Lake Hylia Ledge Fairy', 'Hype Cave', 'Brewery',
|
||||||
|
'Paradox Cave Chest Area NE', 'Blinds Hideout N', 'Kakariko Well (top to back)',
|
||||||
|
'Light Hype Fairy']
|
||||||
for entrance in bonkable_doors:
|
for entrance in bonkable_doors:
|
||||||
add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player))
|
add_rule(world.get_entrance(entrance, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player))
|
||||||
add_bunny_rule(world.get_entrance(entrance, player), player)
|
add_bunny_rule(world.get_entrance(entrance, player), player)
|
||||||
@@ -627,8 +655,7 @@ def bomb_rules(world, player):
|
|||||||
add_bunny_rule(world.get_entrance(entrance, player), player)
|
add_bunny_rule(world.get_entrance(entrance, player), player)
|
||||||
|
|
||||||
bonkable_items = ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right']
|
bonkable_items = ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right']
|
||||||
bombable_items = ['Blind\'s Hideout - Top', 'Kakariko Well - Top', 'Chicken House', 'Aginah\'s Cave', 'Graveyard Cave',
|
bombable_items = ['Chicken House', 'Aginah\'s Cave', 'Graveyard Cave',
|
||||||
'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right',
|
|
||||||
'Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom']
|
'Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom']
|
||||||
for location in bonkable_items:
|
for location in bonkable_items:
|
||||||
add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player))
|
add_rule(world.get_location(location, player), lambda state: state.can_use_bombs(player) or state.has_Boots(player))
|
||||||
@@ -732,6 +759,53 @@ def bomb_rules(world, player):
|
|||||||
elif door.kind(world) in [DoorKind.Bombable]:
|
elif door.kind(world) in [DoorKind.Bombable]:
|
||||||
add_rule(door.entrance, lambda state: state.can_use_bombs(player))
|
add_rule(door.entrance, lambda state: state.can_use_bombs(player))
|
||||||
|
|
||||||
|
|
||||||
|
def pot_rules(world, player):
|
||||||
|
if world.pottery[player] != 'none':
|
||||||
|
blocks = [l for l in world.get_locations() if l.type == LocationType.Pot and l.pot.flags & PotFlags.Block]
|
||||||
|
for block_pot in blocks:
|
||||||
|
add_rule(block_pot, lambda state: state.can_lift_rocks(player))
|
||||||
|
for l in world.get_region('Hookshot Fairy', player).locations:
|
||||||
|
if l.type == LocationType.Pot:
|
||||||
|
add_rule(l, lambda state: state.has('Hookshot', player))
|
||||||
|
for l in world.get_region('Spike Cave', player).locations:
|
||||||
|
if l.type == LocationType.Pot:
|
||||||
|
add_rule(l, lambda state: state.has('Hammer', player) and state.can_lift_rocks(player) and
|
||||||
|
((state.has('Cape', player) and state.can_extend_magic(player, 16, True)) or
|
||||||
|
(state.has('Cane of Byrna', player) and
|
||||||
|
(state.can_extend_magic(player, 12, True) or
|
||||||
|
(state.world.can_take_damage and (state.has_Boots(player) or state.has_hearts(player, 4)))))))
|
||||||
|
for l in world.get_region('Dark Desert Hint', player).locations:
|
||||||
|
if l.type == LocationType.Pot:
|
||||||
|
add_rule(l, lambda state: state.can_use_bombs(player))
|
||||||
|
for l in world.get_region('Palace of Darkness Hint', player).locations:
|
||||||
|
if l.type == LocationType.Pot:
|
||||||
|
add_rule(l, lambda state: state.can_use_bombs(player) or state.has_Boots(player))
|
||||||
|
for number in ['1', '2']:
|
||||||
|
loc = world.get_location_unsafe(f'Dark Lake Hylia Ledge Spike Cave Pot #{number}', player)
|
||||||
|
if loc and loc.type == LocationType.Pot:
|
||||||
|
add_rule(loc, lambda state: state.world.can_take_damage or state.has('Hookshot', player)
|
||||||
|
or state.has('Cape', player)
|
||||||
|
or (state.has('Cane of Byrna', player)
|
||||||
|
and state.world.difficulty_adjustments[player] == 'normal'))
|
||||||
|
for l in world.get_region('Ice Hammer Block', player).locations:
|
||||||
|
if l.type == LocationType.Pot:
|
||||||
|
add_rule(l, lambda state: state.has('Hammer', player) and state.can_lift_rocks(player))
|
||||||
|
for pot in ['Ice Antechamber Pot #3', 'Ice Antechamber Pot #4']:
|
||||||
|
loc = world.get_location_unsafe(pot, player)
|
||||||
|
if loc:
|
||||||
|
set_rule(loc, lambda state: state.has('Hammer', player) and state.can_lift_rocks(player))
|
||||||
|
loc = world.get_location_unsafe('Mire Spikes Pot #3', player)
|
||||||
|
if loc:
|
||||||
|
set_rule(loc, lambda state: (state.world.can_take_damage and state.has_hearts(player, 4))
|
||||||
|
or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||||
|
for l in world.get_region('Ice Refill', player).locations:
|
||||||
|
if l.type == LocationType.Pot:
|
||||||
|
# or can_reach_blue is redundant as you have to hit a crystal switch somewhere...
|
||||||
|
add_rule(l, lambda state: state.can_hit_crystal(player))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def default_rules(world, player):
|
def default_rules(world, player):
|
||||||
set_rule(world.get_entrance('Other World S&Q', player), lambda state: state.has_Mirror(player) and state.has('Beat Agahnim 1', player))
|
set_rule(world.get_entrance('Other World S&Q', player), lambda state: state.has_Mirror(player) and state.has('Beat Agahnim 1', player))
|
||||||
|
|
||||||
@@ -828,7 +902,7 @@ def default_rules(world, player):
|
|||||||
|
|
||||||
set_rule(world.get_entrance('Zora Waterfall Water Drop', player), lambda state: state.has('Flippers', player))
|
set_rule(world.get_entrance('Zora Waterfall Water Drop', player), lambda state: state.has('Flippers', player))
|
||||||
set_rule(world.get_entrance('Zora Waterfall Water Entry', player), lambda state: state.has('Flippers', player))
|
set_rule(world.get_entrance('Zora Waterfall Water Entry', player), lambda state: state.has('Flippers', player))
|
||||||
set_rule(world.get_entrance('Waterfall of Wishing Cave Entry', player), lambda state: state.has('Flippers', player))
|
set_rule(world.get_entrance('Zora Waterfall Water Approach', player), lambda state: state.has('Flippers', player))
|
||||||
set_rule(world.get_entrance('Kakariko Pond Whirlpool', player), lambda state: state.has('Flippers', player))
|
set_rule(world.get_entrance('Kakariko Pond Whirlpool', player), lambda state: state.has('Flippers', player))
|
||||||
set_rule(world.get_entrance('River Bend Water Drop', player), lambda state: state.has('Flippers', player))
|
set_rule(world.get_entrance('River Bend Water Drop', player), lambda state: state.has('Flippers', player))
|
||||||
set_rule(world.get_entrance('River Bend East Water Drop', player), lambda state: state.has('Flippers', player))
|
set_rule(world.get_entrance('River Bend East Water Drop', player), lambda state: state.has('Flippers', player))
|
||||||
@@ -1035,7 +1109,7 @@ def ow_rules(world, player):
|
|||||||
if not world.is_tile_swapped(0x1b, player):
|
if not world.is_tile_swapped(0x1b, player):
|
||||||
set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: False)
|
set_rule(world.get_entrance('Inverted Pyramid Hole', player), lambda state: False)
|
||||||
set_rule(world.get_entrance('Inverted Pyramid Entrance', player), lambda state: False)
|
set_rule(world.get_entrance('Inverted Pyramid Entrance', player), lambda state: False)
|
||||||
set_rule(world.get_entrance('Pyramid Hole', player), lambda state: world.open_pyramid[player] or world.goal[player] == 'trinity' or state.has('Beat Agahnim 2', player))
|
set_rule(world.get_entrance('Pyramid Hole', player), lambda state: world.open_pyramid[player] or state.has('Beat Agahnim 2', player))
|
||||||
|
|
||||||
set_rule(world.get_entrance('HC Area Mirror Spot', player), lambda state: state.has_Mirror(player))
|
set_rule(world.get_entrance('HC Area Mirror Spot', player), lambda state: state.has_Mirror(player))
|
||||||
set_rule(world.get_entrance('HC Ledge Mirror Spot', player), lambda state: state.has_Mirror(player))
|
set_rule(world.get_entrance('HC Ledge Mirror Spot', player), lambda state: state.has_Mirror(player))
|
||||||
@@ -1346,7 +1420,7 @@ def ow_bunny_rules(world, player):
|
|||||||
|
|
||||||
add_bunny_rule(world.get_entrance('Zora Waterfall Water Drop', player), player)
|
add_bunny_rule(world.get_entrance('Zora Waterfall Water Drop', player), player)
|
||||||
add_bunny_rule(world.get_entrance('Zora Waterfall Water Entry', player), player)
|
add_bunny_rule(world.get_entrance('Zora Waterfall Water Entry', player), player)
|
||||||
add_bunny_rule(world.get_entrance('Waterfall of Wishing Cave Entry', player), player)
|
add_bunny_rule(world.get_entrance('Zora Waterfall Water Approach', player), player)
|
||||||
add_bunny_rule(world.get_entrance('Kakariko Pond Whirlpool', player), player)
|
add_bunny_rule(world.get_entrance('Kakariko Pond Whirlpool', player), player)
|
||||||
add_bunny_rule(world.get_entrance('River Bend Water Drop', player), player)
|
add_bunny_rule(world.get_entrance('River Bend Water Drop', player), player)
|
||||||
add_bunny_rule(world.get_entrance('River Bend East Water Drop', player), player)
|
add_bunny_rule(world.get_entrance('River Bend East Water Drop', player), player)
|
||||||
@@ -1385,6 +1459,7 @@ def ow_bunny_rules(world, player):
|
|||||||
add_bunny_rule(world.get_entrance('Bomber Corner Water Drop', player), player)
|
add_bunny_rule(world.get_entrance('Bomber Corner Water Drop', player), player)
|
||||||
add_bunny_rule(world.get_entrance('Bomber Corner Waterfall Water Drop', player), player)
|
add_bunny_rule(world.get_entrance('Bomber Corner Waterfall Water Drop', player), player)
|
||||||
|
|
||||||
|
|
||||||
def no_glitches_rules(world, player):
|
def no_glitches_rules(world, player):
|
||||||
# todo: move some dungeon rules to no glictes logic - see these for examples
|
# todo: move some dungeon rules to no glictes logic - see these for examples
|
||||||
# add_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
# add_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hookshot', player) or state.has_Boots(player))
|
||||||
@@ -1399,7 +1474,7 @@ def no_glitches_rules(world, player):
|
|||||||
|
|
||||||
|
|
||||||
def fake_flipper_rules(world, player):
|
def fake_flipper_rules(world, player):
|
||||||
set_rule(world.get_entrance('Waterfall of Wishing Cave Entry', player), lambda state: True) # warning, assumes FF possible on other end of whirlpool or local ancilla splash delete
|
set_rule(world.get_entrance('Zora Waterfall Water Approach', player), lambda state: True) # warning, assumes FF possible on other end of whirlpool or local ancilla splash delete
|
||||||
set_rule(world.get_entrance('River Bend Water Drop', player), lambda state: True)
|
set_rule(world.get_entrance('River Bend Water Drop', player), lambda state: True)
|
||||||
set_rule(world.get_entrance('River Bend East Water Drop', player), lambda state: True)
|
set_rule(world.get_entrance('River Bend East Water Drop', player), lambda state: True)
|
||||||
set_rule(world.get_entrance('Potion Shop Water Drop', player), lambda state: True)
|
set_rule(world.get_entrance('Potion Shop Water Drop', player), lambda state: True)
|
||||||
@@ -1418,7 +1493,7 @@ def fake_flipper_rules(world, player):
|
|||||||
set_rule(world.get_entrance('Hype Cave Water Entry', player), lambda state: True)
|
set_rule(world.get_entrance('Hype Cave Water Entry', player), lambda state: True)
|
||||||
set_rule(world.get_entrance('Ice Lake Southeast Water Drop', player), lambda state: True)
|
set_rule(world.get_entrance('Ice Lake Southeast Water Drop', player), lambda state: True)
|
||||||
set_rule(world.get_entrance('Bomber Corner Water Drop', player), lambda state: True)
|
set_rule(world.get_entrance('Bomber Corner Water Drop', player), lambda state: True)
|
||||||
add_bunny_rule(world.get_entrance('Waterfall of Wishing Cave Entry', player), player)
|
add_bunny_rule(world.get_entrance('Zora Waterfall Water Approach', player), player)
|
||||||
add_bunny_rule(world.get_entrance('River Bend Water Drop', player), player)
|
add_bunny_rule(world.get_entrance('River Bend Water Drop', player), player)
|
||||||
add_bunny_rule(world.get_entrance('River Bend East Water Drop', player), player)
|
add_bunny_rule(world.get_entrance('River Bend East Water Drop', player), player)
|
||||||
add_bunny_rule(world.get_entrance('Potion Shop Water Drop', player), player)
|
add_bunny_rule(world.get_entrance('Potion Shop Water Drop', player), player)
|
||||||
@@ -1466,7 +1541,8 @@ def add_conditional_lamps(world, player):
|
|||||||
add_lamp_requirement(spot, player)
|
add_lamp_requirement(spot, player)
|
||||||
|
|
||||||
dark_rooms = {
|
dark_rooms = {
|
||||||
'TR Dark Ride': {'sewer': False, 'entrances': ['TR Dark Ride Up Stairs', 'TR Dark Ride SW'], 'locations': []},
|
'TR Dark Ride': {'sewer': False, 'entrances': ['TR Dark Ride Up Stairs', 'TR Dark Ride SW', 'TR Dark Ride Path'], 'locations': []},
|
||||||
|
'TR Dark Ride Ledges': {'sewer': False, 'entrances': ['TR Dark Ride Ledges Path'], 'locations': []},
|
||||||
'Mire Dark Shooters': {'sewer': False, 'entrances': ['Mire Dark Shooters Up Stairs', 'Mire Dark Shooters SW', 'Mire Dark Shooters SE'], 'locations': []},
|
'Mire Dark Shooters': {'sewer': False, 'entrances': ['Mire Dark Shooters Up Stairs', 'Mire Dark Shooters SW', 'Mire Dark Shooters SE'], 'locations': []},
|
||||||
'Mire Key Rupees': {'sewer': False, 'entrances': ['Mire Key Rupees NE'], 'locations': []},
|
'Mire Key Rupees': {'sewer': False, 'entrances': ['Mire Key Rupees NE'], 'locations': []},
|
||||||
'Mire Block X': {'sewer': False, 'entrances': ['Mire Block X NW', 'Mire Block X WS'], 'locations': []},
|
'Mire Block X': {'sewer': False, 'entrances': ['Mire Block X NW', 'Mire Block X WS'], 'locations': []},
|
||||||
@@ -1499,6 +1575,10 @@ def add_conditional_lamps(world, player):
|
|||||||
'Sewers Rope Room': {'sewer': True, 'entrances': ['Sewers Rope Room Up Stairs', 'Sewers Rope Room North Stairs'], 'locations': []},
|
'Sewers Rope Room': {'sewer': True, 'entrances': ['Sewers Rope Room Up Stairs', 'Sewers Rope Room North Stairs'], 'locations': []},
|
||||||
'Sewers Water': {'sewer': True, 'entrances': ['Sewers Water S', 'Sewers Water W'], 'locations': []},
|
'Sewers Water': {'sewer': True, 'entrances': ['Sewers Water S', 'Sewers Water W'], 'locations': []},
|
||||||
'Sewers Key Rat': {'sewer': True, 'entrances': ['Sewers Key Rat E', 'Sewers Key Rat Key Door N'], 'locations': ['Hyrule Castle - Key Rat Key Drop']},
|
'Sewers Key Rat': {'sewer': True, 'entrances': ['Sewers Key Rat E', 'Sewers Key Rat Key Door N'], 'locations': ['Hyrule Castle - Key Rat Key Drop']},
|
||||||
|
'Old Man Cave': {'sewer': False, 'entrances': ['Old Man Cave Exit (East)']},
|
||||||
|
'Old Man House Back': {'sewer': False, 'entrances': ['Old Man House Back to Front', 'Old Man House Exit (Top)']},
|
||||||
|
'Death Mountain Return Cave (left)': {'sewer': False, 'entrances': ['Death Mountain Return Cave E', 'Death Mountain Return Cave Exit (West)']},
|
||||||
|
'Death Mountain Return Cave (right)': {'sewer': False, 'entrances': ['Death Mountain Return Cave Exit (East)', 'Death Mountain Return Cave W']},
|
||||||
}
|
}
|
||||||
|
|
||||||
dark_debug_set = set()
|
dark_debug_set = set()
|
||||||
@@ -1515,17 +1595,12 @@ def add_conditional_lamps(world, player):
|
|||||||
dark_debug_set.add(region)
|
dark_debug_set.add(region)
|
||||||
for ent in info['entrances']:
|
for ent in info['entrances']:
|
||||||
add_conditional_lamp(ent, region, 'Entrance')
|
add_conditional_lamp(ent, region, 'Entrance')
|
||||||
for loc in info['locations']:
|
r = world.get_region(region, player)
|
||||||
|
for loc in r.locations:
|
||||||
add_conditional_lamp(loc, region, 'Location')
|
add_conditional_lamp(loc, region, 'Location')
|
||||||
logging.getLogger('').debug('Non Dark Regions: ' + ', '.join(set(dark_rooms.keys()).difference(dark_debug_set)))
|
logging.getLogger('').debug('Non Dark Regions: ' + ', '.join(set(dark_rooms.keys()).difference(dark_debug_set)))
|
||||||
|
|
||||||
add_conditional_lamp('Old Man', 'Old Man Cave', 'Location')
|
|
||||||
add_conditional_lamp('Old Man Cave Exit (East)', 'Old Man Cave', 'Entrance')
|
|
||||||
add_conditional_lamp('Death Mountain Return Cave Exit (East)', 'Death Mountain Return Cave', 'Entrance')
|
|
||||||
add_conditional_lamp('Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave', 'Entrance')
|
|
||||||
add_conditional_lamp('Old Man House Front to Back', 'Old Man House', 'Entrance')
|
add_conditional_lamp('Old Man House Front to Back', 'Old Man House', 'Entrance')
|
||||||
add_conditional_lamp('Old Man House Back to Front', 'Old Man House', 'Entrance')
|
|
||||||
|
|
||||||
|
|
||||||
def open_rules(world, player):
|
def open_rules(world, player):
|
||||||
# softlock protection as you can reach the sewers small key door with a guard drop key
|
# softlock protection as you can reach the sewers small key door with a guard drop key
|
||||||
@@ -1593,8 +1668,8 @@ def add_connection(parent_name, target_name, entrance_name, world, player):
|
|||||||
def standard_rules(world, player):
|
def standard_rules(world, player):
|
||||||
add_connection('Menu', 'Hyrule Castle Secret Entrance', 'Uncle S&Q', world, player)
|
add_connection('Menu', 'Hyrule Castle Secret Entrance', 'Uncle S&Q', world, player)
|
||||||
world.get_entrance('Uncle S&Q', player).hide_path = True
|
world.get_entrance('Uncle S&Q', player).hide_path = True
|
||||||
set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player) and state.has('Zelda Delivered', player))
|
set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player) and state.has('Zelda Delivered', player))
|
set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
# these are because of rails
|
# these are because of rails
|
||||||
if world.shuffle[player] != 'vanilla':
|
if world.shuffle[player] != 'vanilla':
|
||||||
# where ever these happen to be
|
# where ever these happen to be
|
||||||
@@ -1602,6 +1677,8 @@ def standard_rules(world, player):
|
|||||||
entrance = world.get_portal(portal_name, player).door.entrance
|
entrance = world.get_portal(portal_name, player).door.entrance
|
||||||
set_rule(entrance, lambda state: state.has('Zelda Delivered', 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))
|
set_rule(world.get_entrance('Sanctuary Exit', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
|
set_rule(world.get_entrance('Hyrule Castle Ledge Drop', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
|
set_rule(world.get_entrance('Hyrule Castle Main Gate (North)', player), lambda state: state.has('Zelda Delivered', player))
|
||||||
# zelda should be saved before agahnim is in play
|
# zelda should be saved before agahnim is in play
|
||||||
add_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))
|
||||||
|
|
||||||
@@ -1870,7 +1947,7 @@ bunny_revivable_entrances = {
|
|||||||
"Ice Many Pots", "Mire South Fish", "Mire Right Bridge", "Mire Left Bridge",
|
"Ice Many Pots", "Mire South Fish", "Mire Right Bridge", "Mire Left Bridge",
|
||||||
"TR Boss", "Eastern Hint Tile Blocked Path", "Thieves Spike Switch",
|
"TR Boss", "Eastern Hint Tile Blocked Path", "Thieves Spike Switch",
|
||||||
"Thieves Boss", "Mire Spike Barrier", "Mire Cross", "Mire Hidden Shooters",
|
"Thieves Boss", "Mire Spike Barrier", "Mire Cross", "Mire Hidden Shooters",
|
||||||
"Mire Spikes", "TR Final Abyss", "TR Dark Ride", "TR Pokey 1", "TR Tile Room",
|
"Mire Spikes", "TR Final Abyss Balcony", "TR Dark Ride", "TR Pokey 1", "TR Tile Room",
|
||||||
"TR Roller Room", "Eastern Cannonball", "Thieves Hallway", "Ice Switch Room",
|
"TR Roller Room", "Eastern Cannonball", "Thieves Hallway", "Ice Switch Room",
|
||||||
"Mire Tile Room", "Mire Conveyor Crystal", "Mire Hub", "TR Dash Bridge",
|
"Mire Tile Room", "Mire Conveyor Crystal", "Mire Hub", "TR Dash Bridge",
|
||||||
"TR Hub", "Eastern Boss", "Eastern Lobby", "Thieves Ambush",
|
"TR Hub", "Eastern Boss", "Eastern Lobby", "Thieves Ambush",
|
||||||
@@ -1934,15 +2011,17 @@ bunny_impassible_doors = {
|
|||||||
'Mire Cross ES', 'Mire Left Bridge Hook Path', 'Mire Fishbone Blue Barrier',
|
'Mire Cross ES', 'Mire Left Bridge Hook Path', 'Mire Fishbone Blue Barrier',
|
||||||
'Mire South Fish Blue Barrier', 'Mire Tile Room NW', 'Mire Compass Blue Barrier', 'Mire Attic Hint Hole',
|
'Mire South Fish Blue Barrier', 'Mire Tile Room NW', 'Mire Compass Blue Barrier', 'Mire Attic Hint Hole',
|
||||||
'Mire Dark Shooters SW', 'Mire Crystal Mid Blue Barrier', 'Mire Crystal Left Blue Barrier', 'TR Main Lobby Gap',
|
'Mire Dark Shooters SW', 'Mire Crystal Mid Blue Barrier', 'Mire Crystal Left Blue Barrier', 'TR Main Lobby Gap',
|
||||||
'TR Lobby Ledge Gap', 'TR Hub SW', 'TR Hub SE', 'TR Hub ES', 'TR Hub EN', 'TR Hub NW', 'TR Hub NE', 'TR Torches NW',
|
'TR Lobby Ledge Gap', 'TR Hub SW', 'TR Hub SE', 'TR Hub ES', 'TR Hub EN', 'TR Hub NW', 'TR Hub NE', 'TR Hub Path',
|
||||||
'TR Pokey 2 Bottom to Top Barrier - Blue', 'TR Pokey 2 Top to Bottom Barrier - Blue', 'TR Twin Pokeys SW', 'TR Twin Pokeys EN', 'TR Big Chest Gap',
|
'TR Hub Ledges Path', 'TR Torches NW', 'TR Pokey 2 Bottom to Top Barrier - Blue',
|
||||||
|
'TR Pokey 2 Top to Bottom Barrier - Blue', 'TR Twin Pokeys SW', 'TR Twin Pokeys EN', 'TR Big Chest Gap',
|
||||||
'TR Big Chest Entrance Gap', 'TR Lazy Eyes ES', 'TR Tongue Pull WS', 'TR Tongue Pull NE', 'TR Dark Ride Up Stairs',
|
'TR Big Chest Entrance Gap', 'TR Lazy Eyes ES', 'TR Tongue Pull WS', 'TR Tongue Pull NE', 'TR Dark Ride Up Stairs',
|
||||||
'TR Dark Ride SW', 'TR Crystal Maze Start to Interior Barrier - Blue', 'TR Crystal Maze End to Interior Barrier - Blue',
|
'TR Dark Ride SW', 'TR Dark Ride Path', 'TR Dark Ride Ledges Path',
|
||||||
'TR Final Abyss South Stairs', 'TR Final Abyss NW', 'GT Hope Room EN', 'GT Blocked Stairs Block Path',
|
'TR Crystal Maze Start to Interior Barrier - Blue', 'TR Crystal Maze End to Interior Barrier - Blue',
|
||||||
|
'TR Final Abyss Balcony Path', 'TR Final Abyss Ledge Path', 'GT Hope Room EN', 'GT Blocked Stairs Block Path',
|
||||||
'GT Bob\'s Room Hole', 'GT Speed Torch SE', 'GT Speed Torch South Path', 'GT Speed Torch North Path',
|
'GT Bob\'s Room Hole', 'GT Speed Torch SE', 'GT Speed Torch South Path', 'GT Speed Torch North Path',
|
||||||
'GT Crystal Conveyor NE', 'GT Crystal Conveyor WN', 'GT Conveyor Cross EN', 'GT Conveyor Cross WN',
|
'GT Crystal Conveyor NE', 'GT Crystal Conveyor WN', 'GT Conveyor Cross EN', 'GT Conveyor Cross WN',
|
||||||
'GT Hookshot East-North Path', 'GT Hookshot East-South Path', 'GT Hookshot North-East Path',
|
'GT Hookshot East-Mid Path', 'GT Hookshot South-Mid Path', 'GT Hookshot North-Mid Path',
|
||||||
'GT Hookshot North-South Path', 'GT Hookshot South-East Path', 'GT Hookshot South-North Path',
|
'GT Hookshot Mid-South Path', 'GT Hookshot Mid-East Path', 'GT Hookshot Mid-North Path',
|
||||||
'GT Hookshot Platform Blue Barrier', 'GT Hookshot Entry Blue Barrier', 'GT Double Switch Pot Corners to Exit Barrier - Blue',
|
'GT Hookshot Platform Blue Barrier', 'GT Hookshot Entry Blue Barrier', 'GT Double Switch Pot Corners to Exit Barrier - Blue',
|
||||||
'GT Double Switch Exit to Blue Barrier', 'GT Firesnake Room Hook Path', 'GT Falling Bridge WN', 'GT Falling Bridge WS',
|
'GT Double Switch Exit to Blue Barrier', 'GT Firesnake Room Hook Path', 'GT Falling Bridge WN', 'GT Falling Bridge WS',
|
||||||
'GT Ice Armos NE', 'GT Ice Armos WS', 'GT Crystal Paths SW', 'GT Mimics 1 NW', 'GT Mimics 1 ES', 'GT Mimics 2 WS',
|
'GT Ice Armos NE', 'GT Ice Armos WS', 'GT Crystal Paths SW', 'GT Mimics 1 NW', 'GT Mimics 1 ES', 'GT Mimics 2 WS',
|
||||||
|
|||||||
+1
-1
@@ -45,7 +45,7 @@ def main(args=None):
|
|||||||
|
|
||||||
test("Vanilla ", "--shuffle vanilla")
|
test("Vanilla ", "--shuffle vanilla")
|
||||||
test("Retro ", "--retro --shuffle vanilla")
|
test("Retro ", "--retro --shuffle vanilla")
|
||||||
test("Keysanity ", "--shuffle vanilla --keydropshuffle --keysanity")
|
test("Keysanity ", "--shuffle vanilla --keydropshuffle drops_only --keysanity")
|
||||||
test("Shopsanity", "--shuffle vanilla --shopsanity")
|
test("Shopsanity", "--shuffle vanilla --shopsanity")
|
||||||
test("Simple ", "--shuffle simple")
|
test("Simple ", "--shuffle simple")
|
||||||
test("Full ", "--shuffle full")
|
test("Full ", "--shuffle full")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: UTF-8 -*-
|
# -*- coding: UTF-8 -*-
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
text_addresses = {'Pedestal': (0x180300, 256),
|
text_addresses = {'Pedestal': (0x180300, 256),
|
||||||
'Triforce': (0x180400, 256),
|
'Triforce': (0x180400, 256),
|
||||||
@@ -625,11 +626,16 @@ class MultiByteCoreTextMapper(object):
|
|||||||
"{IBOX}": [0x6B, 0x02, 0x77, 0x07, 0x7A, 0x03],
|
"{IBOX}": [0x6B, 0x02, 0x77, 0x07, 0x7A, 0x03],
|
||||||
"{C:GREEN}": [0x77, 0x07],
|
"{C:GREEN}": [0x77, 0x07],
|
||||||
"{C:YELLOW}": [0x77, 0x02],
|
"{C:YELLOW}": [0x77, 0x02],
|
||||||
|
"{C:WHITE}": [0x77, 0x06],
|
||||||
|
"{C:INV_WHITE}": [0x77, 0x16],
|
||||||
|
"{C:INV_YELLOW}": [0x77, 0x12],
|
||||||
|
"{C:INV_GREEN}": [0x77, 0x17],
|
||||||
|
"{C:RED}": [0x77, 0x01],
|
||||||
|
"{C:INV_RED}": [0x77, 0x11],
|
||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def convert(cls, text, pause=True, wrap=19):
|
def convert(cls, text, pause=True, wrap=19):
|
||||||
#text = text.upper()
|
|
||||||
lines = text.split('\n')
|
lines = text.split('\n')
|
||||||
outbuf = bytearray()
|
outbuf = bytearray()
|
||||||
lineindex = 0
|
lineindex = 0
|
||||||
@@ -639,7 +645,9 @@ class MultiByteCoreTextMapper(object):
|
|||||||
while lines:
|
while lines:
|
||||||
linespace = wrap
|
linespace = wrap
|
||||||
line = lines.pop(0)
|
line = lines.pop(0)
|
||||||
if line.startswith('{'):
|
|
||||||
|
match = re.search('^\{[A-Z0-9_:]+\}$', line)
|
||||||
|
if match:
|
||||||
if line == '{PAGEBREAK}':
|
if line == '{PAGEBREAK}':
|
||||||
if lineindex % 3 != 0:
|
if lineindex % 3 != 0:
|
||||||
# insert a wait for keypress, unless we just did so
|
# insert a wait for keypress, unless we just did so
|
||||||
@@ -656,9 +664,27 @@ class MultiByteCoreTextMapper(object):
|
|||||||
pending_space = False
|
pending_space = False
|
||||||
while words:
|
while words:
|
||||||
word = words.pop(0)
|
word = words.pop(0)
|
||||||
# sanity check: if the word we have is more than 19 characters, we take as much as we can still fit and push the rest back for later
|
|
||||||
|
match = re.search('^(\{[A-Z0-9_:]+\}).*', word)
|
||||||
|
if match:
|
||||||
|
start_command = match.group(1)
|
||||||
|
outbuf.extend(cls.special_commands[start_command])
|
||||||
|
word = word.replace(start_command, '')
|
||||||
|
|
||||||
|
match = re.search('(\{[A-Z0-9_:]+\})\.?$', word)
|
||||||
|
if match:
|
||||||
|
end_command = match.group(1)
|
||||||
|
word = word.replace(end_command, '')
|
||||||
|
period = word.endswith('.')
|
||||||
|
else:
|
||||||
|
end_command, period = None, False
|
||||||
|
|
||||||
|
# sanity check: if the word we have is more than 19 characters,
|
||||||
|
# we take as much as we can still fit and push the rest back for later
|
||||||
if cls.wordlen(word) > wrap:
|
if cls.wordlen(word) > wrap:
|
||||||
(word_first, word_rest) = cls.splitword(word, linespace)
|
(word_first, word_rest) = cls.splitword(word, linespace)
|
||||||
|
if end_command:
|
||||||
|
word_rest = (word_rest[:-1] + end_command + '.') if period else (word_rest + end_command)
|
||||||
words.insert(0, word_rest)
|
words.insert(0, word_rest)
|
||||||
lines.insert(0, ' '.join(words))
|
lines.insert(0, ' '.join(words))
|
||||||
|
|
||||||
@@ -671,9 +697,16 @@ class MultiByteCoreTextMapper(object):
|
|||||||
if cls.wordlen(word) < linespace:
|
if cls.wordlen(word) < linespace:
|
||||||
pending_space = True
|
pending_space = True
|
||||||
linespace -= cls.wordlen(word) + 1 if pending_space else 0
|
linespace -= cls.wordlen(word) + 1 if pending_space else 0
|
||||||
outbuf.extend(RawMBTextMapper.convert(word))
|
word_to_map = word[:-1] if period else word
|
||||||
|
outbuf.extend(RawMBTextMapper.convert(word_to_map))
|
||||||
|
if end_command:
|
||||||
|
outbuf.extend(cls.special_commands[end_command])
|
||||||
|
if period:
|
||||||
|
outbuf.extend(RawMBTextMapper.convert('.'))
|
||||||
else:
|
else:
|
||||||
# ran out of space, push word and lines back and continue with next line
|
# ran out of space, push word and lines back and continue with next line
|
||||||
|
if end_command:
|
||||||
|
word = (word[:-1] + end_command + '.') if period else (word + end_command)
|
||||||
words.insert(0, word)
|
words.insert(0, word)
|
||||||
lines.insert(0, ' '.join(words))
|
lines.insert(0, ' '.join(words))
|
||||||
break
|
break
|
||||||
@@ -789,7 +822,6 @@ class CharTextMapper(object):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def convert(cls, text):
|
def convert(cls, text):
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
#for char in text.lower():
|
|
||||||
for char in text:
|
for char in text:
|
||||||
buf.append(cls.map_char(char))
|
buf.append(cls.map_char(char))
|
||||||
return buf
|
return buf
|
||||||
@@ -1257,7 +1289,6 @@ class RawMBTextMapper(CharTextMapper):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def convert(cls, text):
|
def convert(cls, text):
|
||||||
buf = bytearray()
|
buf = bytearray()
|
||||||
#for char in text.lower():
|
|
||||||
for char in text:
|
for char in text:
|
||||||
res = cls.map_char(char)
|
res = cls.map_char(char)
|
||||||
if isinstance(res, int):
|
if isinstance(res, int):
|
||||||
@@ -1743,7 +1774,7 @@ class TextTable(object):
|
|||||||
text['telepathic_tile_misery_mire'] = CompressedTextMapper.convert("{NOBORDER}\nLighting 4 torches will open your way forward!")
|
text['telepathic_tile_misery_mire'] = CompressedTextMapper.convert("{NOBORDER}\nLighting 4 torches will open your way forward!")
|
||||||
text['hylian_text_2'] = CompressedTextMapper.convert("%%^= %==%\n ^ =%^=\n==%= ^^%^")
|
text['hylian_text_2'] = CompressedTextMapper.convert("%%^= %==%\n ^ =%^=\n==%= ^^%^")
|
||||||
text['desert_entry_translated'] = CompressedTextMapper.convert("Kneel before this stone, and magic will move around you.")
|
text['desert_entry_translated'] = CompressedTextMapper.convert("Kneel before this stone, and magic will move around you.")
|
||||||
text['telepathic_tile_under_ganon'] = CompressedTextMapper.convert("Secondary tournament winners\n{HARP}\n ~~~2021~~~\nmatt7898\n\n ~~~2020~~~\nrelkin")
|
text['telepathic_tile_under_ganon'] = CompressedTextMapper.convert("Doors Async League winners\n{HARP}\n ~~~2022~~~\nAndy\n\n ~~~2021~~~\nprdwong")
|
||||||
text['telepathic_tile_palace_of_darkness'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a funny looking Enemizer")
|
text['telepathic_tile_palace_of_darkness'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a funny looking Enemizer")
|
||||||
# C0
|
# C0
|
||||||
text['telepathic_tile_desert_bonk_torch_room'] = CompressedTextMapper.convert("{NOBORDER}\nThings can be knocked down, if you fancy yourself a dashing dude.")
|
text['telepathic_tile_desert_bonk_torch_room'] = CompressedTextMapper.convert("{NOBORDER}\nThings can be knocked down, if you fancy yourself a dashing dude.")
|
||||||
@@ -1753,7 +1784,7 @@ class TextTable(object):
|
|||||||
text['telepathic_tile_ice_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.")
|
text['telepathic_tile_ice_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nYou can use Fire Rod or Bombos to pass.")
|
||||||
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
|
text['telepathic_tile_ice_stalfos_knights_room'] = CompressedTextMapper.convert("{NOBORDER}\nKnock 'em down and then bomb them dead.")
|
||||||
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
|
text['telepathic_tile_tower_of_hera_entrance'] = CompressedTextMapper.convert("{NOBORDER}\nThis is a bad place, with a guy who will make you fall…\n\n\na lot.")
|
||||||
text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n ~~~2021~~~\ndaaanty\n\n ~~~2019~~~\nJet082")
|
text['houlihan_room'] = CompressedTextMapper.convert("Randomizer tournament winners\n{HARP}\n ~~~2021~~~\nDaaanty\n\n ~~~2019~~~\nJet082\n\n ~~~2018~~~\nAndy\n\n ~~~2017~~~\nA: ajneb174\nS: ajneb174")
|
||||||
text['caught_a_bee'] = CompressedTextMapper.convert("Caught a Bee\n ≥ Keep\n Release\n{CHOICE}")
|
text['caught_a_bee'] = CompressedTextMapper.convert("Caught a Bee\n ≥ Keep\n Release\n{CHOICE}")
|
||||||
text['caught_a_fairy'] = CompressedTextMapper.convert("Caught Fairy!\n ≥ Keep\n Release\n{CHOICE}")
|
text['caught_a_fairy'] = CompressedTextMapper.convert("Caught Fairy!\n ≥ Keep\n Release\n{CHOICE}")
|
||||||
text['no_empty_bottles'] = CompressedTextMapper.convert("Whoa, bucko!\nNo empty bottles.")
|
text['no_empty_bottles'] = CompressedTextMapper.convert("Whoa, bucko!\nNo empty bottles.")
|
||||||
@@ -1981,7 +2012,7 @@ class TextTable(object):
|
|||||||
text['ganon_fall_in_alt'] = CompressedTextMapper.convert("You think you are ready to face me?\n\nI will not die unless you complete your goals. Dingus!")
|
text['ganon_fall_in_alt'] = CompressedTextMapper.convert("You think you are ready to face me?\n\nI will not die unless you complete your goals. Dingus!")
|
||||||
text['ganon_phase_3_alt'] = CompressedTextMapper.convert("Got wax in your ears? I cannot die!")
|
text['ganon_phase_3_alt'] = CompressedTextMapper.convert("Got wax in your ears? I cannot die!")
|
||||||
# 190
|
# 190
|
||||||
text['sign_east_death_mountain_bridge'] = CompressedTextMapper.convert("How did you get up here?")
|
text['sign_east_death_mountain_bridge'] = CompressedTextMapper.convert("Glitched\ntournament\nwinners\n{HARP}\n~~~HMG 2021~~~\nKrithel\n\n~~~OWG 2019~~~\nGlan\n\n~~~OWG 2018~~~\nChristosOwen\nthe numpty")
|
||||||
text['fish_money'] = CompressedTextMapper.convert("It's a secret to everyone.")
|
text['fish_money'] = CompressedTextMapper.convert("It's a secret to everyone.")
|
||||||
text['sign_ganons_tower'] = CompressedTextMapper.convert("You need all 7 crystals to enter.")
|
text['sign_ganons_tower'] = CompressedTextMapper.convert("You need all 7 crystals to enter.")
|
||||||
text['sign_ganon'] = CompressedTextMapper.convert("You need all 7 crystals to beat Ganon.")
|
text['sign_ganon'] = CompressedTextMapper.convert("You need all 7 crystals to beat Ganon.")
|
||||||
|
|||||||
@@ -585,10 +585,11 @@ def extract_data_from_jp_rom(rom):
|
|||||||
with open(rom, 'rb') as stream:
|
with open(rom, 'rb') as stream:
|
||||||
rom_data = bytearray(stream.read())
|
rom_data = bytearray(stream.read())
|
||||||
|
|
||||||
rooms = [0x1c, 0x1d, 0x4e]
|
rooms = range(0, 0x128)
|
||||||
# rooms = [0x7b, 0x7c, 0x7d, 0x8b, 0x8c, 0x8d, 0x9b, 0x9c, 0x9d]
|
# rooms = [0x7b, 0x7c, 0x7d, 0x8b, 0x8c, 0x8d, 0x9b, 0x9c, 0x9d]
|
||||||
# rooms = [0x1a, 0x2a, 0xd1]
|
# rooms = [0x1a, 0x2a, 0xd1]
|
||||||
for room in rooms:
|
for room in rooms:
|
||||||
|
# print(f'Room {room:02x}')
|
||||||
b2idx = room*2
|
b2idx = room*2
|
||||||
b3idx = room*3
|
b3idx = room*3
|
||||||
headerptr = 0x271e2 + b2idx
|
headerptr = 0x271e2 + b2idx
|
||||||
@@ -605,6 +606,7 @@ def extract_data_from_jp_rom(rom):
|
|||||||
stop, idx = False, 0
|
stop, idx = False, 0
|
||||||
ffcnt = 0
|
ffcnt = 0
|
||||||
mode = 0
|
mode = 0
|
||||||
|
secret_cnt = 0
|
||||||
# first two bytes
|
# first two bytes
|
||||||
b1 = rom_data[objectloc+idx]
|
b1 = rom_data[objectloc+idx]
|
||||||
b2 = rom_data[objectloc+idx+1]
|
b2 = rom_data[objectloc+idx+1]
|
||||||
@@ -626,9 +628,20 @@ def extract_data_from_jp_rom(rom):
|
|||||||
idx += 2
|
idx += 2
|
||||||
elif not mode and ffcnt < 3:
|
elif not mode and ffcnt < 3:
|
||||||
objectdata.append(b3)
|
objectdata.append(b3)
|
||||||
|
if b3 == 0xFA and ((b2 & 0x3) << 2) | (b1 & 0x3) in [0xf, 0xc]:
|
||||||
|
# potcalc
|
||||||
|
vram = ((b1 & 0xFC) >> 1) | ((b2 & 0xFC) << 5)
|
||||||
|
low = vram & 0xFF
|
||||||
|
high = (vram & 0xFF00) >> 8
|
||||||
|
if ffcnt == 1:
|
||||||
|
high |= 0x20
|
||||||
|
print(f'db {", ".join([f"${low:02x}", f"${high:02x}"])}')
|
||||||
|
secret_cnt += 1
|
||||||
idx += 3
|
idx += 3
|
||||||
else:
|
else:
|
||||||
idx += 2
|
idx += 2
|
||||||
|
if secret_cnt:
|
||||||
|
print(f'Room {room:02x} {secret_cnt}')
|
||||||
spriteptr = 0x4d62e + b2idx
|
spriteptr = 0x4d62e + b2idx
|
||||||
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
|
spriteloc = rom_data[spriteptr] + rom_data[spriteptr+1]*0x100 + 0x40000
|
||||||
secretptr = 0xdb67 + b2idx
|
secretptr = 0xdb67 + b2idx
|
||||||
@@ -652,23 +665,40 @@ def extract_data_from_jp_rom(rom):
|
|||||||
else:
|
else:
|
||||||
secretdata.append(b3)
|
secretdata.append(b3)
|
||||||
idx += 3
|
idx += 3
|
||||||
print(f'Room {room:02x}')
|
# print(f'Room {room:02x}')
|
||||||
print(f'HeaderPtr {headerptr:06x}')
|
# print(f'HeaderPtr {headerptr:06x}')
|
||||||
print(f'HeaderLoc {headerloc:06x}')
|
# print(f'HeaderLoc {headerloc:06x}')
|
||||||
print(f'db {",".join([f"${x:02x}" for x in header])}')
|
# print(f'db {",".join([f"${x:02x}" for x in header])}')
|
||||||
print(f'Obj Length: {len(objectdata)}')
|
# print(f'Obj Length: {len(objectdata)}')
|
||||||
print(f'ObjectPtr {objectptr:06x}')
|
# print(f'ObjectPtr {objectptr:06x}')
|
||||||
print(f'ObjectLoc {objectloc:06x}')
|
# print(f'ObjectLoc {objectloc:06x}')
|
||||||
for i in range(0, len(objectdata)//16 + 1):
|
# for i in range(0, len(objectdata)//16 + 1):
|
||||||
slice = objectdata[i*16:i*16+16]
|
# slice = objectdata[i*16:i*16+16]
|
||||||
print(f'db {",".join([f"${x:02x}" for x in slice])}')
|
# print(f'db {",".join([f"${x:02x}" for x in slice])}')
|
||||||
print(f'SpritePtr {spriteptr:06x}')
|
# print(f'SpritePtr {spriteptr:06x}')
|
||||||
print(f'SpriteLoc {spriteloc:06x}')
|
# print(f'SpriteLoc {spriteloc:06x}')
|
||||||
print_data_block(spritedata)
|
# print_data_block(spritedata)
|
||||||
print(f'SecretPtr {secretptr:06x}')
|
# print(f'SecretPtr {secretptr:06x}')
|
||||||
print(f'SecretLoc {secretloc:06x}')
|
# print(f'SecretLoc {secretloc:06x}')
|
||||||
print_data_block(secretdata)
|
# print_data_block(secretdata)
|
||||||
print()
|
# print()
|
||||||
|
|
||||||
|
def count_set_bits(val):
|
||||||
|
if val == 0:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
return (val & 1) + count_set_bits(val >> 1)
|
||||||
|
|
||||||
|
def check_pots():
|
||||||
|
from PotShuffle import vanilla_pots
|
||||||
|
for supertile, pot_list in vanilla_pots.items():
|
||||||
|
for i,pot in enumerate(pot_list):
|
||||||
|
if pot.obj_ref:
|
||||||
|
r = pot.obj_ref
|
||||||
|
secret_vram = pot.x | (pot.y << 8)
|
||||||
|
tile_vram = ((r.data[1] & 0xFC) << 5) | ((r.data[0] & 0xFC) >> 1)
|
||||||
|
if secret_vram != tile_vram:
|
||||||
|
print(f'{pot.room}#{i+1} secret: {hex(secret_vram)} tile: {hex(tile_vram)}')
|
||||||
|
|
||||||
|
|
||||||
def stack_size3a(size=2):
|
def stack_size3a(size=2):
|
||||||
@@ -712,4 +742,5 @@ if __name__ == '__main__':
|
|||||||
# read_entrance_data(old_rom=sys.argv[1])
|
# read_entrance_data(old_rom=sys.argv[1])
|
||||||
# room_palette_data(old_rom=sys.argv[1])
|
# room_palette_data(old_rom=sys.argv[1])
|
||||||
# extract_data_from_us_rom(sys.argv[1])
|
# extract_data_from_us_rom(sys.argv[1])
|
||||||
extract_data_from_jp_rom(sys.argv[1])
|
# extract_data_from_jp_rom(sys.argv[1])
|
||||||
|
check_pots()
|
||||||
|
|||||||
@@ -44,5 +44,4 @@ incsrc doortables.asm
|
|||||||
warnpc $288000
|
warnpc $288000
|
||||||
|
|
||||||
; deals with own hooks
|
; deals with own hooks
|
||||||
incsrc keydropshuffle.asm
|
|
||||||
incsrc owrando.asm
|
incsrc owrando.asm
|
||||||
|
|||||||
+1
-1
@@ -176,7 +176,7 @@ org $02d9ce ; <- Bank02.asm : Dungeon_LoadEntrance 10829 (STA $A0 : STA $048E)
|
|||||||
JSL CheckDarkWorldSpawn : NOP
|
JSL CheckDarkWorldSpawn : NOP
|
||||||
|
|
||||||
org $01891e ; <- Bank 01.asm : 991 Dungeon_LoadType2Object (LDA $00 : XBA : AND.w #$00FF)
|
org $01891e ; <- Bank 01.asm : 991 Dungeon_LoadType2Object (LDA $00 : XBA : AND.w #$00FF)
|
||||||
JSL RainPrevention : NOP #2
|
JSL RainPrevention : BCC + : RTS : NOP : +
|
||||||
|
|
||||||
org $1edabf ; <- sprite_energy_ball.asm : 86-7 Sprite_EnergyBall (LDA.b #$10 : LDX.b #$00)
|
org $1edabf ; <- sprite_energy_ball.asm : 86-7 Sprite_EnergyBall (LDA.b #$10 : LDX.b #$00)
|
||||||
JSL StandardAgaDmg
|
JSL StandardAgaDmg
|
||||||
|
|||||||
+59
-17
@@ -7,23 +7,37 @@ DrHudOverride:
|
|||||||
|
|
||||||
HudAdditions:
|
HudAdditions:
|
||||||
{
|
{
|
||||||
lda.l DRFlags : and #$0008 : beq ++
|
LDA.l DRFlags : AND #$0008 : BNE + : JMP .end_item_count : +
|
||||||
; LDA.w #$28A4 : STA !GOAL_DRAW_ADDRESS
|
LDA.l $7EF423 : PHA : CMP #1000 : !BLT +
|
||||||
lda $7EF423
|
JSL HexToDec4Digit_fast
|
||||||
jsr HudHexToDec4DigitCopy
|
LDX.b $04 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS ; draw 1000's digit
|
||||||
LDX.b $05 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+2 ; draw 100's digit
|
BRA .skip
|
||||||
LDX.b $06 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+4 ; draw 10's digit
|
+ JSL HexToDec_fast
|
||||||
LDX.b $07 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+6 ; draw 1's digit
|
.skip
|
||||||
|
LDA #$207F : STA !GOAL_DRAW_ADDRESS+2 : STA !GOAL_DRAW_ADDRESS+4
|
||||||
|
PLA : PHA : CMP.w #100 : !BLT +
|
||||||
|
LDX.b $05 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS+2 ; draw 100's digit
|
||||||
|
+ PLA : CMP.w #10 : !BLT +
|
||||||
|
LDX.b $06 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS+4 ; draw 10's digit
|
||||||
|
+ LDX.b $07 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS+6 ; draw 1's digit
|
||||||
LDA.w #$2830 : STA !GOAL_DRAW_ADDRESS+8 ; draw slash
|
LDA.w #$2830 : STA !GOAL_DRAW_ADDRESS+8 ; draw slash
|
||||||
LDA.l DRFlags : AND #$0100 : BNE +
|
LDA.l DRFlags : AND #$0100 : BNE +
|
||||||
lda $7EF33E
|
LDA.l $7EF33E : CMP #1000 : !BLT .three_digit_goal
|
||||||
jsr HudHexToDec4DigitCopy
|
JSL HexToDec4Digit_fast
|
||||||
LDX.b $05 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+10 ; draw 100's digit
|
LDX.b $04 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS+10 ; draw 1000's digit
|
||||||
LDX.b $06 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+12 ; draw 10's digit
|
LDX.b $05 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS+12 ; draw 100's digit
|
||||||
LDX.b $07 : TXA : ORA.w #$2400 : STA !GOAL_DRAW_ADDRESS+14 ; draw 1's digit
|
LDX.b $06 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS+14 ; draw 10's digit
|
||||||
BRA ++
|
LDX.b $07 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS+16 ; draw 1's digit
|
||||||
+ LDA.w #$2405 : STA !GOAL_DRAW_ADDRESS+10 : STA !GOAL_DRAW_ADDRESS+12 : STA !GOAL_DRAW_ADDRESS+14
|
BRA .end_item_count
|
||||||
++
|
.three_digit_goal
|
||||||
|
JSL HexToDec_fast
|
||||||
|
LDX.b $05 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS+10 ; draw 100's digit
|
||||||
|
LDX.b $06 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS+12 ; draw 10's digit
|
||||||
|
LDX.b $07 : TXA : ORA.w #$2490 : STA !GOAL_DRAW_ADDRESS+14 ; draw 1's digit
|
||||||
|
BRA .end_item_count
|
||||||
|
+ LDA.w #$2405 : STA !GOAL_DRAW_ADDRESS+10 : STA !GOAL_DRAW_ADDRESS+12
|
||||||
|
STA !GOAL_DRAW_ADDRESS+14 : STA !GOAL_DRAW_ADDRESS+16
|
||||||
|
.end_item_count
|
||||||
|
|
||||||
LDX $1B : BNE + : RTS : + ; Skip if outdoors
|
LDX $1B : BNE + : RTS : + ; Skip if outdoors
|
||||||
ldx $040c : cpx #$ff : bne + : rts : + ; Skip if not in dungeon
|
ldx $040c : cpx #$ff : bne + : rts : + ; Skip if not in dungeon
|
||||||
@@ -141,7 +155,7 @@ DrHudDungeonItemsAdditions:
|
|||||||
iny #2
|
iny #2
|
||||||
lda.w #$24f5 : sta $1644, y ; blank out map spot
|
lda.w #$24f5 : sta $1644, y ; blank out map spot
|
||||||
lda $7ef368 : and.l $0098c0, x : beq + ; must have map
|
lda $7ef368 : and.l $0098c0, x : beq + ; must have map
|
||||||
lda #$2826 : sta $1644, y ; check mark
|
JSR MapIndicatorShort : STA $1644, Y
|
||||||
+ iny #2
|
+ iny #2
|
||||||
cpx #$001a : bne +
|
cpx #$001a : bne +
|
||||||
tya : !add #$003c : tay
|
tya : !add #$003c : tay
|
||||||
@@ -164,6 +178,34 @@ DrHudDungeonItemsAdditions:
|
|||||||
plp : ply : plx : rtl
|
plp : ply : plx : rtl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MapIndicatorLong:
|
||||||
|
PHX
|
||||||
|
LDA.l OldHudToNewHudTable, X : TAX
|
||||||
|
JSR MapIndicator
|
||||||
|
PLX
|
||||||
|
RTL
|
||||||
|
|
||||||
|
MapIndicatorShort:
|
||||||
|
PHX
|
||||||
|
TXA : LSR : TAX
|
||||||
|
JSR MapIndicator
|
||||||
|
PLX
|
||||||
|
RTS
|
||||||
|
|
||||||
|
OldHudToNewHudTable:
|
||||||
|
dw 1, 2, 3, 10, 4, 6, 5, 8, 11, 9, 7, 12, 13
|
||||||
|
|
||||||
|
IndicatorCharacters:
|
||||||
|
; check 1 2 3 4 5 6 7 G B R
|
||||||
|
dw $2426, $2817, $2818, $2819, $281A, $281B, $281C, $281D, $2590, $258B, $259B
|
||||||
|
|
||||||
|
MapIndicator:
|
||||||
|
LDA.l CrystalPendantFlags_3, X : AND #$00FF
|
||||||
|
PHX
|
||||||
|
ASL : TAX : LDA.l IndicatorCharacters, X
|
||||||
|
PLX
|
||||||
|
RTS
|
||||||
|
|
||||||
BkStatus:
|
BkStatus:
|
||||||
lda $7ef366 : and.l $0098c0, x : bne +++ ; has the bk already
|
lda $7ef366 : and.l $0098c0, x : bne +++ ; has the bk already
|
||||||
lda.l BigKeyStatus, x : bne ++
|
lda.l BigKeyStatus, x : bne ++
|
||||||
@@ -181,7 +223,7 @@ ConvertToDisplay:
|
|||||||
ConvertToDisplay2:
|
ConvertToDisplay2:
|
||||||
and.w #$00ff : beq ++
|
and.w #$00ff : beq ++
|
||||||
cmp #$000a : !blt +
|
cmp #$000a : !blt +
|
||||||
!add #$2553 : rts
|
!add #$2553 : rts ; todo: use 2580 with 258A as "A" for non transparent digits
|
||||||
+ !add #$2816 : rts
|
+ !add #$2816 : rts
|
||||||
++ lda #$2827 : rts ; 0/O for 0 or placeholder digit ;2483
|
++ lda #$2827 : rts ; 0/O for 0 or placeholder digit ;2483
|
||||||
|
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
org $06926e ; <- 3126e - sprite_prep.asm : 2664 (LDA $0B9B : STA $0CBA, X)
|
|
||||||
jsl SpriteKeyPrep : nop #2
|
|
||||||
|
|
||||||
org $06d049 ; <- 35049 sprite_absorbable : 31-32 (JSL Sprite_DrawRippleIfInWater : JSR Sprite_DrawAbsorbable)
|
|
||||||
jsl SpriteKeyDrawGFX : bra + : nop : +
|
|
||||||
|
|
||||||
org $06d180
|
|
||||||
jsl BigKeyGet : bcs $07 : nop #5
|
|
||||||
|
|
||||||
org $06d18d ; <- 3518D - sprite_absorbable.asm : 274 (LDA $7EF36F : INC A : STA $7EF36F)
|
|
||||||
jsl KeyGet
|
|
||||||
|
|
||||||
org $06f9f3 ; bank06.asm : 6732 (JSL Sprite_LoadProperties)
|
|
||||||
jsl LoadProperties_PreserveItemMaybe
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
org $06d23a
|
|
||||||
Sprite_DrawAbsorbable:
|
|
||||||
org $1eff81
|
|
||||||
Sprite_DrawRippleIfInWater:
|
|
||||||
org $0db818
|
|
||||||
Sprite_LoadProperties:
|
|
||||||
|
|
||||||
org $288000 ;140000
|
|
||||||
ShuffleKeyDrops:
|
|
||||||
db 0
|
|
||||||
MultiClientFlags: ; 140001 -> stored in SRAM at 7ef33d
|
|
||||||
db 0
|
|
||||||
|
|
||||||
LootTable: ;PC: 140002
|
|
||||||
db $0e, $00, $24 ;; ice jelly key
|
|
||||||
db $13, $00, $24 ;; pokey 2
|
|
||||||
db $16, $00, $24 ;; swamp waterway pot
|
|
||||||
db $21, $00, $24 ;; key rat
|
|
||||||
db $35, $00, $24 ;; swamp trench 2 pot
|
|
||||||
db $36, $00, $24 ;; hookshot pot
|
|
||||||
db $37, $00, $24 ;; trench 1 pot
|
|
||||||
db $38, $00, $24 ;; pot row pot
|
|
||||||
db $39, $00, $24 ;; skull gibdo
|
|
||||||
db $3d, $00, $24 ;; gt minihelma
|
|
||||||
db $3e, $00, $24 ;; ice conveyor
|
|
||||||
db $3f, $00, $24 ;; ice hammer block ??? is this a dungeon secret?
|
|
||||||
db $43, $00, $24 ;; tiles 2 pot
|
|
||||||
db $53, $00, $24 ;; beamos hall pot
|
|
||||||
db $56, $00, $24 ;; skull west lobby pot
|
|
||||||
db $63, $00, $24 ;; desert tiles 1 pot
|
|
||||||
db $71, $00, $24 ;; boomerang guard
|
|
||||||
db $72, $00, $24 ;; hc map guard
|
|
||||||
db $7b, $00, $24 ;; gt star pits pot
|
|
||||||
db $80, $00, $32 ;; a big key (for the current dungeon)
|
|
||||||
db $8b, $00, $24 ;; gt conv cross block
|
|
||||||
db $9b, $00, $24 ;; gt dlb switch pot
|
|
||||||
db $9f, $00, $24 ;; ice many pots
|
|
||||||
db $99, $00, $24 ;; eastern eyegore
|
|
||||||
db $a1, $00, $24 ;; mire fishbone pot
|
|
||||||
db $ab, $00, $24 ;; tt spike switch pot
|
|
||||||
db $b0, $00, $24 ;; tower circle of pots usain
|
|
||||||
db $b3, $00, $24 ;; mire spikes pot
|
|
||||||
db $b6, $00, $24 ;; pokey 1
|
|
||||||
db $ba, $00, $24 ;; eastern dark pot
|
|
||||||
db $bc, $00, $24 ;; tt hallway pot
|
|
||||||
db $c0, $00, $24 ;; tower dark archer
|
|
||||||
db $c1, $00, $24 ;; mire glitchy jelly
|
|
||||||
db $ff, $00, $ff
|
|
||||||
;140068
|
|
||||||
|
|
||||||
KeyTable:
|
|
||||||
db $a0, $a0, $a2, $a3, $a4, $a5, $a6, $a7, $a8, $a9, $aa, $ab, $ac, $ad
|
|
||||||
|
|
||||||
SpriteKeyPrep:
|
|
||||||
{
|
|
||||||
lda $0b9b : sta $0cba, x ; what we wrote over
|
|
||||||
pha
|
|
||||||
lda.l ShuffleKeyDrops : beq +
|
|
||||||
phx
|
|
||||||
ldx #$fd
|
|
||||||
- inx #3 : lda.l LootTable, x : cmp #$ff : beq ++ : cmp $a0 : bne -
|
|
||||||
inx : lda.l LootTable, x : sta !MULTIWORLD_SPRITEITEM_PLAYER_ID
|
|
||||||
inx : lda.l LootTable, x
|
|
||||||
plx : sta $0e80, x
|
|
||||||
cmp #$24 : bne +++
|
|
||||||
lda $a0 : cmp #$80 : bne + : lda #$24
|
|
||||||
+++ jsl PrepDynamicTile : bra +
|
|
||||||
++ plx : lda #$24 : sta $0e80, x
|
|
||||||
+ pla
|
|
||||||
rtl
|
|
||||||
}
|
|
||||||
|
|
||||||
SpriteKeyDrawGFX:
|
|
||||||
{
|
|
||||||
jsl Sprite_DrawRippleIfInWater
|
|
||||||
pha
|
|
||||||
lda.l ShuffleKeyDrops : bne +
|
|
||||||
- pla
|
|
||||||
phk : pea.w .jslrtsreturn-1
|
|
||||||
pea.w $068014 ; an rtl address - 1 in Bank06
|
|
||||||
jml Sprite_DrawAbsorbable
|
|
||||||
.jslrtsreturn
|
|
||||||
rtl
|
|
||||||
+ lda $0e80, x
|
|
||||||
cmp #$24 : bne +
|
|
||||||
lda $a0 : cmp #$80 : bne - : lda #$24
|
|
||||||
+ jsl DrawDynamicTile ; see DrawHeartPieceGFX if problems
|
|
||||||
cmp #$03 : bne +
|
|
||||||
pha : lda $0e60, x : ora.b #$20 : sta $0E60, x : pla
|
|
||||||
+
|
|
||||||
jsl.l Sprite_DrawShadowLong
|
|
||||||
|
|
||||||
pla : rtl
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyGet:
|
|
||||||
{
|
|
||||||
lda $7ef36f ; what we wrote over
|
|
||||||
pha
|
|
||||||
lda.l ShuffleKeyDrops : bne +
|
|
||||||
- pla : rtl
|
|
||||||
+ ldy $0e80, x
|
|
||||||
lda $a0 : cmp #$87 : bne +
|
|
||||||
jsr ShouldKeyBeCountedForDungeon : bcc -
|
|
||||||
jsl CountChestKeyLong : bra -
|
|
||||||
+ sty $00
|
|
||||||
jsr KeyGetPlayer : sta !MULTIWORLD_ITEM_PLAYER_ID
|
|
||||||
lda !MULTIWORLD_ITEM_PLAYER_ID : bne .receive
|
|
||||||
phx
|
|
||||||
lda $040c : lsr : tax
|
|
||||||
lda $00 : CMP.l KeyTable, x : bne +
|
|
||||||
- JSL.l FullInventoryExternal : jsl CountChestKeyLong : plx : pla : rtl
|
|
||||||
+ cmp #$af : beq - ; universal key
|
|
||||||
cmp #$24 : beq - ; small key for this dungeon
|
|
||||||
plx
|
|
||||||
.receive
|
|
||||||
jsl.l $0791b3 ; Player_HaltDashAttackLong
|
|
||||||
jsl.l Link_ReceiveItem
|
|
||||||
pla : dec : rtl
|
|
||||||
}
|
|
||||||
|
|
||||||
; Input Y - the item type
|
|
||||||
ShouldKeyBeCountedForDungeon:
|
|
||||||
{
|
|
||||||
phx
|
|
||||||
lda $040c : lsr : tax
|
|
||||||
tya : cmp KeyTable, x : bne +
|
|
||||||
- plx : sec : rts
|
|
||||||
+ cmp #$24 : beq -
|
|
||||||
plx : clc : rts
|
|
||||||
}
|
|
||||||
|
|
||||||
BigKeyGet:
|
|
||||||
{
|
|
||||||
lda.l ShuffleKeyDrops : bne +
|
|
||||||
- stz $02e9 : ldy.b #$32 ; what we wrote over
|
|
||||||
phx : jsl Link_ReceiveItem : plx ; what we wrote over
|
|
||||||
clc : rtl
|
|
||||||
+
|
|
||||||
ldy $0e80, x
|
|
||||||
cpy #$32 : beq -
|
|
||||||
+ sec : rtl
|
|
||||||
}
|
|
||||||
|
|
||||||
KeyGetPlayer:
|
|
||||||
{
|
|
||||||
phx
|
|
||||||
ldx #$fd
|
|
||||||
- inx #3 : lda.l LootTable, x : cmp #$ff : beq ++ : cmp $a0 : bne -
|
|
||||||
++ inx : lda.l LootTable, x
|
|
||||||
plx
|
|
||||||
rts
|
|
||||||
}
|
|
||||||
|
|
||||||
LoadProperties_PreserveItemMaybe:
|
|
||||||
{
|
|
||||||
lda.l ShuffleKeyDrops : bne +
|
|
||||||
JML Sprite_LoadProperties
|
|
||||||
+ lda $0e80, x : pha
|
|
||||||
jsl Sprite_LoadProperties
|
|
||||||
pla : sta $0e80, x
|
|
||||||
rtl
|
|
||||||
}
|
|
||||||
+6
-4
@@ -118,7 +118,7 @@ RetrieveBunnyState:
|
|||||||
+ RTL
|
+ RTL
|
||||||
|
|
||||||
RainPrevention:
|
RainPrevention:
|
||||||
LDA $00 : XBA : AND #$00FF ; what we wrote over
|
LDA $00 : XBA : AND #$00FF : STA.b $0A ; what we wrote over
|
||||||
PHA
|
PHA
|
||||||
LDA $7EF3C5 : AND #$00FF : CMP #$0002 : !BGE .done ; only in rain states (0 or 1)
|
LDA $7EF3C5 : AND #$00FF : CMP #$0002 : !BGE .done ; only in rain states (0 or 1)
|
||||||
LDA.l $7EF3C6 : AND #$0004 : BNE .done ; zelda's been rescued
|
LDA.l $7EF3C6 : AND #$0004 : BNE .done ; zelda's been rescued
|
||||||
@@ -131,9 +131,11 @@ RainPrevention:
|
|||||||
LDX #$FFFE
|
LDX #$FFFE
|
||||||
- INX #2 : LDA.l RemoveRainDoorsRoom, X : CMP #$FFFF : BEQ .done
|
- INX #2 : LDA.l RemoveRainDoorsRoom, X : CMP #$FFFF : BEQ .done
|
||||||
CMP $A0 : BNE -
|
CMP $A0 : BNE -
|
||||||
LDA.l RainDoorMatch, X : CMP $00 : BNE -
|
SEP #$20 : LDA.l RainDoorMatch, X : CMP $00 : BNE .continue
|
||||||
PLA : LDA #$0008 : RTL
|
REP #$20 : PLA : SEC : RTL
|
||||||
.done PLA : RTL
|
.continue
|
||||||
|
REP #$20 : BRA -
|
||||||
|
.done PLA : CLC : RTL
|
||||||
|
|
||||||
; A should be how much dmg to do to Aga when leaving this function
|
; A should be how much dmg to do to Aga when leaving this function
|
||||||
StandardAgaDmg:
|
StandardAgaDmg:
|
||||||
|
|||||||
+18
-18
@@ -218,14 +218,14 @@ OWMirrorSpriteOnMap:
|
|||||||
OWPreserveMirrorSprite:
|
OWPreserveMirrorSprite:
|
||||||
{
|
{
|
||||||
lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla ; if OW Crossed, skip world check and continue
|
lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .vanilla ; if OW Crossed, skip world check and continue
|
||||||
lda $10 : cmp.b #$0f : beq .vanilla
|
lda.b $10 : cmp.b #$0f : beq .vanilla ; if performing mirror superbunny
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
.vanilla
|
.vanilla
|
||||||
lda.l InvertedMode : beq +
|
lda.l InvertedMode : beq +
|
||||||
lda.l $7ef3ca : beq .deleteMirror
|
lda.l CurrentWorld : beq .deleteMirror
|
||||||
rtl
|
rtl
|
||||||
+ lda.l $7ef3ca : bne .deleteMirror
|
+ lda.l CurrentWorld : bne .deleteMirror
|
||||||
rtl
|
rtl
|
||||||
|
|
||||||
.deleteMirror
|
.deleteMirror
|
||||||
@@ -242,7 +242,7 @@ OWMirrorSpriteMove:
|
|||||||
OWMirrorSpriteBonk:
|
OWMirrorSpriteBonk:
|
||||||
{
|
{
|
||||||
jsr.w OWMirrorSpriteMove
|
jsr.w OWMirrorSpriteMove
|
||||||
lda.b #$2c : jml.l SetGameModeLikeMirror ; what we wrote over
|
lda.b #$2c : jml SetGameModeLikeMirror ; what we wrote over
|
||||||
}
|
}
|
||||||
OWMirrorSpriteDelete:
|
OWMirrorSpriteDelete:
|
||||||
{
|
{
|
||||||
@@ -254,9 +254,9 @@ OWMirrorSpriteRestore:
|
|||||||
{
|
{
|
||||||
lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return
|
lda.l OWMode+1 : and.b #!FLAG_OW_CROSSED : beq .return
|
||||||
lda.l InvertedMode : beq +
|
lda.l InvertedMode : beq +
|
||||||
lda.l $7ef3ca : beq .return
|
lda.l CurrentWorld : beq .return
|
||||||
bra .restorePortal
|
bra .restorePortal
|
||||||
+ lda.l $7ef3ca : bne .return
|
+ lda.l CurrentWorld : bne .return
|
||||||
|
|
||||||
.restorePortal
|
.restorePortal
|
||||||
lda.w $1acf : and.b #$0f : sta.w $1acf
|
lda.w $1acf : and.b #$0f : sta.w $1acf
|
||||||
@@ -275,15 +275,15 @@ OWLightWorldOrCrossed:
|
|||||||
OWFluteCancel:
|
OWFluteCancel:
|
||||||
{
|
{
|
||||||
lda.l OWFlags+1 : and #$01 : bne +
|
lda.l OWFlags+1 : and #$01 : bne +
|
||||||
jsl $02e99d : rtl
|
jsl FluteMenu_LoadTransport : rtl
|
||||||
+ lda $7f5006 : cmp #$01 : beq +
|
+ lda $7f5006 : cmp #$01 : beq +
|
||||||
jsl $02e99d
|
jsl FluteMenu_LoadTransport
|
||||||
+ lda #$00 : sta $7f5006 : rtl
|
+ lda #$00 : sta $7f5006 : rtl
|
||||||
}
|
}
|
||||||
OWFluteCancel2:
|
OWFluteCancel2:
|
||||||
{
|
{
|
||||||
lda $f2 : ora $f0 : and #$c0 : bne +
|
lda $f2 : ora $f0 : and #$c0 : bne +
|
||||||
jml $0ab7bd
|
jml FluteMenu_HandleSelection_NoSelection
|
||||||
+ inc $0200
|
+ inc $0200
|
||||||
lda.l OWFlags+1 : and #$01 : beq +
|
lda.l OWFlags+1 : and #$01 : beq +
|
||||||
lda $f2 : cmp #$40 : bne +
|
lda $f2 : cmp #$40 : bne +
|
||||||
@@ -292,7 +292,7 @@ OWFluteCancel2:
|
|||||||
}
|
}
|
||||||
OWSmithAccept:
|
OWSmithAccept:
|
||||||
{
|
{
|
||||||
lda $7ef3cc : cmp #$07 : beq +
|
lda FollowerIndicator : cmp #$07 : beq +
|
||||||
cmp #$08 : beq +
|
cmp #$08 : beq +
|
||||||
clc : rtl
|
clc : rtl
|
||||||
+ sec : rtl
|
+ sec : rtl
|
||||||
@@ -395,7 +395,7 @@ OWDetectSpecialTransition:
|
|||||||
LDA.l OWEdgeDataOffset,X : STA.w $06F8
|
LDA.l OWEdgeDataOffset,X : STA.w $06F8
|
||||||
PLA : SEP #$30 : PLA ; delete 3 bytes from stack
|
PLA : SEP #$30 : PLA ; delete 3 bytes from stack
|
||||||
JSL Link_CheckForEdgeScreenTransition : BCS .return ; Link_CheckForEdgeScreenTransition
|
JSL Link_CheckForEdgeScreenTransition : BCS .return ; Link_CheckForEdgeScreenTransition
|
||||||
LDA.l $04E879,X : STA.b $00 : CMP.b #$08 : BNE .hobo
|
LDA.l Overworld_CheckForSpecialOverworldTrigger_Direction,X : STA.b $00 : CMP.b #$08 : BNE .hobo
|
||||||
LSR : STA.b $20 : STZ.b $E8 ; move Link and camera to edge
|
LSR : STA.b $20 : STZ.b $E8 ; move Link and camera to edge
|
||||||
LDA.b #$06 : STA.b $02
|
LDA.b #$06 : STA.b $02
|
||||||
STZ.w $0418
|
STZ.w $0418
|
||||||
@@ -437,7 +437,7 @@ OWEdgeTransition:
|
|||||||
SEP #$30
|
SEP #$30
|
||||||
RTL
|
RTL
|
||||||
.normal
|
.normal
|
||||||
LDA.l $02A4E3,X : ORA.l $7EF3CA ; what we wrote over
|
LDA.l Overworld_ActualScreenID,X : ORA.l CurrentWorld ; what we wrote over
|
||||||
RTL
|
RTL
|
||||||
}
|
}
|
||||||
OWSpecialExit:
|
OWSpecialExit:
|
||||||
@@ -628,8 +628,8 @@ OWLoadSpecialArea:
|
|||||||
}
|
}
|
||||||
OWWorldUpdate: ; x = owid of destination screen
|
OWWorldUpdate: ; x = owid of destination screen
|
||||||
{
|
{
|
||||||
lda.l OWTileWorldAssoc,x : cmp.l $7ef3ca : beq .return
|
lda.l OWTileWorldAssoc,x : cmp.l CurrentWorld : beq .return
|
||||||
sta.l $7ef3ca ; change world
|
sta.l CurrentWorld ; change world
|
||||||
|
|
||||||
; moving mirror portal off screen when in DW
|
; moving mirror portal off screen when in DW
|
||||||
cmp #0 : beq + : lda #1
|
cmp #0 : beq + : lda #1
|
||||||
@@ -641,10 +641,10 @@ OWWorldUpdate: ; x = owid of destination screen
|
|||||||
lda #$38 : sta $012f ; play sfx - #$3b is an alternative
|
lda #$38 : sta $012f ; play sfx - #$3b is an alternative
|
||||||
|
|
||||||
; toggle bunny mode
|
; toggle bunny mode
|
||||||
lda $7ef357 : bne .nobunny
|
lda MoonPearlEquipment : bne .nobunny
|
||||||
lda.l InvertedMode : bne .inverted
|
lda.l InvertedMode : bne .inverted
|
||||||
lda $7ef3ca : and.b #$40 : bra +
|
lda CurrentWorld : and.b #$40 : bra +
|
||||||
.inverted lda $7ef3ca : and.b #$40 : eor #$40
|
.inverted lda CurrentWorld : and.b #$40 : eor #$40
|
||||||
+ cmp #$40 : bne .nobunny
|
+ cmp #$40 : bne .nobunny
|
||||||
; turn into bunny
|
; turn into bunny
|
||||||
lda $5d : cmp #$04 : beq + ; if swimming, continue
|
lda $5d : cmp #$04 : beq + ; if swimming, continue
|
||||||
@@ -681,7 +681,7 @@ OWEndScrollTransition:
|
|||||||
CMP.w $06FC
|
CMP.w $06FC
|
||||||
RTL
|
RTL
|
||||||
.normal
|
.normal
|
||||||
CMP.l $02C176,X ; what we wrote over
|
CMP.l Overworld_FinalizeEntryOntoScreen_Data,X ; what we wrote over
|
||||||
RTL
|
RTL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
+16
-10
@@ -30,9 +30,19 @@
|
|||||||
1: 2
|
1: 2
|
||||||
2: 2
|
2: 2
|
||||||
3: 4
|
3: 4
|
||||||
keydropshuffle:
|
dropshuffle:
|
||||||
on: 1
|
on: 1
|
||||||
off: 1
|
off: 1
|
||||||
|
pottery:
|
||||||
|
none: 8
|
||||||
|
keys: 1
|
||||||
|
cave: 1
|
||||||
|
dungeon: 1
|
||||||
|
lottery: 1
|
||||||
|
cavekeys: 1
|
||||||
|
reduced: 1
|
||||||
|
clustered: 1
|
||||||
|
nonempty: 1
|
||||||
shopsanity:
|
shopsanity:
|
||||||
on: 1
|
on: 1
|
||||||
off: 1
|
off: 1
|
||||||
@@ -81,17 +91,17 @@
|
|||||||
dungeons: 1
|
dungeons: 1
|
||||||
pedestal: 2
|
pedestal: 2
|
||||||
triforce-hunt: 2
|
triforce-hunt: 2
|
||||||
trinity: 1
|
trinity: 2
|
||||||
triforce_goal_min: 10
|
triforce_goal_min: 10
|
||||||
triforce_goal_max: 30
|
triforce_goal_max: 30
|
||||||
triforce_pool_min: 20
|
triforce_pool_min: 20
|
||||||
triforce_pool_max: 40
|
triforce_pool_max: 40
|
||||||
triforce_min_difference: 10
|
triforce_min_difference: 10
|
||||||
algorithm:
|
algorithm:
|
||||||
|
balanced: 12
|
||||||
|
vanilla_fill: 1
|
||||||
major_only: 1
|
major_only: 1
|
||||||
dungeon_only: 1
|
dungeon_only: 1
|
||||||
vanilla_fill: 1
|
|
||||||
balanced: 10
|
|
||||||
district: 1
|
district: 1
|
||||||
restrict_boss_items:
|
restrict_boss_items:
|
||||||
none: 1
|
none: 1
|
||||||
@@ -175,13 +185,9 @@
|
|||||||
on: 1
|
on: 1
|
||||||
off: 0
|
off: 0
|
||||||
startinventory:
|
startinventory:
|
||||||
"Pegasus Boots":
|
Pegasus Boots: on
|
||||||
on: 0
|
|
||||||
off: 1
|
|
||||||
rom:
|
rom:
|
||||||
quickswap:
|
quickswap: on
|
||||||
on: 1
|
|
||||||
off: 0
|
|
||||||
heartcolor:
|
heartcolor:
|
||||||
red: 1
|
red: 1
|
||||||
blue: 1
|
blue: 1
|
||||||
|
|||||||
+15
-5
@@ -1,5 +1,4 @@
|
|||||||
description: A test suite for testing various combinations
|
description: A test suite for testing various combinations
|
||||||
# Not yet in this branch
|
|
||||||
algorithm:
|
algorithm:
|
||||||
major_only: 1
|
major_only: 1
|
||||||
dungeon_only: 1
|
dungeon_only: 1
|
||||||
@@ -13,10 +12,19 @@ door_shuffle:
|
|||||||
intensity:
|
intensity:
|
||||||
1: 1
|
1: 1
|
||||||
2: 1
|
2: 1
|
||||||
3: 2 # intensity 3 usuall yield more errors
|
3: 2 # intensity 3 usually yield more errors
|
||||||
keydropshuffle:
|
dropshuffle:
|
||||||
on: 1
|
on: 1
|
||||||
off: 1
|
off: 1
|
||||||
|
pottery:
|
||||||
|
none: 10 # fewer locations
|
||||||
|
keys: 1
|
||||||
|
cave: 1
|
||||||
|
cavekeys: 1
|
||||||
|
dungeon: 1
|
||||||
|
reduced: 1
|
||||||
|
clustered: 1
|
||||||
|
lottery: 1
|
||||||
shopsanity:
|
shopsanity:
|
||||||
on: 1
|
on: 1
|
||||||
off: 1
|
off: 1
|
||||||
@@ -46,9 +54,10 @@ retro:
|
|||||||
goals:
|
goals:
|
||||||
ganon: 1
|
ganon: 1
|
||||||
fast_ganon: 1
|
fast_ganon: 1
|
||||||
dungeons: 2 # this yields more errors so is preferred
|
dungeons: 3 # this yields more errors so is preferred
|
||||||
pedestal: 1
|
pedestal: 1
|
||||||
triforce-hunt: 1
|
triforce-hunt: 1
|
||||||
|
trinity: 1
|
||||||
triforce_goal_min: 20
|
triforce_goal_min: 20
|
||||||
triforce_goal_max: 30
|
triforce_goal_max: 30
|
||||||
triforce_pool_min: 30
|
triforce_pool_min: 30
|
||||||
@@ -83,7 +92,7 @@ accessibility:
|
|||||||
none: 0 # i'm not really interested in this yet
|
none: 0 # i'm not really interested in this yet
|
||||||
restrict_boss_items:
|
restrict_boss_items:
|
||||||
none: 1
|
none: 1
|
||||||
mapcompass: 1
|
# mapcompass: 1 has confirmed issues
|
||||||
dungeon: 1
|
dungeon: 1
|
||||||
tower_open:
|
tower_open:
|
||||||
"0": 1
|
"0": 1
|
||||||
@@ -108,6 +117,7 @@ ganon_open:
|
|||||||
boss_shuffle:
|
boss_shuffle:
|
||||||
none: 1
|
none: 1
|
||||||
simple: 1
|
simple: 1
|
||||||
|
unique: 1
|
||||||
full: 1
|
full: 1
|
||||||
random: 1
|
random: 1
|
||||||
enemy_shuffle: # shouldn't affect generation
|
enemy_shuffle: # shouldn't affect generation
|
||||||
|
|||||||
@@ -13,6 +13,11 @@
|
|||||||
"suppress_spoiler": {
|
"suppress_spoiler": {
|
||||||
"action": "store_true"
|
"action": "store_true"
|
||||||
},
|
},
|
||||||
|
"mystery": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool",
|
||||||
|
"help": "suppress"
|
||||||
|
},
|
||||||
"logic": {
|
"logic": {
|
||||||
"choices": [
|
"choices": [
|
||||||
"noglitches",
|
"noglitches",
|
||||||
@@ -43,8 +48,8 @@
|
|||||||
"pedestal",
|
"pedestal",
|
||||||
"dungeons",
|
"dungeons",
|
||||||
"triforcehunt",
|
"triforcehunt",
|
||||||
"crystals",
|
"trinity",
|
||||||
"trinity"
|
"crystals"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"difficulty": {
|
"difficulty": {
|
||||||
@@ -69,6 +74,31 @@
|
|||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
},
|
},
|
||||||
|
"dropshuffle" : {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"pottery" : {
|
||||||
|
"choices" : [
|
||||||
|
"none",
|
||||||
|
"keys",
|
||||||
|
"dungeon",
|
||||||
|
"cave",
|
||||||
|
"cavekeys",
|
||||||
|
"reduced",
|
||||||
|
"clustered",
|
||||||
|
"nonempty",
|
||||||
|
"lottery"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"colorizepots" : {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"shufflepots": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
"mixed_travel" : {
|
"mixed_travel" : {
|
||||||
"choices": [
|
"choices": [
|
||||||
"prevent",
|
"prevent",
|
||||||
@@ -102,7 +132,6 @@
|
|||||||
"algorithm": {
|
"algorithm": {
|
||||||
"choices": [
|
"choices": [
|
||||||
"balanced",
|
"balanced",
|
||||||
"equitable",
|
|
||||||
"vanilla_fill",
|
"vanilla_fill",
|
||||||
"major_only",
|
"major_only",
|
||||||
"dungeon_only",
|
"dungeon_only",
|
||||||
@@ -259,6 +288,14 @@
|
|||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
},
|
},
|
||||||
|
"msu_resume": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
|
"collection_rate": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
"mapshuffle": {
|
"mapshuffle": {
|
||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
@@ -368,6 +405,10 @@
|
|||||||
"dest": "create_rom",
|
"dest": "create_rom",
|
||||||
"help": "suppress"
|
"help": "suppress"
|
||||||
},
|
},
|
||||||
|
"suppress_meta": {
|
||||||
|
"action": "store_true",
|
||||||
|
"type": "bool"
|
||||||
|
},
|
||||||
"shuffleganon": {
|
"shuffleganon": {
|
||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
@@ -407,6 +448,9 @@
|
|||||||
"jsonout": {
|
"jsonout": {
|
||||||
"action": "store_true"
|
"action": "store_true"
|
||||||
},
|
},
|
||||||
|
"bps": {
|
||||||
|
"action": "store_true"
|
||||||
|
},
|
||||||
"enemizercli": {
|
"enemizercli": {
|
||||||
"setting": "enemizercli"
|
"setting": "enemizercli"
|
||||||
},
|
},
|
||||||
@@ -414,6 +458,7 @@
|
|||||||
"choices": [
|
"choices": [
|
||||||
"none",
|
"none",
|
||||||
"simple",
|
"simple",
|
||||||
|
"unique",
|
||||||
"full",
|
"full",
|
||||||
"random"
|
"random"
|
||||||
]
|
]
|
||||||
@@ -442,10 +487,6 @@
|
|||||||
"random"
|
"random"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"shufflepots": {
|
|
||||||
"action": "store_true",
|
|
||||||
"type": "bool"
|
|
||||||
},
|
|
||||||
"remote_items": {
|
"remote_items": {
|
||||||
"action": "store_true",
|
"action": "store_true",
|
||||||
"type": "bool"
|
"type": "bool"
|
||||||
|
|||||||
@@ -39,6 +39,7 @@
|
|||||||
"cannot.reach.required": "Not all required items reachable. Something went terribly wrong here.",
|
"cannot.reach.required": "Not all required items reachable. Something went terribly wrong here.",
|
||||||
"patching.rom": "Patching ROM",
|
"patching.rom": "Patching ROM",
|
||||||
"patching.spoiler": "Creating Spoiler",
|
"patching.spoiler": "Creating Spoiler",
|
||||||
|
"create.meta": "Creating Meta Info",
|
||||||
"calc.playthrough": "Calculating Playthrough",
|
"calc.playthrough": "Calculating Playthrough",
|
||||||
"made.rom": "Patched ROM: %s",
|
"made.rom": "Patched ROM: %s",
|
||||||
"made.playthrough": "Printed Playthrough: %s",
|
"made.playthrough": "Printed Playthrough: %s",
|
||||||
@@ -60,6 +61,7 @@
|
|||||||
"help": {
|
"help": {
|
||||||
"lang": [ "App Language, if available, defaults to English" ],
|
"lang": [ "App Language, if available, defaults to English" ],
|
||||||
"create_spoiler": [ "Output a Spoiler File" ],
|
"create_spoiler": [ "Output a Spoiler File" ],
|
||||||
|
"bps": [ "Output BPS patches instead of ROMs"],
|
||||||
"logic": [
|
"logic": [
|
||||||
"Select Enforcement of Item Requirements. (default: %(default)s)",
|
"Select Enforcement of Item Requirements. (default: %(default)s)",
|
||||||
"No Glitches: No Glitch knowledge required.",
|
"No Glitches: No Glitch knowledge required.",
|
||||||
@@ -157,8 +159,6 @@
|
|||||||
"balanced: vt26 derivative that aims to strike a balance between",
|
"balanced: vt26 derivative that aims to strike a balance between",
|
||||||
" the overworld heavy vt25 and the dungeon heavy vt26",
|
" the overworld heavy vt25 and the dungeon heavy vt26",
|
||||||
" algorithm.",
|
" algorithm.",
|
||||||
"equitable: does not place dungeon items first allowing new potential",
|
|
||||||
" but mixed with the normal advancement pool",
|
|
||||||
"restricted placements: these consider all major items to be special and attempts",
|
"restricted placements: these consider all major items to be special and attempts",
|
||||||
"to place items from fixed to semi-random locations. For purposes of these shuffles, all",
|
"to place items from fixed to semi-random locations. For purposes of these shuffles, all",
|
||||||
"Y items, A items, swords (unless vanilla swords), mails, shields, heart containers and",
|
"Y items, A items, swords (unless vanilla swords), mails, shields, heart containers and",
|
||||||
@@ -295,7 +295,21 @@
|
|||||||
"keyshuffle": [ "Small Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
"keyshuffle": [ "Small Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||||
"bigkeyshuffle": [ "Big Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
"bigkeyshuffle": [ "Big Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
|
||||||
"shopsanity": ["Shop contents are shuffle in the main item pool and other items can take their place. (default: %(default)s)"],
|
"shopsanity": ["Shop contents are shuffle in the main item pool and other items can take their place. (default: %(default)s)"],
|
||||||
"keydropshuffle": [ "Key Drops (Pots and Enemies) are shuffled and other items can take their place (default: %(default)s)" ],
|
"dropshuffle": [ "Keys dropped by enemies are shuffled and other items can take their place. (default: %(default)s)"],
|
||||||
|
"pottery": [ "Controls how items under pots are shuffled and if other items can take their place:",
|
||||||
|
"None: No pots are changed",
|
||||||
|
"Keys: Key pots are included in the location pool and other items can take their place",
|
||||||
|
"Cave: Only pots in houses and caves are included in the location pool",
|
||||||
|
"CaveKeys: Both pots in houses and caves and keys pots are included in the location pool",
|
||||||
|
"Reduced: Same as KeyCaves + 25%% of Pots in dungeons (dynamic mode)",
|
||||||
|
"Clustered: Same as KeyCaves + 50%% of Pots in dungeons, chosen by logical group (dynamic mode)",
|
||||||
|
"NonEmpty: All pots that are not originally empty are included in the location pool",
|
||||||
|
"Dungeon: Only pots in dungeons are included in the location pool",
|
||||||
|
"Lottery: All pots are part of the location pool"
|
||||||
|
],
|
||||||
|
"colorizepots": ["All pots chosen to be in location pool by the pottery setting are different.",
|
||||||
|
"Forced on in dynamic modes. Forced off in lottery"],
|
||||||
|
"shufflepots": [ "Pots and switches are shuffled on the supertile (legacy potshuffle) (default: %(default)s)"],
|
||||||
"mixed_travel": [
|
"mixed_travel": [
|
||||||
"How to handle potential traversal between dungeon in Crossed door shuffle",
|
"How to handle potential traversal between dungeon in Crossed door shuffle",
|
||||||
"Prevent: Rails are placed to prevent bombs jump and hovering from changing dungeon except with glitched logic settings",
|
"Prevent: Rails are placed to prevent bombs jump and hovering from changing dungeon except with glitched logic settings",
|
||||||
@@ -356,6 +370,8 @@
|
|||||||
],
|
],
|
||||||
"reduce_flashing": [ "Reduce some in-game flashing (default: %(default)s)" ],
|
"reduce_flashing": [ "Reduce some in-game flashing (default: %(default)s)" ],
|
||||||
"shuffle_sfx": [ "Shuffle sounds effects (default: %(default)s)" ],
|
"shuffle_sfx": [ "Shuffle sounds effects (default: %(default)s)" ],
|
||||||
|
"msu_resume": [ "Enable MSU Resume (default: %(default)s)" ],
|
||||||
|
"collection_rate": [ "Display collection rate (default: %(default)s)" ],
|
||||||
"create_rom": [ "Create an output rom file. (default: %(default)s)" ],
|
"create_rom": [ "Create an output rom file. (default: %(default)s)" ],
|
||||||
"gui": [ "Launch the GUI. (default: %(default)s)" ],
|
"gui": [ "Launch the GUI. (default: %(default)s)" ],
|
||||||
"jsonout": [
|
"jsonout": [
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"checkboxes": {
|
"checkboxes": {
|
||||||
"nobgm": { "type": "checkbox" },
|
"nobgm": { "type": "checkbox" },
|
||||||
|
"msu_resume": { "type": "checkbox" },
|
||||||
"quickswap": { "type": "checkbox" },
|
"quickswap": { "type": "checkbox" },
|
||||||
"reduce_flashing": {"type": "checkbox" },
|
"reduce_flashing": {"type": "checkbox" },
|
||||||
"shuffle_sfx": {"type": "checkbox" }
|
"shuffle_sfx": {"type": "checkbox" }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"adjust.quickswap": "L/R Quickswapping",
|
"adjust.quickswap": "L/R Quickswapping",
|
||||||
"adjust.reduce_flashing": "Reduce Flashing",
|
"adjust.reduce_flashing": "Reduce Flashing",
|
||||||
"adjust.shuffle_sfx": "Shuffle Sound Effects",
|
"adjust.shuffle_sfx": "Shuffle Sound Effects",
|
||||||
|
"adjust.msu_resume": "MSU Resume",
|
||||||
|
|
||||||
"adjust.heartcolor": "Heart Color",
|
"adjust.heartcolor": "Heart Color",
|
||||||
"adjust.heartcolor.red": "Red",
|
"adjust.heartcolor.red": "Red",
|
||||||
@@ -53,7 +54,20 @@
|
|||||||
"randomizer.dungeon.compassshuffle": "Compasses",
|
"randomizer.dungeon.compassshuffle": "Compasses",
|
||||||
"randomizer.dungeon.smallkeyshuffle": "Small Keys",
|
"randomizer.dungeon.smallkeyshuffle": "Small Keys",
|
||||||
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
|
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
|
||||||
"randomizer.dungeon.keydropshuffle": "Key Drops (pots and enemies)",
|
"randomizer.dungeon.keydropshuffle": "Key Drop Shuffle (Legacy)",
|
||||||
|
"randomizer.dungeon.dropshuffle": "Shuffle Enemy Key Drops",
|
||||||
|
"randomizer.dungeon.potshuffle": "Pot Shuffle (Legacy)",
|
||||||
|
"randomizer.dungeon.pottery": "Pottery",
|
||||||
|
"randomizer.dungeon.pottery.none": "None",
|
||||||
|
"randomizer.dungeon.pottery.keys": "Key Pots",
|
||||||
|
"randomizer.dungeon.pottery.cave": "Cave Pots",
|
||||||
|
"randomizer.dungeon.pottery.cavekeys": "Cave+Key Pots",
|
||||||
|
"randomizer.dungeon.pottery.reduced": "Reduced Dungeon Pots (Dynamic)",
|
||||||
|
"randomizer.dungeon.pottery.clustered": "Clustered Dungeon Pots (Dynamic)",
|
||||||
|
"randomizer.dungeon.pottery.nonempty": "Excludes Empty Pots",
|
||||||
|
"randomizer.dungeon.pottery.dungeon": "Dungeon Pots",
|
||||||
|
"randomizer.dungeon.pottery.lottery": "Lottery (All Pots and Large Blocks)",
|
||||||
|
"randomizer.dungeon.colorizepots": "Colorize Randomized Pots",
|
||||||
|
|
||||||
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
|
"randomizer.dungeon.dungeondoorshuffle": "Dungeon Door Shuffle",
|
||||||
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
|
"randomizer.dungeon.dungeondoorshuffle.vanilla": "Vanilla",
|
||||||
@@ -66,7 +80,6 @@
|
|||||||
"randomizer.dungeon.dungeonintensity.3": "3: Dungeon Lobbies",
|
"randomizer.dungeon.dungeonintensity.3": "3: Dungeon Lobbies",
|
||||||
"randomizer.dungeon.dungeonintensity.random": "Random",
|
"randomizer.dungeon.dungeonintensity.random": "Random",
|
||||||
|
|
||||||
"randomizer.dungeon.potshuffle": "Pot Shuffle",
|
|
||||||
"randomizer.dungeon.experimental": "Enable Experimental Features",
|
"randomizer.dungeon.experimental": "Enable Experimental Features",
|
||||||
|
|
||||||
"randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters",
|
"randomizer.dungeon.dungeon_counters": "Dungeon Chest Counters",
|
||||||
@@ -93,6 +106,7 @@
|
|||||||
"randomizer.enemizer.bossshuffle": "Boss Shuffle",
|
"randomizer.enemizer.bossshuffle": "Boss Shuffle",
|
||||||
"randomizer.enemizer.bossshuffle.none": "None",
|
"randomizer.enemizer.bossshuffle.none": "None",
|
||||||
"randomizer.enemizer.bossshuffle.simple": "Simple",
|
"randomizer.enemizer.bossshuffle.simple": "Simple",
|
||||||
|
"randomizer.enemizer.bossshuffle.unique": "Unique",
|
||||||
"randomizer.enemizer.bossshuffle.full": "Full",
|
"randomizer.enemizer.bossshuffle.full": "Full",
|
||||||
"randomizer.enemizer.bossshuffle.random": "Random",
|
"randomizer.enemizer.bossshuffle.random": "Random",
|
||||||
|
|
||||||
@@ -170,6 +184,8 @@
|
|||||||
"randomizer.gameoptions.quickswap": "L/R Quickswapping",
|
"randomizer.gameoptions.quickswap": "L/R Quickswapping",
|
||||||
"randomizer.gameoptions.reduce_flashing": "Reduce Flashing",
|
"randomizer.gameoptions.reduce_flashing": "Reduce Flashing",
|
||||||
"randomizer.gameoptions.shuffle_sfx": "Shuffle Sound Effects",
|
"randomizer.gameoptions.shuffle_sfx": "Shuffle Sound Effects",
|
||||||
|
"randomizer.gameoptions.msu_resume": "MSU Resume",
|
||||||
|
"randomizer.gameoptions.collection_rate": "Display Collection Rate",
|
||||||
|
|
||||||
"randomizer.gameoptions.heartcolor": "Heart Color",
|
"randomizer.gameoptions.heartcolor": "Heart Color",
|
||||||
"randomizer.gameoptions.heartcolor.red": "Red",
|
"randomizer.gameoptions.heartcolor.red": "Red",
|
||||||
@@ -207,6 +223,7 @@
|
|||||||
"randomizer.gameoptions.sprite.unchanged": "(unchanged)",
|
"randomizer.gameoptions.sprite.unchanged": "(unchanged)",
|
||||||
|
|
||||||
|
|
||||||
|
"randomizer.generation.bps": "Create BPS Patches",
|
||||||
"randomizer.generation.createspoiler": "Create Spoiler Log",
|
"randomizer.generation.createspoiler": "Create Spoiler Log",
|
||||||
"randomizer.generation.createrom": "Create Patched ROM",
|
"randomizer.generation.createrom": "Create Patched ROM",
|
||||||
"randomizer.generation.calcplaythrough": "Calculate Playthrough",
|
"randomizer.generation.calcplaythrough": "Calculate Playthrough",
|
||||||
@@ -315,7 +332,6 @@
|
|||||||
|
|
||||||
"randomizer.item.sortingalgo": "Item Sorting",
|
"randomizer.item.sortingalgo": "Item Sorting",
|
||||||
"randomizer.item.sortingalgo.balanced": "Balanced",
|
"randomizer.item.sortingalgo.balanced": "Balanced",
|
||||||
"randomizer.item.sortingalgo.equitable": "Equitable",
|
|
||||||
"randomizer.item.sortingalgo.vanilla_fill": "Vanilla Fill",
|
"randomizer.item.sortingalgo.vanilla_fill": "Vanilla Fill",
|
||||||
"randomizer.item.sortingalgo.major_only": "Major Location Restriction",
|
"randomizer.item.sortingalgo.major_only": "Major Location Restriction",
|
||||||
"randomizer.item.sortingalgo.dungeon_only": "Dungeon Restriction",
|
"randomizer.item.sortingalgo.dungeon_only": "Dungeon Restriction",
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
"mapshuffle": { "type": "checkbox" },
|
"mapshuffle": { "type": "checkbox" },
|
||||||
"compassshuffle": { "type": "checkbox" },
|
"compassshuffle": { "type": "checkbox" },
|
||||||
"smallkeyshuffle": { "type": "checkbox" },
|
"smallkeyshuffle": { "type": "checkbox" },
|
||||||
"bigkeyshuffle": { "type": "checkbox" },
|
"bigkeyshuffle": { "type": "checkbox" }
|
||||||
"keydropshuffle": { "type": "checkbox" }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,27 @@
|
|||||||
"width": 45
|
"width": 45
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"keydropshuffle": { "type": "checkbox" },
|
||||||
|
"pottery": {
|
||||||
|
"type": "selectbox",
|
||||||
|
"default": "none",
|
||||||
|
"options": [
|
||||||
|
"none",
|
||||||
|
"keys",
|
||||||
|
"cave",
|
||||||
|
"cavekeys",
|
||||||
|
"reduced",
|
||||||
|
"clustered",
|
||||||
|
"nonempty",
|
||||||
|
"dungeon",
|
||||||
|
"lottery"
|
||||||
|
],
|
||||||
|
"config": {
|
||||||
|
"width": 35
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"colorizepots": { "type": "checkbox" },
|
||||||
|
"dropshuffle": { "type": "checkbox" },
|
||||||
"potshuffle": { "type": "checkbox" },
|
"potshuffle": { "type": "checkbox" },
|
||||||
"experimental": { "type": "checkbox" },
|
"experimental": { "type": "checkbox" },
|
||||||
"dungeon_counters": {
|
"dungeon_counters": {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"none",
|
"none",
|
||||||
"simple",
|
"simple",
|
||||||
"full",
|
"full",
|
||||||
|
"unique",
|
||||||
"random"
|
"random"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"checkboxes": {
|
"checkboxes": {
|
||||||
"nobgm": { "type": "checkbox" },
|
"nobgm": { "type": "checkbox" },
|
||||||
|
"msu_resume": { "type": "checkbox" },
|
||||||
|
"collection_rate": {"type": "checkbox"},
|
||||||
"quickswap": { "type": "checkbox" },
|
"quickswap": { "type": "checkbox" },
|
||||||
"reduce_flashing": { "type": "checkbox" },
|
"reduce_flashing": { "type": "checkbox" },
|
||||||
"shuffle_sfx": { "type": "checkbox" }
|
"shuffle_sfx": { "type": "checkbox" }
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"checkboxes": {
|
"checkboxes": {
|
||||||
"createspoiler": { "type": "checkbox" },
|
|
||||||
"createrom": { "type": "checkbox" },
|
"createrom": { "type": "checkbox" },
|
||||||
|
"bps": { "type": "checkbox" },
|
||||||
|
"createspoiler": { "type": "checkbox" },
|
||||||
"calcplaythrough": { "type": "checkbox" },
|
"calcplaythrough": { "type": "checkbox" },
|
||||||
"usestartinventory": { "type": "checkbox" },
|
"usestartinventory": { "type": "checkbox" },
|
||||||
"usecustompool": { "type": "checkbox" }
|
"usecustompool": { "type": "checkbox" }
|
||||||
|
|||||||
@@ -118,7 +118,6 @@
|
|||||||
"default": "balanced",
|
"default": "balanced",
|
||||||
"options": [
|
"options": [
|
||||||
"balanced",
|
"balanced",
|
||||||
"equitable",
|
|
||||||
"vanilla_fill",
|
"vanilla_fill",
|
||||||
"major_only",
|
"major_only",
|
||||||
"dungeon_only",
|
"dungeon_only",
|
||||||
|
|||||||
+7
-10
@@ -1,5 +1,5 @@
|
|||||||
import random
|
import random
|
||||||
from Utils import int16_as_bytes
|
from Utils import int16_as_bytes, snes_to_pc
|
||||||
|
|
||||||
|
|
||||||
class SFX(object):
|
class SFX(object):
|
||||||
@@ -153,16 +153,13 @@ def shuffle_sfx_data():
|
|||||||
|
|
||||||
|
|
||||||
sfx_table = {
|
sfx_table = {
|
||||||
2: 0x1a8c29,
|
2: 0x1A8BD0,
|
||||||
3: 0x1A8D25
|
3: 0x1A8CCC
|
||||||
}
|
}
|
||||||
|
|
||||||
# 0x1a8c29
|
|
||||||
# d8059
|
|
||||||
|
|
||||||
sfx_accompaniment_table = {
|
sfx_accompaniment_table = {
|
||||||
2: 0x1A8CA7,
|
2: 0x1A8C4E,
|
||||||
3: 0x1A8DA3
|
3: 0x1A8D4A
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -171,9 +168,9 @@ def randomize_sfx(rom):
|
|||||||
|
|
||||||
for shuffled_sfx in sfx_map.values():
|
for shuffled_sfx in sfx_map.values():
|
||||||
for sfx in shuffled_sfx.values():
|
for sfx in shuffled_sfx.values():
|
||||||
base_address = sfx_table[sfx.target_set]
|
base_address = snes_to_pc(sfx_table[sfx.target_set])
|
||||||
rom.write_bytes(base_address + (sfx.target_id * 2) - 2, int16_as_bytes(sfx.addr))
|
rom.write_bytes(base_address + (sfx.target_id * 2) - 2, int16_as_bytes(sfx.addr))
|
||||||
ac_base = sfx_accompaniment_table[sfx.target_set]
|
ac_base = snes_to_pc(sfx_accompaniment_table[sfx.target_set])
|
||||||
last = sfx.target_id
|
last = sfx.target_id
|
||||||
if sfx.target_chain:
|
if sfx.target_chain:
|
||||||
for chained in sfx.target_chain:
|
for chained in sfx.target_chain:
|
||||||
|
|||||||
@@ -101,9 +101,12 @@ SETTINGSTOPROCESS = {
|
|||||||
"compassshuffle": "compassshuffle",
|
"compassshuffle": "compassshuffle",
|
||||||
"smallkeyshuffle": "keyshuffle",
|
"smallkeyshuffle": "keyshuffle",
|
||||||
"bigkeyshuffle": "bigkeyshuffle",
|
"bigkeyshuffle": "bigkeyshuffle",
|
||||||
"keydropshuffle": "keydropshuffle",
|
|
||||||
"dungeondoorshuffle": "door_shuffle",
|
"dungeondoorshuffle": "door_shuffle",
|
||||||
"dungeonintensity": "intensity",
|
"dungeonintensity": "intensity",
|
||||||
|
"keydropshuffle": "keydropshuffle",
|
||||||
|
"dropshuffle": "dropshuffle",
|
||||||
|
"pottery": "pottery",
|
||||||
|
"colorizepots": "colorizepots",
|
||||||
"potshuffle": "shufflepots",
|
"potshuffle": "shufflepots",
|
||||||
"experimental": "experimental",
|
"experimental": "experimental",
|
||||||
"dungeon_counters": "dungeon_counters",
|
"dungeon_counters": "dungeon_counters",
|
||||||
@@ -119,9 +122,12 @@ SETTINGSTOPROCESS = {
|
|||||||
"owpalettes": "ow_palettes",
|
"owpalettes": "ow_palettes",
|
||||||
"uwpalettes": "uw_palettes",
|
"uwpalettes": "uw_palettes",
|
||||||
"reduce_flashing": "reduce_flashing",
|
"reduce_flashing": "reduce_flashing",
|
||||||
"shuffle_sfx": "shuffle_sfx"
|
"shuffle_sfx": "shuffle_sfx",
|
||||||
|
'msu_resume': 'msu_resume',
|
||||||
|
'collection_rate': 'collection_rate',
|
||||||
},
|
},
|
||||||
"generation": {
|
"generation": {
|
||||||
|
"bps": "bps",
|
||||||
"createspoiler": "create_spoiler",
|
"createspoiler": "create_spoiler",
|
||||||
"createrom": "create_rom",
|
"createrom": "create_rom",
|
||||||
"calcplaythrough": "calc_playthrough",
|
"calcplaythrough": "calc_playthrough",
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
from RoomData import DoorKind, Position
|
||||||
|
from source.dungeon.RoomObject import RoomObject, DoorObject
|
||||||
|
|
||||||
|
|
||||||
|
class Room:
|
||||||
|
|
||||||
|
def __init__(self, layout, layer1, layer2, doors):
|
||||||
|
self.layout = layout
|
||||||
|
self.layer1 = layer1
|
||||||
|
self.layer2 = layer2
|
||||||
|
self.doors = doors
|
||||||
|
|
||||||
|
def write_to_rom(self, address, rom):
|
||||||
|
rom.write_bytes(address, self.layout)
|
||||||
|
address += 2
|
||||||
|
for obj in self.layer1:
|
||||||
|
rom.write_bytes(address, obj.data)
|
||||||
|
address += 3
|
||||||
|
rom.write_bytes(address, [0xFF, 0xFF])
|
||||||
|
address += 2
|
||||||
|
for obj in self.layer2:
|
||||||
|
rom.write_bytes(address, obj.data)
|
||||||
|
address += 3
|
||||||
|
rom.write_bytes(address, [0xFF, 0xFF, 0xF0, 0xFF])
|
||||||
|
address += 4
|
||||||
|
for door in self.doors:
|
||||||
|
rom.write_bytes(address, door.get_bytes())
|
||||||
|
address += 2
|
||||||
|
rom.write_bytes(address, [0xFF, 0xFF])
|
||||||
|
return address + 2 # where the data ended
|
||||||
|
|
||||||
|
|
||||||
|
Room0127 = Room([0xE1, 0x00],
|
||||||
|
[RoomObject(0x0AB600, [0xFE, 0x89, 0x00]),
|
||||||
|
RoomObject(0x0AB603, [0xA2, 0xA1, 0x61]),
|
||||||
|
RoomObject(0x0AB606, [0xFE, 0x8E, 0x81]),
|
||||||
|
RoomObject(0x0AB609, [0xFF, 0x49, 0x02]),
|
||||||
|
RoomObject(0x0AB60C, [0xD2, 0xA1, 0x62]),
|
||||||
|
RoomObject(0x0AB60F, [0xFF, 0x4E, 0x83]),
|
||||||
|
RoomObject(0x0AB612, [0x20, 0xB3, 0xDD]),
|
||||||
|
RoomObject(0x0AB615, [0x50, 0xB3, 0xDD]),
|
||||||
|
RoomObject(0x0AB618, [0x33, 0xCB, 0xFA]),
|
||||||
|
RoomObject(0x0AB61B, [0x3B, 0xCB, 0xFA]),
|
||||||
|
RoomObject(0x0AB61E, [0x43, 0xCB, 0xFA]),
|
||||||
|
RoomObject(0x0AB621, [0x4B, 0xCB, 0xFA]),
|
||||||
|
RoomObject(0x0AB624, [0xBF, 0x94, 0xF9]),
|
||||||
|
RoomObject(0x0AB627, [0xB3, 0xB3, 0xFA]),
|
||||||
|
RoomObject(0x0AB62A, [0xCB, 0xB3, 0xFA]),
|
||||||
|
RoomObject(0x0AB62D, [0xAD, 0xC8, 0xDF]),
|
||||||
|
RoomObject(0x0AB630, [0xC4, 0xC8, 0xDF]),
|
||||||
|
RoomObject(0x0AB633, [0xB3, 0xE3, 0xFA]),
|
||||||
|
RoomObject(0x0AB636, [0xCB, 0xE3, 0xFA]),
|
||||||
|
RoomObject(0x0AB639, [0x81, 0x93, 0xC0]),
|
||||||
|
RoomObject(0x0AB63C, [0x81, 0xD2, 0xC0]),
|
||||||
|
RoomObject(0x0AB63F, [0xE1, 0x93, 0xC0]),
|
||||||
|
RoomObject(0x0AB642, [0xE1, 0xD2, 0xC0])],
|
||||||
|
[], [DoorObject(Position.SouthW, DoorKind.CaveEntrance),
|
||||||
|
DoorObject(Position.SouthE, DoorKind.CaveEntrance)])
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
from Utils import snes_to_pc
|
||||||
|
|
||||||
|
# Subtype 3 object (0x2xx by jpdasm id - see bank 01)
|
||||||
|
# B
|
||||||
|
Normal_Pot = (0xFA, 3, 3)
|
||||||
|
Shuffled_Pot = (0xFB, 0, 0) # formerly weird pot, or black diagonal thing
|
||||||
|
|
||||||
|
|
||||||
|
class RoomObject:
|
||||||
|
|
||||||
|
def __init__(self, address, data):
|
||||||
|
self.address = address
|
||||||
|
self.data = data
|
||||||
|
|
||||||
|
def change_type(self, new_type):
|
||||||
|
type_id, datum_a, datum_b = new_type
|
||||||
|
if 0xF8 <= type_id < 0xFC: # sub type 3
|
||||||
|
self.data = (self.data[0] & 0xFC) | datum_a, (self.data[1] & 0xFC) | datum_b, type_id
|
||||||
|
else:
|
||||||
|
pass # not yet implemented
|
||||||
|
|
||||||
|
def write_to_rom(self, rom):
|
||||||
|
rom.write_bytes(snes_to_pc(self.address), self.data)
|
||||||
|
|
||||||
|
|
||||||
|
class DoorObject:
|
||||||
|
|
||||||
|
def __init__(self, pos, kind):
|
||||||
|
self.pos = pos
|
||||||
|
self.kind = kind
|
||||||
|
|
||||||
|
def get_bytes(self):
|
||||||
|
return [self.pos.value, self.kind.value]
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
from tkinter import ttk, filedialog, messagebox, StringVar, Button, Entry, Frame, Label, E, W, LEFT, RIGHT, X, BOTTOM
|
from tkinter import ttk, filedialog, messagebox, StringVar, Button, Entry, Frame, Label, E, W, LEFT, RIGHT, X, BOTTOM
|
||||||
from AdjusterMain import adjust
|
from AdjusterMain import adjust, patch
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from source.classes.SpriteSelector import SpriteSelector
|
from source.classes.SpriteSelector import SpriteSelector
|
||||||
import source.gui.widgets as widgets
|
import source.gui.widgets as widgets
|
||||||
@@ -79,7 +79,9 @@ def adjust_page(top, parent, settings):
|
|||||||
romEntry2 = Entry(adjustRomFrame, textvariable=self.romVar2)
|
romEntry2 = Entry(adjustRomFrame, textvariable=self.romVar2)
|
||||||
|
|
||||||
def RomSelect2():
|
def RomSelect2():
|
||||||
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")])
|
initdir = os.path.join(os.getcwd(), settings['outputpath']) if 'outputpath' in settings else os.getcwd()
|
||||||
|
rom = filedialog.askopenfilename(filetypes=[("Rom Files", (".sfc", ".smc")), ("All Files", "*")],
|
||||||
|
initialdir=initdir)
|
||||||
if rom:
|
if rom:
|
||||||
settings["rom"] = rom
|
settings["rom"] = rom
|
||||||
self.romVar2.set(rom)
|
self.romVar2.set(rom)
|
||||||
@@ -103,7 +105,8 @@ def adjust_page(top, parent, settings):
|
|||||||
"quickswap": "quickswap",
|
"quickswap": "quickswap",
|
||||||
"nobgm": "disablemusic",
|
"nobgm": "disablemusic",
|
||||||
"reduce_flashing": "reduce_flashing",
|
"reduce_flashing": "reduce_flashing",
|
||||||
"shuffle_sfx": "shuffle_sfx"
|
'msu_resume': 'msu_resume',
|
||||||
|
"shuffle_sfx": "shuffle_sfx",
|
||||||
}
|
}
|
||||||
guiargs = Namespace()
|
guiargs = Namespace()
|
||||||
for option in options:
|
for option in options:
|
||||||
@@ -122,6 +125,58 @@ def adjust_page(top, parent, settings):
|
|||||||
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
||||||
|
|
||||||
adjustButton = Button(self.frames["bottomAdjustFrame"], text='Adjust Rom', command=adjustRom)
|
adjustButton = Button(self.frames["bottomAdjustFrame"], text='Adjust Rom', command=adjustRom)
|
||||||
adjustButton.pack(side=BOTTOM, padx=(5, 0))
|
adjustButton.pack(padx=(5, 0))
|
||||||
|
|
||||||
|
patchFileFrame = Frame(self.frames["bottomAdjustFrame"])
|
||||||
|
patchFileLabel = Label(patchFileFrame, text='BPS Patch: ')
|
||||||
|
self.patchVar = StringVar(value=settings["patch"])
|
||||||
|
patchEntry = Entry(patchFileFrame, textvariable=self.patchVar)
|
||||||
|
|
||||||
|
def PatchSelect():
|
||||||
|
initdir = os.path.join(os.getcwd(), settings['outputpath']) if 'outputpath' in settings else os.getcwd()
|
||||||
|
file = filedialog.askopenfilename(filetypes=[("BPS Files", ".bps"), ("All Files", "*")], initialdir=initdir)
|
||||||
|
if file:
|
||||||
|
settings["patch"] = file
|
||||||
|
self.patchVar.set(file)
|
||||||
|
patchSelectButton = Button(patchFileFrame, text='Select Patch', command=PatchSelect)
|
||||||
|
|
||||||
|
patchFileLabel.pack(side=LEFT)
|
||||||
|
patchEntry.pack(side=LEFT, fill=X, expand=True)
|
||||||
|
patchSelectButton.pack(side=LEFT)
|
||||||
|
patchFileFrame.pack(fill=X)
|
||||||
|
|
||||||
|
def patchRom():
|
||||||
|
if output_path.cached_path is None:
|
||||||
|
output_path.cached_path = top.settings["outputpath"]
|
||||||
|
options = {
|
||||||
|
"heartbeep": "heartbeep",
|
||||||
|
"heartcolor": "heartcolor",
|
||||||
|
"menuspeed": "fastmenu",
|
||||||
|
"owpalettes": "ow_palettes",
|
||||||
|
"uwpalettes": "uw_palettes",
|
||||||
|
"quickswap": "quickswap",
|
||||||
|
"nobgm": "disablemusic",
|
||||||
|
"reduce_flashing": "reduce_flashing",
|
||||||
|
'msu_resume': 'msu_resume',
|
||||||
|
"shuffle_sfx": "shuffle_sfx",
|
||||||
|
}
|
||||||
|
guiargs = Namespace()
|
||||||
|
for option in options:
|
||||||
|
arg = options[option]
|
||||||
|
setattr(guiargs, arg, self.widgets[option].storageVar.get())
|
||||||
|
guiargs.patch = self.patchVar.get()
|
||||||
|
guiargs.baserom = top.pages["randomizer"].pages["generation"].widgets["rom"].storageVar.get()
|
||||||
|
guiargs.sprite = self.sprite
|
||||||
|
guiargs.outputpath = os.path.dirname(guiargs.patch)
|
||||||
|
try:
|
||||||
|
patch(args=guiargs)
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(e)
|
||||||
|
messagebox.showerror(title="Error while creating seed", message=str(e))
|
||||||
|
else:
|
||||||
|
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
||||||
|
|
||||||
|
patchButton = Button(self.frames["bottomAdjustFrame"], text='Patch Rom', command=patchRom)
|
||||||
|
patchButton.pack(side=BOTTOM, padx=(5, 0))
|
||||||
|
|
||||||
return self,settings
|
return self,settings
|
||||||
|
|||||||
@@ -275,4 +275,9 @@ def create_guiargs(parent):
|
|||||||
|
|
||||||
guiargs = update_deprecated_args(guiargs)
|
guiargs = update_deprecated_args(guiargs)
|
||||||
|
|
||||||
|
# Key drop shuffle stuff
|
||||||
|
if guiargs.keydropshuffle:
|
||||||
|
guiargs.dropshuffle = 1
|
||||||
|
guiargs.pottery = 'keys' if guiargs.pottery == 'none' else guiargs.pottery
|
||||||
|
|
||||||
return guiargs
|
return guiargs
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ def loadcliargs(gui, args, settings=None):
|
|||||||
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
gui.pages[mainpage].pages[subpage].widgets[widget].label.configure(text=label)
|
||||||
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg])
|
gui.pages[mainpage].pages[subpage].widgets[widget].storageVar.set(args[arg])
|
||||||
# If we're on the Game Options page and it's not about Hints
|
# If we're on the Game Options page and it's not about Hints
|
||||||
if subpage == "gameoptions" and not widget == "hints":
|
if subpage == "gameoptions" and widget not in ["hints", "collection_rate"]:
|
||||||
# Check if we've got settings
|
# Check if we've got settings
|
||||||
# Check if we've got the widget in Adjust settings
|
# Check if we've got the widget in Adjust settings
|
||||||
hasSettings = settings is not None
|
hasSettings = settings is not None
|
||||||
|
|||||||
+86
-101
@@ -1,12 +1,13 @@
|
|||||||
import RaceRandom as random
|
import RaceRandom as random
|
||||||
import logging
|
import logging
|
||||||
from math import ceil
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from source.item.District import resolve_districts
|
from source.item.District import resolve_districts
|
||||||
|
from BaseClasses import PotItem, PotFlags
|
||||||
from DoorShuffle import validate_vanilla_reservation
|
from DoorShuffle import validate_vanilla_reservation
|
||||||
from Dungeons import dungeon_table
|
from Dungeons import dungeon_table
|
||||||
from Items import item_table, ItemFactory
|
from Items import item_table, ItemFactory
|
||||||
|
from PotShuffle import vanilla_pots
|
||||||
|
|
||||||
|
|
||||||
class ItemPoolConfig(object):
|
class ItemPoolConfig(object):
|
||||||
@@ -28,7 +29,6 @@ class LocationGroup(object):
|
|||||||
|
|
||||||
# flags
|
# flags
|
||||||
self.keyshuffle = False
|
self.keyshuffle = False
|
||||||
self.keydropshuffle = False
|
|
||||||
self.shopsanity = False
|
self.shopsanity = False
|
||||||
self.retro = False
|
self.retro = False
|
||||||
|
|
||||||
@@ -36,9 +36,8 @@ class LocationGroup(object):
|
|||||||
self.locations = list(locs)
|
self.locations = list(locs)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def flags(self, k, d=False, s=False, r=False):
|
def flags(self, k, s=False, r=False):
|
||||||
self.keyshuffle = k
|
self.keyshuffle = k
|
||||||
self.keydropshuffle = d
|
|
||||||
self.shopsanity = s
|
self.shopsanity = s
|
||||||
self.retro = r
|
self.retro = r
|
||||||
return self
|
return self
|
||||||
@@ -64,34 +63,40 @@ def create_item_pool_config(world):
|
|||||||
config.static_placement = {}
|
config.static_placement = {}
|
||||||
config.location_groups = {}
|
config.location_groups = {}
|
||||||
for player in range(1, world.players + 1):
|
for player in range(1, world.players + 1):
|
||||||
config.static_placement[player] = vanilla_mapping.copy()
|
config.static_placement[player] = defaultdict(list)
|
||||||
if world.keydropshuffle[player]:
|
config.static_placement[player].update(vanilla_mapping)
|
||||||
|
if world.dropshuffle[player]:
|
||||||
for item, locs in keydrop_vanilla_mapping.items():
|
for item, locs in keydrop_vanilla_mapping.items():
|
||||||
if item in config.static_placement[player]:
|
|
||||||
config.static_placement[player][item].extend(locs)
|
config.static_placement[player][item].extend(locs)
|
||||||
else:
|
if world.pottery[player] not in ['none', 'cave']:
|
||||||
config.static_placement[player][item] = list(locs)
|
for item, locs in potkeys_vanilla_mapping.items():
|
||||||
|
config.static_placement[player][item].extend(locs)
|
||||||
|
if world.pottery[player] in ['lottery', 'cave', 'dungeon']:
|
||||||
|
for super_tile, pot_list in vanilla_pots.items():
|
||||||
|
for pot_index, pot in enumerate(pot_list):
|
||||||
|
if pot.item not in [PotItem.Key, PotItem.Hole, PotItem.Switch]:
|
||||||
|
item = pot_items[pot.item]
|
||||||
|
descriptor = 'Large Block' if pot.flags & PotFlags.Block else f'Pot #{pot_index+1}'
|
||||||
|
location = f'{pot.room} {descriptor}'
|
||||||
|
config.static_placement[player][item].append(location)
|
||||||
if world.shopsanity[player]:
|
if world.shopsanity[player]:
|
||||||
for item, locs in shop_vanilla_mapping.items():
|
for item, locs in shop_vanilla_mapping.items():
|
||||||
if item in config.static_placement[player]:
|
|
||||||
config.static_placement[player][item].extend(locs)
|
config.static_placement[player][item].extend(locs)
|
||||||
else:
|
|
||||||
config.static_placement[player][item] = list(locs)
|
|
||||||
if world.retro[player]:
|
if world.retro[player]:
|
||||||
for item, locs in retro_vanilla_mapping.items():
|
for item, locs in retro_vanilla_mapping.items():
|
||||||
if item in config.static_placement[player]:
|
|
||||||
config.static_placement[player][item].extend(locs)
|
config.static_placement[player][item].extend(locs)
|
||||||
else:
|
|
||||||
config.static_placement[player][item] = list(locs)
|
|
||||||
# universal keys
|
# universal keys
|
||||||
universal_key_locations = []
|
universal_key_locations = []
|
||||||
for item, locs in vanilla_mapping.items():
|
for item, locs in vanilla_mapping.items():
|
||||||
if 'Small Key' in item:
|
if 'Small Key' in item:
|
||||||
universal_key_locations.extend(locs)
|
universal_key_locations.extend(locs)
|
||||||
if world.keydropshuffle[player]:
|
if world.dropshuffle[player]:
|
||||||
for item, locs in keydrop_vanilla_mapping.items():
|
for item, locs in keydrop_vanilla_mapping.items():
|
||||||
if 'Small Key' in item:
|
if 'Small Key' in item:
|
||||||
universal_key_locations.extend(locs)
|
universal_key_locations.extend(locs)
|
||||||
|
if world.pottery[player] not in ['none', 'cave']:
|
||||||
|
for item, locs in potkeys_vanilla_mapping.items():
|
||||||
|
universal_key_locations.extend(locs)
|
||||||
if world.shopsanity[player]:
|
if world.shopsanity[player]:
|
||||||
single_arrow_placement = list(shop_vanilla_mapping['Red Potion'])
|
single_arrow_placement = list(shop_vanilla_mapping['Red Potion'])
|
||||||
single_arrow_placement.append('Red Shield Shop - Right')
|
single_arrow_placement.append('Red Shield Shop - Right')
|
||||||
@@ -117,12 +122,14 @@ def create_item_pool_config(world):
|
|||||||
groups = LocationGroup('Major').locs(init_set)
|
groups = LocationGroup('Major').locs(init_set)
|
||||||
if world.bigkeyshuffle[player]:
|
if world.bigkeyshuffle[player]:
|
||||||
groups.locations.extend(mode_grouping['Big Keys'])
|
groups.locations.extend(mode_grouping['Big Keys'])
|
||||||
if world.keydropshuffle[player]:
|
if world.dropshuffle[player] != 'none':
|
||||||
groups.locations.extend(mode_grouping['Big Key Drops'])
|
groups.locations.extend(mode_grouping['Big Key Drops'])
|
||||||
if world.keyshuffle[player]:
|
if world.keyshuffle[player]:
|
||||||
groups.locations.extend(mode_grouping['Small Keys'])
|
groups.locations.extend(mode_grouping['Small Keys'])
|
||||||
if world.keydropshuffle[player]:
|
if world.dropshuffle[player] != 'none':
|
||||||
groups.locations.extend(mode_grouping['Key Drops'])
|
groups.locations.extend(mode_grouping['Key Drops'])
|
||||||
|
if world.pottery[player] not in ['none', 'cave']:
|
||||||
|
groups.locations.extend(mode_grouping['Pot Keys'])
|
||||||
if world.compassshuffle[player]:
|
if world.compassshuffle[player]:
|
||||||
groups.locations.extend(mode_grouping['Compasses'])
|
groups.locations.extend(mode_grouping['Compasses'])
|
||||||
if world.mapshuffle[player]:
|
if world.mapshuffle[player]:
|
||||||
@@ -268,7 +275,7 @@ def massage_item_pool(world):
|
|||||||
world.itempool.remove(deleted)
|
world.itempool.remove(deleted)
|
||||||
discrepancy -= 1
|
discrepancy -= 1
|
||||||
if discrepancy > 0:
|
if discrepancy > 0:
|
||||||
logging.getLogger('').warning(f'Too many good items in pool, something will be removed at random')
|
raise Exception(f'Too many required items in pool, {discrepancy} items cannot be placed')
|
||||||
if world.item_pool_config.placeholders is not None:
|
if world.item_pool_config.placeholders is not None:
|
||||||
removed = 0
|
removed = 0
|
||||||
single_rupees = [item for item in world.itempool if item.name == 'Rupee (1)']
|
single_rupees = [item for item in world.itempool if item.name == 'Rupee (1)']
|
||||||
@@ -322,49 +329,6 @@ def count_major_items(config, world, player):
|
|||||||
return sum(1 for x in world.itempool if x.name in config.item_pool[player] and x.player == player)
|
return sum(1 for x in world.itempool if x.name in config.item_pool[player] and x.player == player)
|
||||||
|
|
||||||
|
|
||||||
def calc_dungeon_limits(world, player):
|
|
||||||
b, s, c, m, k, r, bi = (world.bigkeyshuffle[player], world.keyshuffle[player], world.compassshuffle[player],
|
|
||||||
world.mapshuffle[player], world.keydropshuffle[player], world.retro[player],
|
|
||||||
world.restrict_boss_items[player])
|
|
||||||
if world.doorShuffle[player] in ['vanilla', 'basic']:
|
|
||||||
limits = {}
|
|
||||||
for dungeon, info in dungeon_table.items():
|
|
||||||
val = info.free_items
|
|
||||||
if bi != 'none' and info.prize:
|
|
||||||
if bi == 'mapcompass' and (not c or not m):
|
|
||||||
val -= 1
|
|
||||||
if bi == 'dungeon' and (not c or not m or not (s or r) or not b):
|
|
||||||
val -= 1
|
|
||||||
if b:
|
|
||||||
val += 1 if info.bk_present else 0
|
|
||||||
if k:
|
|
||||||
val += 1 if info.bk_drops else 0
|
|
||||||
if s or r:
|
|
||||||
val += info.key_num
|
|
||||||
if k:
|
|
||||||
val += info.key_drops
|
|
||||||
if c:
|
|
||||||
val += 1 if info.compass_present else 0
|
|
||||||
if m:
|
|
||||||
val += 1 if info.map_present else 0
|
|
||||||
limits[dungeon] = val
|
|
||||||
else:
|
|
||||||
limits = 60
|
|
||||||
if world.bigkeyshuffle[player]:
|
|
||||||
limits += 11
|
|
||||||
if world.keydropshuffle[player]:
|
|
||||||
limits += 1
|
|
||||||
if world.keyshuffle[player] or world.retro[player]:
|
|
||||||
limits += 29
|
|
||||||
if world.keydropshuffle[player]:
|
|
||||||
limits += 32
|
|
||||||
if world.compassshuffle[player]:
|
|
||||||
limits += 11
|
|
||||||
if world.mapshuffle[player]:
|
|
||||||
limits += 12
|
|
||||||
return limits
|
|
||||||
|
|
||||||
|
|
||||||
def determine_major_items(world, player):
|
def determine_major_items(world, player):
|
||||||
major_item_set = set(major_items)
|
major_item_set = set(major_items)
|
||||||
if world.progressive == 'off':
|
if world.progressive == 'off':
|
||||||
@@ -454,6 +418,19 @@ def filter_locations(item_to_place, locations, world, vanilla_skip=False, potion
|
|||||||
return locations
|
return locations
|
||||||
|
|
||||||
|
|
||||||
|
def filter_pot_locations(locations, world):
|
||||||
|
if world.algorithm == 'district':
|
||||||
|
config = world.item_pool_config
|
||||||
|
restricted = config.location_groups[0].locations
|
||||||
|
filtered = [l for l in locations if l.name not in restricted or l.player not in restricted[l.name]]
|
||||||
|
return filtered if len(filtered) > 0 else locations
|
||||||
|
if world.algorithm == 'vanilla_fill':
|
||||||
|
filtered = [l for l in locations if l.pot and l.pot.item in [PotItem.Chicken, PotItem.BigMagic]]
|
||||||
|
return filtered if len(filtered) > 0 else locations
|
||||||
|
return locations
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
vanilla_mapping = {
|
vanilla_mapping = {
|
||||||
'Green Pendant': ['Eastern Palace - Prize'],
|
'Green Pendant': ['Eastern Palace - Prize'],
|
||||||
'Red Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
'Red Pendant': ['Desert Palace - Prize', 'Tower of Hera - Prize'],
|
||||||
@@ -615,25 +592,31 @@ vanilla_mapping = {
|
|||||||
|
|
||||||
|
|
||||||
keydrop_vanilla_mapping = {
|
keydrop_vanilla_mapping = {
|
||||||
'Small Key (Desert Palace)': ['Desert Palace - Desert Tiles 1 Pot Key',
|
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Eyegore Key Drop'],
|
||||||
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key'],
|
|
||||||
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop'],
|
|
||||||
'Small Key (Escape)': ['Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
'Small Key (Escape)': ['Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||||
'Hyrule Castle - Key Rat Key Drop'],
|
'Hyrule Castle - Key Rat Key Drop'],
|
||||||
'Big Key (Escape)': ['Hyrule Castle - Big Key Drop'],
|
'Big Key (Escape)': ['Hyrule Castle - Big Key Drop'],
|
||||||
'Small Key (Agahnims Tower)': ['Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'],
|
'Small Key (Agahnims Tower)': ['Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'],
|
||||||
|
'Small Key (Skull Woods)': ['Skull Woods - Spike Corner Key Drop'],
|
||||||
|
'Small Key (Ice Palace)': ['Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop'],
|
||||||
|
'Small Key (Misery Mire)': ['Misery Mire - Conveyor Crystal Key Drop'],
|
||||||
|
'Small Key (Turtle Rock)': ['Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop'],
|
||||||
|
'Small Key (Ganons Tower)': ['Ganons Tower - Mini Helmasaur Key Drop'],
|
||||||
|
}
|
||||||
|
|
||||||
|
potkeys_vanilla_mapping = {
|
||||||
|
'Small Key (Desert Palace)': ['Desert Palace - Desert Tiles 1 Pot Key',
|
||||||
|
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key'],
|
||||||
|
'Small Key (Eastern Palace)': ['Eastern Palace - Dark Square Pot Key'],
|
||||||
'Small Key (Thieves Town)': ["Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key"],
|
'Small Key (Thieves Town)': ["Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key"],
|
||||||
'Small Key (Skull Woods)': ['Skull Woods - West Lobby Pot Key', 'Skull Woods - Spike Corner Key Drop'],
|
'Small Key (Skull Woods)': ['Skull Woods - West Lobby Pot Key'],
|
||||||
'Small Key (Swamp Palace)': ['Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key',
|
'Small Key (Swamp Palace)': ['Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key',
|
||||||
'Swamp Palace - Hookshot Pot Key', 'Swamp Palace - Trench 2 Pot Key',
|
'Swamp Palace - Hookshot Pot Key', 'Swamp Palace - Trench 2 Pot Key',
|
||||||
'Swamp Palace - Waterway Pot Key'],
|
'Swamp Palace - Waterway Pot Key'],
|
||||||
'Small Key (Ice Palace)': ['Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
'Small Key (Ice Palace)': ['Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key'],
|
||||||
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key'],
|
'Small Key (Misery Mire)': ['Misery Mire - Spikes Pot Key', 'Misery Mire - Fishbone Pot Key'],
|
||||||
'Small Key (Misery Mire)': ['Misery Mire - Spikes Pot Key',
|
|
||||||
'Misery Mire - Fishbone Pot Key', 'Misery Mire - Conveyor Crystal Key Drop'],
|
|
||||||
'Small Key (Turtle Rock)': ['Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop'],
|
|
||||||
'Small Key (Ganons Tower)': ['Ganons Tower - Conveyor Cross Pot Key', 'Ganons Tower - Double Switch Pot Key',
|
'Small Key (Ganons Tower)': ['Ganons Tower - Conveyor Cross Pot Key', 'Ganons Tower - Double Switch Pot Key',
|
||||||
'Ganons Tower - Conveyor Star Pits Pot Key', 'Ganons Tower - Mini Helmasuar Key Drop'],
|
'Ganons Tower - Conveyor Star Pits Pot Key'],
|
||||||
}
|
}
|
||||||
|
|
||||||
shop_vanilla_mapping = {
|
shop_vanilla_mapping = {
|
||||||
@@ -682,7 +665,7 @@ mode_grouping = {
|
|||||||
'Maze Race', 'Spectacle Rock', 'Pyramid', "Zora's Ledge", 'Lumberjack Tree',
|
'Maze Race', 'Spectacle Rock', 'Pyramid', "Zora's Ledge", 'Lumberjack Tree',
|
||||||
'Sunken Treasure', 'Spectacle Rock Cave', 'Lost Woods Hideout', 'Checkerboard Cave', 'Peg Cave', 'Cave 45',
|
'Sunken Treasure', 'Spectacle Rock Cave', 'Lost Woods Hideout', 'Checkerboard Cave', 'Peg Cave', 'Cave 45',
|
||||||
'Graveyard Cave', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Bonk Rock Cave', "Aginah's Cave",
|
'Graveyard Cave', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Bonk Rock Cave', "Aginah's Cave",
|
||||||
'Chest Game', 'Digging Game', 'Mire Shed - Right', 'Mimic Cave'
|
'Chest Game', 'Digging Game', 'Mire Shed - Left', 'Mimic Cave'
|
||||||
],
|
],
|
||||||
'Big Keys': [
|
'Big Keys': [
|
||||||
'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest',
|
'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest',
|
||||||
@@ -735,7 +718,7 @@ mode_grouping = {
|
|||||||
'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right', 'Spiral Cave', 'Brewery', 'C-Shaped House',
|
'Paradox Cave Upper - Left', 'Paradox Cave Upper - Right', 'Spiral Cave', 'Brewery', 'C-Shaped House',
|
||||||
'Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom',
|
'Hype Cave - Top', 'Hype Cave - Middle Right', 'Hype Cave - Middle Left', 'Hype Cave - Bottom',
|
||||||
'Hype Cave - Generous Guy', 'Superbunny Cave - Bottom', 'Superbunny Cave - Top', 'Hookshot Cave - Top Right',
|
'Hype Cave - Generous Guy', 'Superbunny Cave - Bottom', 'Superbunny Cave - Top', 'Hookshot Cave - Top Right',
|
||||||
'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left', 'Mire Shed - Left'
|
'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right', 'Hookshot Cave - Bottom Left', 'Mire Shed - Right'
|
||||||
],
|
],
|
||||||
'GT Trash': [
|
'GT Trash': [
|
||||||
'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Top Left',
|
'Ganons Tower - DMs Room - Top Right', 'Ganons Tower - DMs Room - Top Left',
|
||||||
@@ -751,19 +734,22 @@ mode_grouping = {
|
|||||||
],
|
],
|
||||||
'Key Drops': [
|
'Key Drops': [
|
||||||
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||||
'Hyrule Castle - Key Rat Key Drop', 'Eastern Palace - Dark Square Pot Key',
|
'Hyrule Castle - Key Rat Key Drop', 'Eastern Palace - Dark Eyegore Key Drop',
|
||||||
'Eastern Palace - Dark Eyegore Key Drop', 'Desert Palace - Desert Tiles 1 Pot Key',
|
|
||||||
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key',
|
|
||||||
'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop',
|
'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop',
|
||||||
|
'Skull Woods - Spike Corner Key Drop', 'Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
||||||
|
'Misery Mire - Conveyor Crystal Key Drop', 'Turtle Rock - Pokey 1 Key Drop',
|
||||||
|
'Turtle Rock - Pokey 2 Key Drop', 'Ganons Tower - Mini Helmasuar Key Drop',
|
||||||
|
],
|
||||||
|
'Pot Keys': [
|
||||||
|
'Eastern Palace - Dark Square Pot Key', 'Desert Palace - Desert Tiles 1 Pot Key',
|
||||||
|
'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key',
|
||||||
'Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key', 'Swamp Palace - Hookshot Pot Key',
|
'Swamp Palace - Pot Row Pot Key', 'Swamp Palace - Trench 1 Pot Key', 'Swamp Palace - Hookshot Pot Key',
|
||||||
'Swamp Palace - Trench 2 Pot Key', 'Swamp Palace - Waterway Pot Key', 'Skull Woods - West Lobby Pot Key',
|
'Swamp Palace - Trench 2 Pot Key', 'Swamp Palace - Waterway Pot Key', 'Skull Woods - West Lobby Pot Key',
|
||||||
'Skull Woods - Spike Corner Key Drop', "Thieves' Town - Hallway Pot Key",
|
"Thieves' Town - Hallway Pot Key", "Thieves' Town - Spike Switch Pot Key",
|
||||||
"Thieves' Town - Spike Switch Pot Key", 'Ice Palace - Jelly Key Drop', 'Ice Palace - Conveyor Key Drop',
|
|
||||||
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key', 'Misery Mire - Spikes Pot Key',
|
'Ice Palace - Hammer Block Key Drop', 'Ice Palace - Many Pots Pot Key', 'Misery Mire - Spikes Pot Key',
|
||||||
'Misery Mire - Fishbone Pot Key', 'Misery Mire - Conveyor Crystal Key Drop', 'Turtle Rock - Pokey 1 Key Drop',
|
'Misery Mire - Fishbone Pot Key', 'Ganons Tower - Conveyor Cross Pot Key',
|
||||||
'Turtle Rock - Pokey 2 Key Drop', 'Ganons Tower - Conveyor Cross Pot Key',
|
|
||||||
'Ganons Tower - Double Switch Pot Key', 'Ganons Tower - Conveyor Star Pits Pot Key',
|
'Ganons Tower - Double Switch Pot Key', 'Ganons Tower - Conveyor Star Pits Pot Key',
|
||||||
'Ganons Tower - Mini Helmasuar Key Drop',
|
|
||||||
],
|
],
|
||||||
'Big Key Drops': ['Hyrule Castle - Big Key Drop'],
|
'Big Key Drops': ['Hyrule Castle - Big Key Drop'],
|
||||||
'Shops': [
|
'Shops': [
|
||||||
@@ -804,25 +790,24 @@ vanilla_swords = {"Link's Uncle", 'Master Sword Pedestal', 'Blacksmith', 'Pyrami
|
|||||||
trash_items = {
|
trash_items = {
|
||||||
'Nothing': -1,
|
'Nothing': -1,
|
||||||
'Bee Trap': 0,
|
'Bee Trap': 0,
|
||||||
'Rupee (1)': 1,
|
'Rupee (1)': 1, 'Rupees (5)': 1, 'Small Heart': 1, 'Bee': 1, 'Arrows (5)': 1, 'Chicken': 1, 'Single Bomb': 1,
|
||||||
'Rupees (5)': 1,
|
'Rupees (20)': 2, 'Small Magic': 2,
|
||||||
'Rupees (20)': 1,
|
'Bombs (3)': 3, 'Arrows (10)': 3, 'Bombs (10)': 3,
|
||||||
|
'Big Magic': 4, 'Red Potion': 4, 'Blue Shield': 4, 'Rupees (50)': 4, 'Rupees (100)': 4,
|
||||||
'Small Heart': 2,
|
|
||||||
'Bee': 2,
|
|
||||||
'Arrows (5)': 2,
|
|
||||||
'Chicken': 2,
|
|
||||||
'Single Bomb': 2,
|
|
||||||
|
|
||||||
'Bombs (3)': 3,
|
|
||||||
'Arrows (10)': 3,
|
|
||||||
'Bombs (10)': 3,
|
|
||||||
|
|
||||||
'Red Potion': 4,
|
|
||||||
'Blue Shield': 4,
|
|
||||||
'Rupees (50)': 4,
|
|
||||||
'Rupees (100)': 4,
|
|
||||||
'Rupees (300)': 5,
|
'Rupees (300)': 5,
|
||||||
|
|
||||||
'Piece of Heart': 17
|
'Piece of Heart': 17
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pot_items = {
|
||||||
|
PotItem.Nothing: 'Nothing',
|
||||||
|
PotItem.Bomb: 'Single Bomb',
|
||||||
|
PotItem.FiveArrows: 'Arrows (5)', # convert to 10
|
||||||
|
PotItem.OneRupee: 'Rupee (1)',
|
||||||
|
PotItem.FiveRupees: 'Rupees (5)',
|
||||||
|
PotItem.Heart: 'Small Heart',
|
||||||
|
PotItem.BigMagic: 'Big Magic', # fast fill
|
||||||
|
PotItem.SmallMagic: 'Small Magic',
|
||||||
|
PotItem.Chicken: 'Chicken' # fast fill
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_pot_items = {y: x for x, y in pot_items.items()}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import importlib.util
|
||||||
|
import webbrowser
|
||||||
|
from tkinter import Tk, Label, Button, Frame
|
||||||
|
|
||||||
|
|
||||||
|
def check_requirements(console=False):
|
||||||
|
check_packages = {'aenum': 'aenum',
|
||||||
|
'fast-enum': 'fast_enum',
|
||||||
|
'python-bps-continued': 'bps',
|
||||||
|
'colorama': 'colorama',
|
||||||
|
'aioconsole' : 'aioconsole',
|
||||||
|
'websockets' : 'websockets',
|
||||||
|
'pyyaml': 'yaml'}
|
||||||
|
missing = []
|
||||||
|
for package, import_name in check_packages.items():
|
||||||
|
spec = importlib.util.find_spec(import_name)
|
||||||
|
if spec is None:
|
||||||
|
missing.append(package)
|
||||||
|
if len(missing) > 0:
|
||||||
|
packages = ','.join(missing)
|
||||||
|
if console:
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger('')
|
||||||
|
logger.error('You need to install the following python packages:')
|
||||||
|
logger.error(f'{packages}')
|
||||||
|
logger.error('See the step about "Installing Platform-specific dependencies":')
|
||||||
|
logger.error('https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/BUILDING.md')
|
||||||
|
else:
|
||||||
|
master = Tk()
|
||||||
|
master.title('Error')
|
||||||
|
frame = Frame(master)
|
||||||
|
frame.pack(expand=True, padx =50, pady=50)
|
||||||
|
Label(frame, text='You need to install the following python packages:').pack()
|
||||||
|
Label(frame, text=f'{packages}').pack()
|
||||||
|
Label(frame, text='See the step about "Installing Platform-specific dependencies":').pack()
|
||||||
|
url = 'https://github.com/aerinon/ALttPDoorRandomizer/blob/DoorDev/docs/BUILDING.md'
|
||||||
|
link = Label(frame, fg='blue', cursor='hand2', text=url)
|
||||||
|
link.pack()
|
||||||
|
link.bind('<Button-1>', lambda e: webbrowser.open_new_tab(url))
|
||||||
|
Button(master, text='Ok', command=master.destroy).pack()
|
||||||
|
master.mainloop()
|
||||||
@@ -25,7 +25,7 @@ def main(args=None):
|
|||||||
|
|
||||||
def test(testname: str, command: str):
|
def test(testname: str, command: str):
|
||||||
tests[testname] = [command]
|
tests[testname] = [command]
|
||||||
basecommand = f"python3.8 Mystery.py --suppress_rom"
|
basecommand = f"python3.8 Mystery.py --suppress_rom --suppress_meta"
|
||||||
|
|
||||||
def gen_seed():
|
def gen_seed():
|
||||||
taskcommand = basecommand + " " + command
|
taskcommand = basecommand + " " + command
|
||||||
|
|||||||
@@ -0,0 +1,324 @@
|
|||||||
|
# Code derived from https://github.com/marcrobledo/RomPatcher.js (MIT License)
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from time import perf_counter
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from binascii import crc32
|
||||||
|
try:
|
||||||
|
from fast_enum import FastEnum
|
||||||
|
except ImportError:
|
||||||
|
from enum import IntFlag as FastEnum
|
||||||
|
|
||||||
|
|
||||||
|
def bps_get_vlv_len(data):
|
||||||
|
length = 0
|
||||||
|
while True:
|
||||||
|
x = data & 0x7f
|
||||||
|
data >>= 7
|
||||||
|
if data == 0:
|
||||||
|
length += 1
|
||||||
|
break
|
||||||
|
length += 1
|
||||||
|
data -= 1
|
||||||
|
return length
|
||||||
|
|
||||||
|
|
||||||
|
def bps_read_vlv(stream):
|
||||||
|
data, shift = 0, 1
|
||||||
|
while True:
|
||||||
|
x = stream.read(1)[0]
|
||||||
|
data += (x & 0x7f) * shift
|
||||||
|
if x & 0x80:
|
||||||
|
return data
|
||||||
|
shift <<= 7
|
||||||
|
data += shift
|
||||||
|
|
||||||
|
|
||||||
|
class Bps:
|
||||||
|
def __init__(self):
|
||||||
|
self.source_size = 0
|
||||||
|
self.target_size = 0
|
||||||
|
self.metadata = ''
|
||||||
|
self.actions = []
|
||||||
|
self.source_checksum = 0
|
||||||
|
self.target_checksum = 0
|
||||||
|
self.patch_checksum = 0
|
||||||
|
|
||||||
|
self.binary_ba = bytearray()
|
||||||
|
self.offset = 0
|
||||||
|
|
||||||
|
def write_to_binary(self):
|
||||||
|
patch_size = 4
|
||||||
|
patch_size += bps_get_vlv_len(self.source_size)
|
||||||
|
patch_size += bps_get_vlv_len(self.target_size)
|
||||||
|
patch_size += bps_get_vlv_len(len(self.metadata))
|
||||||
|
patch_size += len(self.metadata)
|
||||||
|
|
||||||
|
for action in self.actions:
|
||||||
|
mode, length, data = action
|
||||||
|
patch_size += bps_get_vlv_len(((length - 1) << 2) + mode)
|
||||||
|
|
||||||
|
if mode == BpsMode.BPS_ACTION_TARGET_READ:
|
||||||
|
patch_size += length
|
||||||
|
elif mode == BpsMode.BPS_ACTION_SOURCE_COPY or mode == BpsMode.BPS_ACTION_TARGET_COPY:
|
||||||
|
patch_size += bps_get_vlv_len((abs(data) << 1) + (1 if data < 0 else 0))
|
||||||
|
patch_size += 12
|
||||||
|
|
||||||
|
self.binary_ba = bytearray(patch_size)
|
||||||
|
self.write_string('BPS1')
|
||||||
|
self.bps_write_vlv(self.source_size)
|
||||||
|
self.bps_write_vlv(self.target_size)
|
||||||
|
self.bps_write_vlv(len(self.metadata))
|
||||||
|
self.write_string(self.metadata)
|
||||||
|
|
||||||
|
for action in self.actions:
|
||||||
|
mode, length, data = action
|
||||||
|
self.bps_write_vlv(((length - 1) << 2) + mode)
|
||||||
|
if mode == BpsMode.BPS_ACTION_TARGET_READ:
|
||||||
|
self.write_bytes(data)
|
||||||
|
elif mode == BpsMode.BPS_ACTION_SOURCE_COPY or mode == BpsMode.BPS_ACTION_TARGET_COPY:
|
||||||
|
self.bps_write_vlv((abs(data) << 1) + (1 if data < 0 else 0))
|
||||||
|
self.write_u32(self.source_checksum)
|
||||||
|
self.write_u32(self.target_checksum)
|
||||||
|
self.write_u32(self.patch_checksum)
|
||||||
|
|
||||||
|
def write_string(self, string):
|
||||||
|
for ch in string:
|
||||||
|
self.binary_ba[self.offset] = ord(ch)
|
||||||
|
self.offset += 1
|
||||||
|
|
||||||
|
def write_byte(self, byte):
|
||||||
|
self.binary_ba[self.offset] = byte
|
||||||
|
self.offset += 1
|
||||||
|
|
||||||
|
def write_bytes(self, m_bytes):
|
||||||
|
for byte in m_bytes:
|
||||||
|
self.binary_ba[self.offset] = byte
|
||||||
|
self.offset += 1
|
||||||
|
|
||||||
|
def write_u32(self, data):
|
||||||
|
self.binary_ba[self.offset] = data & 0x000000ff
|
||||||
|
self.binary_ba[self.offset+1] = (data & 0x0000ff00) >> 8
|
||||||
|
self.binary_ba[self.offset+2] = (data & 0x00ff0000) >> 16
|
||||||
|
self.binary_ba[self.offset+3] = (data & 0xff000000) >> 24
|
||||||
|
self.offset += 4
|
||||||
|
|
||||||
|
def bps_write_vlv(self, data):
|
||||||
|
while True:
|
||||||
|
x = data & 0x7f
|
||||||
|
data >>= 7
|
||||||
|
if data == 0:
|
||||||
|
self.write_byte(0x80 | x)
|
||||||
|
break
|
||||||
|
self.write_byte(x)
|
||||||
|
data -= 1
|
||||||
|
|
||||||
|
|
||||||
|
class BpsMode(FastEnum):
|
||||||
|
BPS_ACTION_SOURCE_READ = 0
|
||||||
|
BPS_ACTION_TARGET_READ = 1
|
||||||
|
BPS_ACTION_SOURCE_COPY = 2
|
||||||
|
BPS_ACTION_TARGET_COPY = 3
|
||||||
|
|
||||||
|
|
||||||
|
def create_bps_from_data(original, modified):
|
||||||
|
patch = Bps()
|
||||||
|
patch.source_size = len(original)
|
||||||
|
patch.target_size = len(modified)
|
||||||
|
|
||||||
|
patch.actions = create_bps_linear(original, modified)
|
||||||
|
|
||||||
|
patch.source_checksum = crc32(original)
|
||||||
|
patch.target_checksum = crc32(modified)
|
||||||
|
patch.write_to_binary()
|
||||||
|
patch.patch_checksum = crc32(patch.binary_ba[:-4])
|
||||||
|
patch.offset = len(patch.binary_ba) - 4
|
||||||
|
patch.write_u32(patch.patch_checksum)
|
||||||
|
return patch
|
||||||
|
|
||||||
|
|
||||||
|
def create_bps_delta(original, modified):
|
||||||
|
patch_actions = []
|
||||||
|
source_data = original
|
||||||
|
target_data = modified
|
||||||
|
source_size = len(original)
|
||||||
|
target_size = len(modified)
|
||||||
|
|
||||||
|
source_relative_offset = 0
|
||||||
|
target_relative_offset = 0
|
||||||
|
output_offset = 0
|
||||||
|
|
||||||
|
source_tree = defaultdict(list)
|
||||||
|
source_tree_2 = defaultdict(list)
|
||||||
|
target_tree = defaultdict(list)
|
||||||
|
|
||||||
|
t1_start = perf_counter()
|
||||||
|
for offset in range(0, source_size):
|
||||||
|
symbol = source_data[offset]
|
||||||
|
if offset < source_size - 1:
|
||||||
|
symbol |= source_data[offset + 1] << 8
|
||||||
|
source_tree[symbol].append(offset)
|
||||||
|
print(f'Elasped Time 1: {perf_counter()-t1_start}')
|
||||||
|
|
||||||
|
source_array = list(source_data)
|
||||||
|
|
||||||
|
t2_start = perf_counter()
|
||||||
|
for offset in range(0, source_size):
|
||||||
|
symbol = source_array[offset]
|
||||||
|
if offset < source_size - 1:
|
||||||
|
symbol |= source_array[offset + 1] << 8
|
||||||
|
source_tree_2[symbol].append(offset)
|
||||||
|
print(f'Elasped Time 2: {perf_counter()-t2_start}')
|
||||||
|
|
||||||
|
trl = {'target_read_length': 0}
|
||||||
|
|
||||||
|
def target_read_flush(buffer):
|
||||||
|
if buffer['target_read_length']:
|
||||||
|
action = (BpsMode.BPS_ACTION_TARGET_READ, buffer['target_read_length'], [])
|
||||||
|
patch_actions.append(action)
|
||||||
|
offset = output_offset - buffer['target_read_length']
|
||||||
|
while buffer['target_read_length']:
|
||||||
|
action[2].append(target_data[offset])
|
||||||
|
offset += 1
|
||||||
|
buffer['target_read_length'] -= 1
|
||||||
|
|
||||||
|
while output_offset < target_size:
|
||||||
|
max_length, max_offset, mode = 0, 0, BpsMode.BPS_ACTION_TARGET_READ
|
||||||
|
|
||||||
|
symbol = target_data[output_offset]
|
||||||
|
|
||||||
|
if output_offset < target_size - 1:
|
||||||
|
symbol |= target_data[output_offset + 1] << 8
|
||||||
|
|
||||||
|
# source read
|
||||||
|
length, offset = 0, output_offset
|
||||||
|
while offset < source_size and offset < target_size and source_data[offset] == target_data[offset]:
|
||||||
|
length += 1
|
||||||
|
offset += 1
|
||||||
|
if length > max_length:
|
||||||
|
max_length, mode = length, BpsMode.BPS_ACTION_SOURCE_READ
|
||||||
|
|
||||||
|
# source copy
|
||||||
|
for node in source_tree[symbol]:
|
||||||
|
length, x, y = 0, node, output_offset
|
||||||
|
while x < source_size and y < target_size and source_data[x] == target_data[y]:
|
||||||
|
length += 1
|
||||||
|
x += 1
|
||||||
|
y += 1
|
||||||
|
if length > max_length:
|
||||||
|
max_length, max_offset, mode = length, node, BpsMode.BPS_ACTION_SOURCE_COPY
|
||||||
|
|
||||||
|
# target copy
|
||||||
|
for node in target_tree[symbol]:
|
||||||
|
length, x, y = 0, node, output_offset
|
||||||
|
while y < target_size and target_data[x] == target_data[y]:
|
||||||
|
length += 1
|
||||||
|
x += 1
|
||||||
|
y += 1
|
||||||
|
if length > max_length:
|
||||||
|
max_length, max_offset, mode = length, node, BpsMode.BPS_ACTION_TARGET_COPY
|
||||||
|
target_tree[symbol].append(output_offset)
|
||||||
|
|
||||||
|
# target read
|
||||||
|
if max_length < 4:
|
||||||
|
max_length = min(1, target_size - output_offset)
|
||||||
|
mode = BpsMode.BPS_ACTION_TARGET_READ
|
||||||
|
|
||||||
|
if mode != BpsMode.BPS_ACTION_TARGET_READ:
|
||||||
|
target_read_flush(trl)
|
||||||
|
|
||||||
|
if mode == BpsMode.BPS_ACTION_SOURCE_READ:
|
||||||
|
patch_actions.append((mode, max_length, None))
|
||||||
|
elif mode == BpsMode.BPS_ACTION_TARGET_READ:
|
||||||
|
trl['target_read_length'] += max_length
|
||||||
|
else:
|
||||||
|
if mode == BpsMode.BPS_ACTION_SOURCE_COPY:
|
||||||
|
relative_offset = max_offset - source_relative_offset
|
||||||
|
source_relative_offset = max_offset + max_length
|
||||||
|
else:
|
||||||
|
relative_offset = max_offset - target_relative_offset
|
||||||
|
target_relative_offset = max_offset + max_length
|
||||||
|
patch_actions.append((mode, max_length, relative_offset))
|
||||||
|
|
||||||
|
output_offset += max_length
|
||||||
|
|
||||||
|
target_read_flush(trl)
|
||||||
|
|
||||||
|
return patch_actions
|
||||||
|
|
||||||
|
|
||||||
|
def create_bps_linear(original, modified):
|
||||||
|
patch_actions = []
|
||||||
|
source_data = original
|
||||||
|
target_data = modified
|
||||||
|
source_size = len(original)
|
||||||
|
target_size = len(modified)
|
||||||
|
|
||||||
|
target_relative_offset = 0
|
||||||
|
output_offset = 0
|
||||||
|
trl = {'target_read_length': 0}
|
||||||
|
|
||||||
|
def target_read_flush(buffer):
|
||||||
|
if buffer['target_read_length']:
|
||||||
|
action = (BpsMode.BPS_ACTION_TARGET_READ, buffer['target_read_length'], [])
|
||||||
|
patch_actions.append(action)
|
||||||
|
offset = output_offset - buffer['target_read_length']
|
||||||
|
while buffer['target_read_length']:
|
||||||
|
action[2].append(target_data[offset])
|
||||||
|
offset += 1
|
||||||
|
buffer['target_read_length'] -= 1
|
||||||
|
|
||||||
|
eof = min(source_size, target_size)
|
||||||
|
while output_offset < target_size:
|
||||||
|
src_length, n = 0, 0
|
||||||
|
|
||||||
|
while output_offset + n < eof:
|
||||||
|
if source_data[output_offset + n] != target_data[output_offset + n]:
|
||||||
|
break
|
||||||
|
src_length += 1
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
rle_length, n = 0, 1
|
||||||
|
while output_offset + n < target_size:
|
||||||
|
if target_data[output_offset] != target_data[output_offset + n]:
|
||||||
|
break
|
||||||
|
rle_length += 1
|
||||||
|
n += 1
|
||||||
|
|
||||||
|
if rle_length >= 4:
|
||||||
|
trl['target_read_length'] += 1
|
||||||
|
output_offset += 1
|
||||||
|
target_read_flush(trl)
|
||||||
|
|
||||||
|
relative_offset = (output_offset - 1) - target_relative_offset
|
||||||
|
patch_actions.append((BpsMode.BPS_ACTION_TARGET_COPY, rle_length, relative_offset))
|
||||||
|
output_offset += rle_length
|
||||||
|
target_relative_offset = output_offset - 1
|
||||||
|
elif src_length >= 4:
|
||||||
|
target_read_flush(trl)
|
||||||
|
patch_actions.append((BpsMode.BPS_ACTION_SOURCE_READ, src_length, None))
|
||||||
|
output_offset += src_length
|
||||||
|
else:
|
||||||
|
trl['target_read_length'] += 1
|
||||||
|
output_offset += 1
|
||||||
|
|
||||||
|
target_read_flush(trl)
|
||||||
|
|
||||||
|
return patch_actions
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
with open(sys.argv[1], 'rb') as source:
|
||||||
|
sourcedata = source.read()
|
||||||
|
|
||||||
|
with open(sys.argv[2], 'rb') as target:
|
||||||
|
targetdata = target.read()
|
||||||
|
|
||||||
|
patch = create_bps_from_data(sourcedata, targetdata)
|
||||||
|
with open(sys.argv[3], 'wb') as patchfile:
|
||||||
|
patchfile.write(patch.binary_ba)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
# Demo script showing bias in VT and ER boss shuffle algorithms, with proposed fixes.
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
boss_location_list = [
|
||||||
|
'GT_top',
|
||||||
|
'GT_mid',
|
||||||
|
'TH',
|
||||||
|
'SW',
|
||||||
|
'EP',
|
||||||
|
'DP',
|
||||||
|
'PD',
|
||||||
|
'SP',
|
||||||
|
'TT',
|
||||||
|
'IP',
|
||||||
|
'MM',
|
||||||
|
'TR',
|
||||||
|
'GT_bot'
|
||||||
|
]
|
||||||
|
|
||||||
|
entrance_rando_boss_location_list = [
|
||||||
|
'GT_top',
|
||||||
|
'TH',
|
||||||
|
'SW',
|
||||||
|
'GT_mid',
|
||||||
|
'EP',
|
||||||
|
'DP',
|
||||||
|
'PD',
|
||||||
|
'SP',
|
||||||
|
'TT',
|
||||||
|
'IP',
|
||||||
|
'MM',
|
||||||
|
'TR',
|
||||||
|
'GT_bot'
|
||||||
|
]
|
||||||
|
|
||||||
|
boss_list = [
|
||||||
|
'Armos',
|
||||||
|
'Lanmo',
|
||||||
|
'Moldorm',
|
||||||
|
'Helma',
|
||||||
|
'Arrghus',
|
||||||
|
'Moth',
|
||||||
|
'Blind',
|
||||||
|
'Khold',
|
||||||
|
'Vitty',
|
||||||
|
'Trinexx'
|
||||||
|
]
|
||||||
|
|
||||||
|
results_dict = {
|
||||||
|
'GT_top': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'GT_mid': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'TH': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'SW': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'EP': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'DP': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'PD': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'SP': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'TT': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'IP': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'MM': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'TR': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0},
|
||||||
|
'GT_bot': {'Armos': 0, 'Lanmo': 0, 'Moldorm': 0, 'Helma': 0, 'Arrghus': 0, 'Moth': 0, 'Blind': 0, 'Khold': 0, 'Vitty': 0, 'Trinexx': 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
def can_place_boss(boss:str, dungeon_name: str) -> bool:
|
||||||
|
if (dungeon_name == 'GT_top'):
|
||||||
|
if boss in {'Armos', 'Arrghus', 'Blind', 'Trinexx', 'Lanmo'}:
|
||||||
|
return False
|
||||||
|
elif (dungeon_name == 'GT_mid'):
|
||||||
|
if boss == 'Blind':
|
||||||
|
return False
|
||||||
|
elif (dungeon_name == 'TH'):
|
||||||
|
if boss in {'Armos', 'Arrghus', 'Blind', 'Trinexx', 'Lanmo'}:
|
||||||
|
return False
|
||||||
|
elif (dungeon_name == 'SW'):
|
||||||
|
if boss == 'Trinexx':
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
def resetresults():
|
||||||
|
for boss_location in boss_location_list:
|
||||||
|
for boss in boss_list:
|
||||||
|
results_dict[boss_location][boss] = 0
|
||||||
|
|
||||||
|
def printresults(iterations):
|
||||||
|
for boss_location in boss_location_list:
|
||||||
|
print("Location: " + boss_location.ljust(6) + ' ', end='')
|
||||||
|
for boss in boss_list:
|
||||||
|
percentagestr = "{0:.2%}".format(results_dict[boss_location][boss] / iterations)
|
||||||
|
print(boss + ": " + percentagestr.rjust(6) + ' ', end='')
|
||||||
|
restrictive_count = results_dict[boss_location]['Armos'] + results_dict[boss_location]['Arrghus'] + results_dict[boss_location]['Blind'] + results_dict[boss_location]['Trinexx'] + results_dict[boss_location]['Lanmo']
|
||||||
|
restrictive_pct = "{0:.2%}".format(restrictive_count / iterations)
|
||||||
|
nonrestrictive_count = results_dict[boss_location]['Moldorm'] + results_dict[boss_location]['Helma'] + results_dict[boss_location]['Moth'] + results_dict[boss_location]['Khold'] + results_dict[boss_location]['Vitty']
|
||||||
|
nonrestrictive_pct = "{0:.2%}".format(nonrestrictive_count / iterations)
|
||||||
|
print("Restrictive: " + restrictive_pct.rjust(7) + " Non-restrictive: " + nonrestrictive_pct.rjust(7))
|
||||||
|
|
||||||
|
# Demonstration of reshuffle algorithm at:
|
||||||
|
# https://github.com/sporchia/alttp_vt_randomizer/blob/master/app/Randomizer.php#L399
|
||||||
|
def vt(reshuffle):
|
||||||
|
for i in range(iterations):
|
||||||
|
dupes = random.sample(boss_list, 3)
|
||||||
|
temp_full_boss_list = boss_list + dupes
|
||||||
|
shuffled_boss_list = temp_full_boss_list.copy()
|
||||||
|
random.shuffle(shuffled_boss_list)
|
||||||
|
for boss_location in boss_location_list:
|
||||||
|
boss = shuffled_boss_list.pop(0)
|
||||||
|
while (not can_place_boss(boss, boss_location)):
|
||||||
|
shuffled_boss_list.append(boss)
|
||||||
|
|
||||||
|
# Proposed fix
|
||||||
|
if reshuffle:
|
||||||
|
random.shuffle(shuffled_boss_list)
|
||||||
|
|
||||||
|
boss = shuffled_boss_list.pop(0)
|
||||||
|
results_dict[boss_location][boss] = results_dict[boss_location][boss] + 1
|
||||||
|
|
||||||
|
# Demonstration of reshuffle algorithm at:
|
||||||
|
# https://github.com/KevinCathcart/ALttPEntranceRandomizer/blob/Dev/Bosses.py#L203
|
||||||
|
def er(reshuffle):
|
||||||
|
for i in range(iterations):
|
||||||
|
dupes = random.sample(boss_list, 3)
|
||||||
|
temp_full_boss_list = boss_list + dupes
|
||||||
|
shuffled_boss_list = temp_full_boss_list.copy()
|
||||||
|
random.shuffle(shuffled_boss_list)
|
||||||
|
for boss_location in entrance_rando_boss_location_list:
|
||||||
|
boss = next((b for b in shuffled_boss_list if can_place_boss(b, boss_location)), None)
|
||||||
|
shuffled_boss_list.remove(boss)
|
||||||
|
results_dict[boss_location][boss] = results_dict[boss_location][boss] + 1
|
||||||
|
|
||||||
|
# Proposed fix
|
||||||
|
if reshuffle:
|
||||||
|
random.shuffle(shuffled_boss_list)
|
||||||
|
|
||||||
|
def er_unique():
|
||||||
|
for i in range(iterations):
|
||||||
|
temp_full_boss_list = boss_list
|
||||||
|
moldorm2 = random.choice([b for b in boss_list
|
||||||
|
if b not in ["Armos", "Arrghus", "Blind", "Trinexx", "Lanmo"]])
|
||||||
|
lanmo2 = random.choice([b for b in boss_list
|
||||||
|
if b not in [moldorm2, "Blind"]])
|
||||||
|
armos2 = random.choice([b for b in boss_list if b not in [moldorm2, lanmo2]])
|
||||||
|
gt_bosses = [moldorm2, lanmo2, armos2]
|
||||||
|
|
||||||
|
shuffled_boss_list = temp_full_boss_list.copy()
|
||||||
|
for boss_location in entrance_rando_boss_location_list:
|
||||||
|
if '_' in boss_location:
|
||||||
|
boss = random.choice([b for b in gt_bosses if can_place_boss(b, boss_location)])
|
||||||
|
gt_bosses.remove(boss)
|
||||||
|
else:
|
||||||
|
boss = random.choice([b for b in shuffled_boss_list if can_place_boss(b, boss_location)])
|
||||||
|
shuffled_boss_list.remove(boss)
|
||||||
|
results_dict[boss_location][boss] += 1
|
||||||
|
|
||||||
|
def er_unique_chaos():
|
||||||
|
for i in range(iterations):
|
||||||
|
temp_full_boss_list = boss_list
|
||||||
|
|
||||||
|
shuffled_boss_list = temp_full_boss_list.copy()
|
||||||
|
for boss_location in entrance_rando_boss_location_list:
|
||||||
|
if '_' in boss_location:
|
||||||
|
boss = random.choice(b for b in boss_list if can_place_boss(b, boss_location))
|
||||||
|
else:
|
||||||
|
boss = random.choice(b for b in shuffled_boss_list if can_place_boss(b, boss_location))
|
||||||
|
shuffled_boss_list.remove(boss)
|
||||||
|
results_dict[boss_location][boss] += 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
iterations = 100000
|
||||||
|
# reshuffle = False
|
||||||
|
#
|
||||||
|
# vt(reshuffle)
|
||||||
|
#
|
||||||
|
# print("Original results:")
|
||||||
|
#
|
||||||
|
# printresults(iterations)
|
||||||
|
# resetresults()
|
||||||
|
#
|
||||||
|
# reshuffle = True
|
||||||
|
#
|
||||||
|
# vt(reshuffle)
|
||||||
|
#
|
||||||
|
# print("")
|
||||||
|
# print("Reshuffled results:")
|
||||||
|
#
|
||||||
|
# printresults(iterations)
|
||||||
|
# resetresults()
|
||||||
|
#
|
||||||
|
# #ER
|
||||||
|
# reshuffle = False
|
||||||
|
# er(reshuffle)
|
||||||
|
#
|
||||||
|
# print("")
|
||||||
|
# print("ER Original results:")
|
||||||
|
#
|
||||||
|
# printresults(iterations)
|
||||||
|
# resetresults()
|
||||||
|
#
|
||||||
|
# reshuffle = True
|
||||||
|
# er(reshuffle)
|
||||||
|
#
|
||||||
|
# print("")
|
||||||
|
# print("ER Reshuffled results:")
|
||||||
|
#
|
||||||
|
# printresults(iterations)
|
||||||
|
# resetresults()
|
||||||
|
|
||||||
|
er_unique()
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print("ER Unique results:")
|
||||||
|
|
||||||
|
printresults(iterations)
|
||||||
|
resetresults()
|
||||||
|
|
||||||
|
er_unique_chaos()
|
||||||
|
|
||||||
|
print("")
|
||||||
|
print("ER Chaos results:")
|
||||||
|
|
||||||
|
printresults(iterations)
|
||||||
|
|
||||||
|
# Results:
|
||||||
|
#Original results:
|
||||||
|
#Location: GT_top Armos: 0.00% Lanmo: 0.00% Moldorm: 20.00% Helma: 19.89% Arrghus: 0.00% Moth: 20.01% Blind: 0.00% Khold: 20.14% Vitty: 19.96% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||||
|
#Location: GT_mid Armos: 11.05% Lanmo: 11.16% Moldorm: 11.00% Helma: 11.35% Arrghus: 11.07% Moth: 11.00% Blind: 0.00% Khold: 11.01% Vitty: 11.20% Trinexx: 11.15% Restrictive: 44.43% Non-restrictive: 55.57%
|
||||||
|
#Location: TH Armos: 0.00% Lanmo: 0.00% Moldorm: 20.06% Helma: 19.95% Arrghus: 0.00% Moth: 20.07% Blind: 0.00% Khold: 19.88% Vitty: 20.05% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||||
|
#Location: SW Armos: 11.30% Lanmo: 11.04% Moldorm: 11.30% Helma: 10.95% Arrghus: 11.09% Moth: 11.04% Blind: 11.15% Khold: 11.15% Vitty: 10.98% Trinexx: 0.00% Restrictive: 44.59% Non-restrictive: 55.42%
|
||||||
|
#Location: EP Armos: 9.89% Lanmo: 9.90% Moldorm: 10.02% Helma: 9.97% Arrghus: 10.17% Moth: 9.98% Blind: 9.95% Khold: 10.16% Vitty: 9.90% Trinexx: 10.07% Restrictive: 49.97% Non-restrictive: 50.03%
|
||||||
|
#Location: DP Armos: 9.98% Lanmo: 10.09% Moldorm: 9.97% Helma: 9.95% Arrghus: 10.17% Moth: 10.01% Blind: 9.87% Khold: 10.02% Vitty: 9.88% Trinexx: 10.04% Restrictive: 50.16% Non-restrictive: 49.84%
|
||||||
|
#Location: PD Armos: 10.12% Lanmo: 9.96% Moldorm: 9.85% Helma: 9.90% Arrghus: 10.31% Moth: 9.91% Blind: 9.96% Khold: 9.87% Vitty: 9.94% Trinexx: 10.17% Restrictive: 50.52% Non-restrictive: 49.48%
|
||||||
|
#Location: SP Armos: 10.41% Lanmo: 10.35% Moldorm: 9.61% Helma: 9.66% Arrghus: 10.29% Moth: 9.60% Blind: 10.47% Khold: 9.67% Vitty: 9.53% Trinexx: 10.42% Restrictive: 51.93% Non-restrictive: 48.07%
|
||||||
|
#Location: TT Armos: 11.04% Lanmo: 10.98% Moldorm: 8.86% Helma: 8.95% Arrghus: 10.98% Moth: 9.12% Blind: 11.18% Khold: 8.98% Vitty: 9.03% Trinexx: 10.89% Restrictive: 55.06% Non-restrictive: 44.94%
|
||||||
|
#Location: IP Armos: 12.10% Lanmo: 11.89% Moldorm: 7.81% Helma: 7.83% Arrghus: 11.94% Moth: 7.89% Blind: 12.84% Khold: 7.87% Vitty: 7.99% Trinexx: 11.84% Restrictive: 60.62% Non-restrictive: 39.38%
|
||||||
|
#Location: MM Armos: 13.67% Lanmo: 13.66% Moldorm: 6.00% Helma: 6.17% Arrghus: 13.48% Moth: 6.18% Blind: 15.06% Khold: 6.25% Vitty: 6.14% Trinexx: 13.41% Restrictive: 69.28% Non-restrictive: 30.72%
|
||||||
|
#Location: TR Armos: 15.35% Lanmo: 15.49% Moldorm: 3.87% Helma: 3.90% Arrghus: 15.31% Moth: 3.78% Blind: 19.06% Khold: 3.89% Vitty: 3.82% Trinexx: 15.53% Restrictive: 80.74% Non-restrictive: 19.26%
|
||||||
|
#Location: GT_bot Armos: 15.24% Lanmo: 15.24% Moldorm: 1.54% Helma: 1.51% Arrghus: 15.22% Moth: 1.52% Blind: 20.44% Khold: 1.51% Vitty: 1.53% Trinexx: 26.25% Restrictive: 92.38% Non-restrictive: 7.62%
|
||||||
|
#
|
||||||
|
#Reshuffled results:
|
||||||
|
#Location: GT_top Armos: 0.00% Lanmo: 0.00% Moldorm: 20.02% Helma: 19.97% Arrghus: 0.00% Moth: 20.02% Blind: 0.00% Khold: 20.07% Vitty: 19.92% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||||
|
#Location: GT_mid Armos: 12.23% Lanmo: 12.05% Moldorm: 10.23% Helma: 10.24% Arrghus: 12.11% Moth: 10.31% Blind: 0.00% Khold: 10.36% Vitty: 10.33% Trinexx: 12.13% Restrictive: 48.52% Non-restrictive: 51.48%
|
||||||
|
#Location: TH Armos: 0.00% Lanmo: 0.00% Moldorm: 19.86% Helma: 20.09% Arrghus: 0.00% Moth: 20.23% Blind: 0.00% Khold: 19.83% Vitty: 19.99% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||||
|
#Location: SW Armos: 13.23% Lanmo: 13.34% Moldorm: 9.05% Helma: 9.02% Arrghus: 13.37% Moth: 9.10% Blind: 14.97% Khold: 8.98% Vitty: 8.93% Trinexx: 0.00% Restrictive: 54.92% Non-restrictive: 45.08%
|
||||||
|
#Location: EP Armos: 11.54% Lanmo: 11.50% Moldorm: 7.96% Helma: 7.82% Arrghus: 11.76% Moth: 7.81% Blind: 12.84% Khold: 7.79% Vitty: 8.03% Trinexx: 12.95% Restrictive: 60.59% Non-restrictive: 39.41%
|
||||||
|
#Location: DP Armos: 11.55% Lanmo: 11.49% Moldorm: 7.93% Helma: 7.85% Arrghus: 11.72% Moth: 7.69% Blind: 12.80% Khold: 7.99% Vitty: 7.74% Trinexx: 13.25% Restrictive: 60.80% Non-restrictive: 39.20%
|
||||||
|
#Location: PD Armos: 11.79% Lanmo: 11.47% Moldorm: 7.81% Helma: 7.78% Arrghus: 11.61% Moth: 7.91% Blind: 12.81% Khold: 7.78% Vitty: 8.00% Trinexx: 13.02% Restrictive: 60.71% Non-restrictive: 39.29%
|
||||||
|
#Location: SP Armos: 11.59% Lanmo: 11.56% Moldorm: 7.90% Helma: 7.75% Arrghus: 11.52% Moth: 8.02% Blind: 12.67% Khold: 7.92% Vitty: 7.85% Trinexx: 13.23% Restrictive: 60.57% Non-restrictive: 39.43%
|
||||||
|
#Location: TT Armos: 11.64% Lanmo: 11.64% Moldorm: 7.77% Helma: 7.73% Arrghus: 11.70% Moth: 7.67% Blind: 12.89% Khold: 8.05% Vitty: 7.85% Trinexx: 13.07% Restrictive: 60.93% Non-restrictive: 39.07%
|
||||||
|
#Location: IP Armos: 11.54% Lanmo: 11.77% Moldorm: 7.95% Helma: 7.97% Arrghus: 11.49% Moth: 7.80% Blind: 12.64% Khold: 7.87% Vitty: 7.89% Trinexx: 13.10% Restrictive: 60.54% Non-restrictive: 39.47%
|
||||||
|
#Location: MM Armos: 11.69% Lanmo: 11.65% Moldorm: 7.91% Helma: 7.88% Arrghus: 11.59% Moth: 7.78% Blind: 12.80% Khold: 7.73% Vitty: 7.92% Trinexx: 13.04% Restrictive: 60.77% Non-restrictive: 39.23%
|
||||||
|
#Location: TR Armos: 11.58% Lanmo: 11.82% Moldorm: 7.75% Helma: 7.83% Arrghus: 11.62% Moth: 7.79% Blind: 12.76% Khold: 7.90% Vitty: 7.76% Trinexx: 13.19% Restrictive: 60.97% Non-restrictive: 39.03%
|
||||||
|
#Location: GT_bot Armos: 11.54% Lanmo: 11.60% Moldorm: 7.91% Helma: 7.87% Arrghus: 11.62% Moth: 7.90% Blind: 12.78% Khold: 7.82% Vitty: 7.86% Trinexx: 13.10% Restrictive: 60.65% Non-restrictive: 39.35%
|
||||||
|
#
|
||||||
|
#ER Original results:
|
||||||
|
#Location: GT_top Armos: 0.00% Lanmo: 0.00% Moldorm: 19.94% Helma: 19.87% Arrghus: 0.00% Moth: 19.98% Blind: 0.00% Khold: 20.31% Vitty: 19.90% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||||
|
#Location: GT_mid Armos: 14.15% Lanmo: 14.33% Moldorm: 4.51% Helma: 4.53% Arrghus: 14.19% Moth: 4.56% Blind: 0.00% Khold: 4.61% Vitty: 4.49% Trinexx: 34.62% Restrictive: 77.30% Non-restrictive: 22.70%
|
||||||
|
#Location: TH Armos: 0.00% Lanmo: 0.00% Moldorm: 19.93% Helma: 19.98% Arrghus: 0.00% Moth: 20.10% Blind: 0.00% Khold: 19.75% Vitty: 20.24% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||||
|
#Location: SW Armos: 21.52% Lanmo: 21.56% Moldorm: 2.76% Helma: 2.80% Arrghus: 21.50% Moth: 2.82% Blind: 21.48% Khold: 2.81% Vitty: 2.75% Trinexx: 0.00% Restrictive: 86.05% Non-restrictive: 13.95%
|
||||||
|
#Location: EP Armos: 11.39% Lanmo: 11.33% Moldorm: 5.89% Helma: 5.69% Arrghus: 11.32% Moth: 5.83% Blind: 24.71% Khold: 5.82% Vitty: 5.76% Trinexx: 12.27% Restrictive: 71.01% Non-restrictive: 28.99%
|
||||||
|
#Location: DP Armos: 11.62% Lanmo: 11.66% Moldorm: 8.05% Helma: 8.32% Arrghus: 11.75% Moth: 8.13% Blind: 12.46% Khold: 8.10% Vitty: 8.18% Trinexx: 11.73% Restrictive: 59.22% Non-restrictive: 40.78%
|
||||||
|
#Location: PD Armos: 11.01% Lanmo: 10.77% Moldorm: 9.18% Helma: 9.20% Arrghus: 10.89% Moth: 9.03% Blind: 10.83% Khold: 9.13% Vitty: 9.17% Trinexx: 10.79% Restrictive: 54.29% Non-restrictive: 45.71%
|
||||||
|
#Location: SP Armos: 10.26% Lanmo: 10.17% Moldorm: 9.73% Helma: 9.61% Arrghus: 10.33% Moth: 9.69% Blind: 10.31% Khold: 9.84% Vitty: 9.61% Trinexx: 10.45% Restrictive: 51.52% Non-restrictive: 48.48%
|
||||||
|
#Location: TT Armos: 10.12% Lanmo: 10.01% Moldorm: 10.07% Helma: 9.97% Arrghus: 10.16% Moth: 9.92% Blind: 9.95% Khold: 9.73% Vitty: 9.96% Trinexx: 10.11% Restrictive: 50.35% Non-restrictive: 49.65%
|
||||||
|
#Location: IP Armos: 10.03% Lanmo: 10.01% Moldorm: 10.00% Helma: 10.10% Arrghus: 9.89% Moth: 9.93% Blind: 10.13% Khold: 9.99% Vitty: 9.80% Trinexx: 10.13% Restrictive: 50.18% Non-restrictive: 49.82%
|
||||||
|
#Location: MM Armos: 9.89% Lanmo: 9.99% Moldorm: 10.09% Helma: 10.10% Arrghus: 10.02% Moth: 9.93% Blind: 10.03% Khold: 10.00% Vitty: 10.06% Trinexx: 9.89% Restrictive: 49.81% Non-restrictive: 50.19%
|
||||||
|
#Location: TR Armos: 10.03% Lanmo: 10.10% Moldorm: 10.01% Helma: 9.92% Arrghus: 9.94% Moth: 10.01% Blind: 10.04% Khold: 9.95% Vitty: 9.92% Trinexx: 10.08% Restrictive: 50.19% Non-restrictive: 49.81%
|
||||||
|
#Location: GT_bot Armos: 9.90% Lanmo: 10.04% Moldorm: 9.88% Helma: 9.85% Arrghus: 10.00% Moth: 10.04% Blind: 10.09% Khold: 10.09% Vitty: 10.11% Trinexx: 10.01% Restrictive: 50.03% Non-restrictive: 49.97%
|
||||||
|
#
|
||||||
|
#ER Reshuffled results:
|
||||||
|
#Location: GT_top Armos: 0.00% Lanmo: 0.00% Moldorm: 20.11% Helma: 19.81% Arrghus: 0.00% Moth: 20.22% Blind: 0.00% Khold: 20.08% Vitty: 19.78% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||||
|
#Location: GT_mid Armos: 13.17% Lanmo: 13.37% Moldorm: 9.05% Helma: 9.14% Arrghus: 13.23% Moth: 9.29% Blind: 0.00% Khold: 8.96% Vitty: 9.11% Trinexx: 14.69% Restrictive: 54.45% Non-restrictive: 45.55%
|
||||||
|
#Location: TH Armos: 0.00% Lanmo: 0.00% Moldorm: 20.03% Helma: 19.72% Arrghus: 0.00% Moth: 20.17% Blind: 0.00% Khold: 19.86% Vitty: 20.23% Trinexx: 0.00% Restrictive: 0.00% Non-restrictive: 100.00%
|
||||||
|
#Location: SW Armos: 13.38% Lanmo: 13.41% Moldorm: 9.27% Helma: 9.33% Arrghus: 13.41% Moth: 9.27% Blind: 13.48% Khold: 9.28% Vitty: 9.17% Trinexx: 0.00% Restrictive: 53.69% Non-restrictive: 46.31%
|
||||||
|
#Location: EP Armos: 11.50% Lanmo: 11.56% Moldorm: 7.90% Helma: 8.06% Arrghus: 11.58% Moth: 8.11% Blind: 12.72% Khold: 7.93% Vitty: 7.93% Trinexx: 12.70% Restrictive: 60.06% Non-restrictive: 39.94%
|
||||||
|
#Location: DP Armos: 11.66% Lanmo: 11.51% Moldorm: 8.05% Helma: 7.96% Arrghus: 11.43% Moth: 7.83% Blind: 12.94% Khold: 7.89% Vitty: 7.88% Trinexx: 12.87% Restrictive: 60.40% Non-restrictive: 39.60%
|
||||||
|
#Location: PD Armos: 11.36% Lanmo: 11.53% Moldorm: 7.89% Helma: 7.96% Arrghus: 11.46% Moth: 7.72% Blind: 13.12% Khold: 8.07% Vitty: 8.02% Trinexx: 12.87% Restrictive: 60.34% Non-restrictive: 39.66%
|
||||||
|
#Location: SP Armos: 11.27% Lanmo: 11.38% Moldorm: 7.89% Helma: 7.98% Arrghus: 11.56% Moth: 7.89% Blind: 13.10% Khold: 8.17% Vitty: 8.08% Trinexx: 12.68% Restrictive: 59.99% Non-restrictive: 40.01%
|
||||||
|
#Location: TT Armos: 11.32% Lanmo: 11.49% Moldorm: 7.91% Helma: 8.16% Arrghus: 11.44% Moth: 7.97% Blind: 12.93% Khold: 8.05% Vitty: 7.97% Trinexx: 12.77% Restrictive: 59.94% Non-restrictive: 40.06%
|
||||||
|
#Location: IP Armos: 11.69% Lanmo: 11.74% Moldorm: 7.91% Helma: 8.00% Arrghus: 11.34% Moth: 7.87% Blind: 12.80% Khold: 7.82% Vitty: 7.95% Trinexx: 12.88% Restrictive: 60.44% Non-restrictive: 39.56%
|
||||||
|
#Location: MM Armos: 11.57% Lanmo: 11.31% Moldorm: 8.13% Helma: 7.90% Arrghus: 11.32% Moth: 7.91% Blind: 13.15% Khold: 7.97% Vitty: 7.82% Trinexx: 12.91% Restrictive: 60.26% Non-restrictive: 39.74%
|
||||||
|
#Location: TR Armos: 11.44% Lanmo: 11.30% Moldorm: 8.04% Helma: 8.13% Arrghus: 11.47% Moth: 7.90% Blind: 12.82% Khold: 8.09% Vitty: 7.96% Trinexx: 12.84% Restrictive: 59.87% Non-restrictive: 40.13%
|
||||||
|
#Location: GT_bot Armos: 11.36% Lanmo: 11.53% Moldorm: 7.87% Helma: 7.99% Arrghus: 11.49% Moth: 7.96% Blind: 13.01% Khold: 7.98% Vitty: 8.00% Trinexx: 12.80% Restrictive: 60.19% Non-restrictive: 39.81
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
meta:
|
||||||
|
# seed: 872603231
|
||||||
|
seed: 222336029
|
||||||
|
settings:
|
||||||
|
1:
|
||||||
|
door_shuffle: crossed
|
||||||
|
# door_shuffle: vanilla
|
||||||
|
mode: standard
|
||||||
|
shufflebosses: unique
|
||||||
|
doors:
|
||||||
|
1:
|
||||||
|
doors:
|
||||||
|
Hyrule Dungeon Cellblock Up Stairs: Thieves Basement Block Up Stairs
|
||||||
|
bosses:
|
||||||
|
1:
|
||||||
|
Thieves Town: Moldorm
|
||||||
|
#start_inventory:
|
||||||
|
# 1:
|
||||||
|
# - Titans Mitts
|
||||||
|
# - Ocarina
|
||||||
|
# - Moon Pearl
|
||||||
|
# - Tempered Sword
|
||||||
|
# - Boss Heart Container
|
||||||
|
# - Boss Heart Container
|
||||||
|
# - Boss Heart Container
|
||||||
|
# - Boss Heart Container
|
||||||
Reference in New Issue
Block a user