Merge remote-tracking branch 'origin/OverworldShuffle' into OverworldShuffle
This commit is contained in:
354
BaseClasses.py
354
BaseClasses.py
@@ -21,7 +21,7 @@ from source.dungeon.RoomObject import RoomObject
|
||||
class World(object):
|
||||
|
||||
def __init__(self, players, owShuffle, owCrossed, owMixed, shuffle, doorShuffle, logic, mode, swords, difficulty, difficulty_adjustments,
|
||||
timer, progressive, goal, algorithm, accessibility, shuffle_ganon, retro, custom, customitemarray, hints):
|
||||
timer, progressive, goal, algorithm, accessibility, shuffle_ganon, custom, customitemarray, hints):
|
||||
self.players = players
|
||||
self.teams = 1
|
||||
self.owShuffle = owShuffle.copy()
|
||||
@@ -34,6 +34,9 @@ class World(object):
|
||||
self.shuffle = shuffle.copy()
|
||||
self.doorShuffle = doorShuffle.copy()
|
||||
self.intensity = {}
|
||||
self.door_type_mode = {}
|
||||
self.trap_door_mode = {}
|
||||
self.key_logic_algorithm = {}
|
||||
self.logic = logic.copy()
|
||||
self.mode = mode.copy()
|
||||
self.swords = swords.copy()
|
||||
@@ -71,9 +74,6 @@ class World(object):
|
||||
self.fix_trock_exit = {}
|
||||
self.shuffle_ganon = shuffle_ganon
|
||||
self.fix_gtower_exit = self.shuffle_ganon
|
||||
self.retro = retro.copy()
|
||||
self.rupee_bow = retro.copy()
|
||||
self.universal_keys = retro.copy()
|
||||
self.custom = custom
|
||||
self.customitemarray = customitemarray
|
||||
self.can_take_damage = True
|
||||
@@ -107,15 +107,6 @@ class World(object):
|
||||
self.pot_contents = {}
|
||||
|
||||
for player in range(1, players + 1):
|
||||
# If World State is Retro, set to Open and set Retro flag
|
||||
if self.mode[player] == "retro":
|
||||
self.mode[player] = "open"
|
||||
self.retro[player] = True
|
||||
self.rupee_bow[player] = True
|
||||
self.universal_keys[player] = True
|
||||
if self.goal[player] == "z1":
|
||||
self.rupee_bow[player] = True
|
||||
self.universal_keys[player] = True
|
||||
def set_player_attr(attr, val):
|
||||
self.__dict__.setdefault(attr, {})[player] = val
|
||||
set_player_attr('_region_cache', {})
|
||||
@@ -139,13 +130,16 @@ class World(object):
|
||||
set_player_attr('can_access_trock_front', None)
|
||||
set_player_attr('can_access_trock_big_chest', None)
|
||||
set_player_attr('can_access_trock_middle', None)
|
||||
set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic'] or shuffle[player] in ['lean', 'crossed', 'insanity', 'madness_legacy'])
|
||||
set_player_attr('fix_fake_world', logic[player] not in ['owglitches', 'nologic']
|
||||
or shuffle[player] in ['lean', 'crossed', 'insanity'])
|
||||
set_player_attr('mapshuffle', False)
|
||||
set_player_attr('compassshuffle', False)
|
||||
set_player_attr('keyshuffle', False)
|
||||
set_player_attr('keyshuffle', 'none')
|
||||
set_player_attr('bigkeyshuffle', False)
|
||||
set_player_attr('restrict_boss_items', 'none')
|
||||
set_player_attr('bombbag', False)
|
||||
set_player_attr('flute_mode', False)
|
||||
set_player_attr('bow_mode', False)
|
||||
set_player_attr('difficulty_requirements', None)
|
||||
set_player_attr('boss_shuffle', 'none')
|
||||
set_player_attr('enemy_shuffle', 'none')
|
||||
@@ -160,6 +154,7 @@ class World(object):
|
||||
set_player_attr('ganon_item', 'default')
|
||||
set_player_attr('ganon_item_orig', 'default')
|
||||
set_player_attr('open_pyramid', 'auto')
|
||||
set_player_attr('take_any', 'none')
|
||||
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
|
||||
set_player_attr('treasure_hunt_count', 0)
|
||||
set_player_attr('treasure_hunt_total', 0)
|
||||
@@ -169,16 +164,27 @@ class World(object):
|
||||
set_player_attr('collection_rate', False)
|
||||
set_player_attr('colorizepots', False)
|
||||
set_player_attr('pot_pool', {})
|
||||
set_player_attr('decoupledoors', False)
|
||||
set_player_attr('door_type_mode', 'original')
|
||||
set_player_attr('trap_door_mode', 'optional')
|
||||
set_player_attr('key_logic_algorithm', 'default')
|
||||
|
||||
set_player_attr('shopsanity', False)
|
||||
set_player_attr('mixed_travel', 'prevent')
|
||||
set_player_attr('standardize_palettes', 'standardize')
|
||||
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False})
|
||||
set_player_attr('prizes', {'pull': [0, 0, 0], 'crab': [0, 0], 'stun': 0, 'fish': 0})
|
||||
set_player_attr('prizes', {'dig;': [], 'pull': [0, 0, 0], 'crab': [0, 0], 'stun': 0, 'fish': 0, 'enemies': []})
|
||||
|
||||
set_player_attr('exp_cache', defaultdict(dict))
|
||||
set_player_attr('enabled_entrances', {})
|
||||
|
||||
def finish_init(self):
|
||||
for player in range(1, self.players + 1):
|
||||
if self.mode[player] == 'retro':
|
||||
self.mode[player] = 'open'
|
||||
if self.goal[player] == 'completionist':
|
||||
self.accessibility[player] = 'locations'
|
||||
|
||||
def get_name_string_for_object(self, obj):
|
||||
return obj.name if self.players == 1 else f'{obj.name} ({self.get_player_names(obj.player)})'
|
||||
|
||||
@@ -325,6 +331,9 @@ class World(object):
|
||||
def is_tile_swapped(self, owid, player):
|
||||
return (self.mode[player] == 'inverted') != (owid in self.owswaps[player][0] and self.owMixed[player])
|
||||
|
||||
def is_tile_lw_like(self, owid, player):
|
||||
return (owid >= 0x40 and owid < 0x80) == self.is_tile_swapped(owid, player)
|
||||
|
||||
def is_atgt_swapped(self, player):
|
||||
return self.is_tile_swapped(0x03, player) and self.is_tile_swapped(0x1b, player)
|
||||
|
||||
@@ -488,7 +497,8 @@ class World(object):
|
||||
|
||||
def push_precollected(self, item):
|
||||
item.world = self
|
||||
if (item.smallkey and self.keyshuffle[item.player]) or (item.bigkey and self.bigkeyshuffle[item.player]):
|
||||
if ((item.smallkey and self.keyshuffle[item.player] != 'none')
|
||||
or (item.bigkey and self.bigkeyshuffle[item.player])):
|
||||
item.advancement = True
|
||||
self.precollected_items.append(item)
|
||||
self.state.collect(item, True)
|
||||
@@ -577,7 +587,10 @@ class World(object):
|
||||
if self.has_beaten_game(state):
|
||||
return True
|
||||
|
||||
prog_locations = [location for location in self.get_locations() if location.item is not None and (location.item.advancement or location.event) and location not in state.locations_checked]
|
||||
prog_locations = [location for location in self.get_locations() if location.item is not None
|
||||
and (location.item.advancement or location.event
|
||||
or self.goal[location.player] == 'completionist')
|
||||
and location not in state.locations_checked]
|
||||
|
||||
while prog_locations:
|
||||
sphere = []
|
||||
@@ -609,6 +622,7 @@ class CollectionState(object):
|
||||
self.world = parent
|
||||
if not skip_init:
|
||||
self.prog_items = Counter()
|
||||
self.forced_keys = Counter()
|
||||
self.reachable_regions = {player: dict() for player in range(1, parent.players + 1)}
|
||||
self.blocked_connections = {player: dict() for player in range(1, parent.players + 1)}
|
||||
self.events = []
|
||||
@@ -623,6 +637,7 @@ class CollectionState(object):
|
||||
self.opened_doors = {player: set() for player in range(1, parent.players + 1)}
|
||||
self.dungeons_to_check = {player: defaultdict(dict) for player in range(1, parent.players + 1)}
|
||||
self.dungeon_limits = None
|
||||
self.placing_item = None
|
||||
# self.trace = None
|
||||
|
||||
def update_reachable_regions(self, player):
|
||||
@@ -640,12 +655,13 @@ class CollectionState(object):
|
||||
queue = deque(self.blocked_connections[player].items())
|
||||
|
||||
self.traverse_world(queue, rrp, bc, player)
|
||||
unresolved_events = [x for y in self.reachable_regions[player] for x in y.locations
|
||||
if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement)
|
||||
and x not in self.locations_checked and x.can_reach(self)]
|
||||
unresolved_events = self._do_not_flood_the_keys(unresolved_events)
|
||||
if len(unresolved_events) == 0:
|
||||
self.check_key_doors_in_dungeons(rrp, player)
|
||||
if self.world.key_logic_algorithm[player] == 'default':
|
||||
unresolved_events = [x for y in self.reachable_regions[player] for x in y.locations
|
||||
if x.event and x.item and (x.item.smallkey or x.item.bigkey or x.item.advancement)
|
||||
and x not in self.locations_checked and x.can_reach(self)]
|
||||
unresolved_events = self._do_not_flood_the_keys(unresolved_events)
|
||||
if len(unresolved_events) == 0:
|
||||
self.check_key_doors_in_dungeons(rrp, player)
|
||||
|
||||
def traverse_world(self, queue, rrp, bc, player):
|
||||
# run BFS on all connections, and keep track of those blocked by missing items
|
||||
@@ -681,15 +697,6 @@ class CollectionState(object):
|
||||
queue.append((conn, new_crystal_state))
|
||||
|
||||
self.path[new_region] = (new_region.name, self.path.get(connection, None))
|
||||
|
||||
# Retry connections if the new region can unblock them
|
||||
from EntranceShuffle import indirect_connections
|
||||
if new_region.name in indirect_connections:
|
||||
new_entrance = self.world.get_entrance(indirect_connections[new_region.name], player)
|
||||
if new_entrance in bc and new_entrance.parent_region in rrp:
|
||||
new_crystal_state = rrp[new_entrance.parent_region]
|
||||
if (new_entrance, new_crystal_state) not in queue:
|
||||
queue.append((new_entrance, new_crystal_state))
|
||||
# else those connections that are not accessible yet
|
||||
if self.is_small_door(connection):
|
||||
door = connection.door if connection.door.smallKey else connection.door.controller
|
||||
@@ -701,7 +708,7 @@ class CollectionState(object):
|
||||
if key_logic.sm_doors[door]:
|
||||
self.reached_doors[player].add(key_logic.sm_doors[door].name)
|
||||
if not connection.can_reach(self):
|
||||
checklist_key = 'Universal' if self.world.universal_keys[player] else dungeon_name
|
||||
checklist_key = 'Universal' if self.world.keyshuffle[player] == 'universal' else dungeon_name
|
||||
checklist = self.dungeons_to_check[player][checklist_key]
|
||||
checklist[connection.name] = (connection, crystal_state)
|
||||
elif door.name not in self.opened_doors[player]:
|
||||
@@ -738,6 +745,7 @@ class CollectionState(object):
|
||||
|
||||
def check_key_doors_in_dungeons(self, rrp, player):
|
||||
for dungeon_name, checklist in self.dungeons_to_check[player].items():
|
||||
# todo: optimization idea - abort exploration if there are unresolved events now
|
||||
if self.apply_dungeon_exploration(rrp, player, dungeon_name, checklist):
|
||||
continue
|
||||
init_door_candidates = self.should_explore_child_state(self, dungeon_name, player)
|
||||
@@ -846,6 +854,15 @@ class CollectionState(object):
|
||||
rrp[k] = missing_regions[k]
|
||||
possible_path = terminal_states[0].path[k]
|
||||
self.path[k] = paths[k] = possible_path
|
||||
for conn in k.exits:
|
||||
if self.is_small_door(conn):
|
||||
door = conn.door if conn.door.smallKey else conn.door.controller
|
||||
key_logic = self.world.key_logic[player][dungeon_name]
|
||||
if door.name not in self.reached_doors[player]:
|
||||
self.door_counter[player][0][dungeon_name] += 1
|
||||
self.reached_doors[player].add(door.name)
|
||||
if key_logic.sm_doors[door]:
|
||||
self.reached_doors[player].add(key_logic.sm_doors[door].name)
|
||||
missing_bc = {}
|
||||
for blocked, crystal in common_bc.items():
|
||||
if (blocked not in bc and blocked.parent_region in rrp
|
||||
@@ -873,7 +890,7 @@ class CollectionState(object):
|
||||
return None
|
||||
|
||||
def set_dungeon_limits(self, player, dungeon_name):
|
||||
if self.world.retro[player] and self.world.mode[player] == 'standard':
|
||||
if self.world.keyshuffle[player] == 'universal' and self.world.mode[player] == 'standard':
|
||||
self.dungeon_limits = ['Hyrule Castle', 'Agahnims Tower']
|
||||
else:
|
||||
self.dungeon_limits = [dungeon_name]
|
||||
@@ -897,14 +914,16 @@ class CollectionState(object):
|
||||
door_candidates.append(door.name)
|
||||
return door_candidates
|
||||
door_candidates, skip = [], set()
|
||||
if state.world.accessibility[player] != 'locations' and remaining_keys == 0 and dungeon_name in state.world.key_logic[player]:
|
||||
if (state.world.accessibility[player] != 'locations' and remaining_keys == 0 and dungeon_name != 'Universal'
|
||||
and state.placing_item and state.placing_item.name == small_key_name):
|
||||
key_logic = state.world.key_logic[player][dungeon_name]
|
||||
for door, paired in key_logic.sm_doors.items():
|
||||
if door.name in key_logic.door_rules:
|
||||
rule = key_logic.door_rules[door.name]
|
||||
key = KeyRuleType.AllowSmall
|
||||
if (key in rule.new_rules and key_total >= rule.new_rules[key] and door.name not in skip
|
||||
and door.name in state.reached_doors[player] and door.name not in state.opened_doors[player]):
|
||||
and door.name in state.reached_doors[player] and door.name not in state.opened_doors[player]
|
||||
and rule.small_location.item is None):
|
||||
if paired:
|
||||
door_candidates.append((door.name, paired.name))
|
||||
skip.add(paired.name)
|
||||
@@ -926,6 +945,7 @@ class CollectionState(object):
|
||||
def copy(self):
|
||||
ret = CollectionState(self.world, skip_init=True)
|
||||
ret.prog_items = self.prog_items.copy()
|
||||
ret.forced_keys = self.forced_keys.copy()
|
||||
ret.reachable_regions = {player: copy.copy(self.reachable_regions[player]) for player in range(1, self.world.players + 1)}
|
||||
ret.blocked_connections = {player: copy.copy(self.blocked_connections[player]) for player in range(1, self.world.players + 1)}
|
||||
ret.events = copy.copy(self.events)
|
||||
@@ -940,6 +960,7 @@ class CollectionState(object):
|
||||
player: defaultdict(dict, {name: copy.copy(checklist)
|
||||
for name, checklist in self.dungeons_to_check[player].items()})
|
||||
for player in range(1, self.world.players + 1)}
|
||||
ret.placing_item = self.placing_item
|
||||
return ret
|
||||
|
||||
def apply_dungeon_exploration(self, rrp, player, dungeon_name, checklist):
|
||||
@@ -1023,7 +1044,7 @@ class CollectionState(object):
|
||||
'Golden Sword', 'Progressive Sword', 'Progressive Glove', 'Silver Arrows', 'Green Pendant',
|
||||
'Blue Pendant', 'Red Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5',
|
||||
'Crystal 6', 'Crystal 7', 'Blue Boomerang', 'Red Boomerang', 'Blue Shield', 'Red Shield',
|
||||
'Mirror Shield', 'Progressive Shield', 'Bug Catching Net', 'Cane of Byrna', 'Ocarina (Activated)'
|
||||
'Mirror Shield', 'Progressive Shield', 'Bug Catching Net', 'Cane of Byrna', 'Ocarina (Activated)',
|
||||
'Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart', 'Magic Upgrade (1/2)',
|
||||
'Magic Upgrade (1/4)']
|
||||
or item_name.startswith(('Bottle', 'Small Key', 'Big Key'))
|
||||
@@ -1064,7 +1085,7 @@ class CollectionState(object):
|
||||
new_locations = True
|
||||
while new_locations:
|
||||
reachable_events = [location for location in locations if location.event and
|
||||
(not key_only or (not self.world.keyshuffle[location.item.player] and location.item.smallkey) or (not self.world.bigkeyshuffle[location.item.player] and location.item.bigkey))
|
||||
(not key_only or (self.world.keyshuffle[location.item.player] == 'none' and location.item.smallkey) or (not self.world.bigkeyshuffle[location.item.player] and location.item.bigkey))
|
||||
and location.can_reach(self)]
|
||||
reachable_events = self._do_not_flood_the_keys(reachable_events)
|
||||
new_locations = False
|
||||
@@ -1124,7 +1145,7 @@ class CollectionState(object):
|
||||
return self.prog_items[item, player] >= count
|
||||
|
||||
def has_sm_key(self, item, player, count=1):
|
||||
if self.world.universal_keys[player]:
|
||||
if self.world.keyshuffle[player] == 'universal':
|
||||
if self.world.mode[player] == 'standard' and self.world.doorShuffle[player] == 'vanilla' and item == 'Small Key (Escape)':
|
||||
return True # Cannot access the shop until escape is finished. This is safe because the key is manually placed in make_custom_item_pool
|
||||
return self.can_buy_unlimited('Small Key (Universal)', player)
|
||||
@@ -1132,6 +1153,14 @@ class CollectionState(object):
|
||||
return (item, player) in self.prog_items
|
||||
return self.prog_items[item, player] >= count
|
||||
|
||||
def has_sm_key_strict(self, item, player, count=1):
|
||||
if self.world.keyshuffle[player] == 'universal':
|
||||
if self.world.mode[player] == 'standard' and self.world.doorShuffle[player] == 'vanilla' and item == 'Small Key (Escape)':
|
||||
return True # Cannot access the shop until escape is finished. This is safe because the key is manually placed in make_custom_item_pool
|
||||
return self.can_buy_unlimited('Small Key (Universal)', player)
|
||||
obtained = self.prog_items[item, player] - self.forced_keys[item, player]
|
||||
return obtained >= count
|
||||
|
||||
def can_buy_unlimited(self, item, player):
|
||||
for shop in self.world.shops[player]:
|
||||
if shop.region.player == player and shop.has_unlimited(item) and shop.region.can_reach(self):
|
||||
@@ -1164,6 +1193,12 @@ class CollectionState(object):
|
||||
def item_count(self, item, player):
|
||||
return self.prog_items[item, player]
|
||||
|
||||
def everything(self, player):
|
||||
all_locations = self.world.get_filled_locations(player)
|
||||
all_locations.remove(self.world.get_location('Ganon', player))
|
||||
return (len([x for x in self.locations_checked if x.player == player])
|
||||
>= len(all_locations))
|
||||
|
||||
def has_crystals(self, count, player):
|
||||
crystals = ['Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 5', 'Crystal 6', 'Crystal 7']
|
||||
return len([crystal for crystal in crystals if self.has(crystal, player)]) >= count
|
||||
@@ -1260,7 +1295,7 @@ class CollectionState(object):
|
||||
or self.has('Cane of Somaria', player))
|
||||
|
||||
def can_shoot_arrows(self, player):
|
||||
if self.world.rupee_bow[player]:
|
||||
if self.world.bow_mode[player] in ['retro', 'retro_silvers']:
|
||||
#todo: Non-progressive silvers grant wooden arrows, but progressive bows do not. Always require shop arrows to be safe
|
||||
return self.has('Bow', player) and (self.can_buy_unlimited('Single Arrow', player) or self.has('Single Arrow', player))
|
||||
return self.has('Bow', player)
|
||||
@@ -1391,11 +1426,12 @@ class CollectionState(object):
|
||||
|
||||
def can_flute(self, player):
|
||||
if self.world.mode[player] == 'standard' and not self.has('Zelda Delivered', player):
|
||||
return False
|
||||
return False # can't flute in rain state
|
||||
if any(map(lambda i: i.name in ['Ocarina', 'Ocarina (Activated)'], self.world.precollected_items)):
|
||||
return True
|
||||
lw = self.world.get_region('Kakariko Area', player)
|
||||
return self.has('Ocarina (Activated)', player) or (self.has('Ocarina', player) and lw.can_reach(self) and self.is_not_bunny(lw, player))
|
||||
return self.has('Ocarina (Activated)', player) or (self.has('Ocarina', player) and lw.can_reach(self)
|
||||
and self.is_not_bunny(lw, player))
|
||||
|
||||
def can_melt_things(self, player):
|
||||
return self.has('Fire Rod', player) or (self.has('Bombos', player) and self.can_use_medallions(player))
|
||||
@@ -1450,6 +1486,8 @@ class CollectionState(object):
|
||||
def collect(self, item, event=False, location=None):
|
||||
if location:
|
||||
self.locations_checked.add(location)
|
||||
if item and item.smallkey and location.forced_item is not None:
|
||||
self.forced_keys[item.name, item.player] += 1
|
||||
if not item:
|
||||
return
|
||||
changed = False
|
||||
@@ -1715,14 +1753,14 @@ class Region(object):
|
||||
return False
|
||||
|
||||
def can_fill(self, item):
|
||||
inside_dungeon_item = ((item.smallkey and not self.world.keyshuffle[item.player])
|
||||
inside_dungeon_item = ((item.smallkey and self.world.keyshuffle[item.player] == 'none')
|
||||
or (item.bigkey and not self.world.bigkeyshuffle[item.player])
|
||||
or (item.map and not self.world.mapshuffle[item.player])
|
||||
or (item.compass and not self.world.compassshuffle[item.player]))
|
||||
sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Escape)'
|
||||
if sewer_hack or inside_dungeon_item:
|
||||
# not all small keys to escape must be in escape
|
||||
# sewer_hack = self.world.mode[item.player] == 'standard' and item.name == 'Small Key (Escape)'
|
||||
if inside_dungeon_item:
|
||||
return self.dungeon and self.dungeon.is_dungeon_item(item) and item.player == self.player
|
||||
|
||||
return True
|
||||
|
||||
def can_cause_bunny(self, player):
|
||||
@@ -1776,7 +1814,13 @@ class Entrance(object):
|
||||
while len(self.temp_path):
|
||||
exit = self.temp_path.pop(0)
|
||||
path = (exit.name, (exit.parent_region.name, path))
|
||||
item_name = self.connected_region.locations[0].item.name if self.connected_region.locations[0].item else 'Deliver Item'
|
||||
item_name = 'Deliver Item'
|
||||
if len(self.connected_region.locations) > 0 and self.connected_region.locations[0].item:
|
||||
item_name = self.connected_region.locations[0].item.name
|
||||
for loc in self.parent_region.locations:
|
||||
if loc.event and not loc.real and loc.item and loc.item.name.find('Farmable') < 0:
|
||||
item_name = loc.item.name
|
||||
break
|
||||
path = (item_name, (self.parent_region.name, path))
|
||||
state.path[self] = (self.name, path)
|
||||
return True
|
||||
@@ -1821,45 +1865,39 @@ class Entrance(object):
|
||||
exits_to_traverse = list()
|
||||
found = False
|
||||
|
||||
if not found and allow_mirror_reentry and state.has('Magic Mirror', self.player):
|
||||
if not found and allow_mirror_reentry and state.has_Mirror(self.player):
|
||||
# check for path using mirror portal re-entry at location of the follower pickup
|
||||
# this is checked first as this often the shortest path
|
||||
follower_region = start_region
|
||||
if follower_region.type not in [RegionType.LightWorld, RegionType.DarkWorld]:
|
||||
follower_region = [i for i in start_region.entrances if i.parent_region.name != 'Menu'][0].parent_region
|
||||
if (follower_region.world.mode[self.player] != 'inverted') == (follower_region.type == RegionType.LightWorld):
|
||||
from OWEdges import OWTileRegions
|
||||
from OverworldShuffle import ow_connections
|
||||
owid = OWTileRegions[follower_region.name]
|
||||
(mirror_map_orig, other_world) = ow_connections[owid % 0x40]
|
||||
mirror_map = list(mirror_map_orig).copy()
|
||||
mirror_map.extend(other_world)
|
||||
mirror_exit = None
|
||||
while len(mirror_map):
|
||||
from OverworldShuffle import get_mirror_edges
|
||||
mirror_map = get_mirror_edges(follower_region.world, follower_region, self.player)
|
||||
while len(mirror_map) and not found:
|
||||
if mirror_map[0][1] == follower_region.name:
|
||||
mirror_exit = mirror_map[0][0]
|
||||
break
|
||||
mirror_region = follower_region.world.get_entrance(mirror_exit, self.player).parent_region
|
||||
if mirror_exit and mirror_region:
|
||||
if mirror_region.can_reach(state):
|
||||
traverse_paths(mirror_region, self.parent_region)
|
||||
break # no need to continue if there is no path from the mirror re-entry to dest
|
||||
mirror_map.pop(0)
|
||||
if mirror_exit:
|
||||
mirror_region = follower_region.world.get_entrance(mirror_exit, self.player).parent_region
|
||||
if mirror_region.can_reach(state):
|
||||
traverse_paths(mirror_region, self.parent_region)
|
||||
if found:
|
||||
path = state.path.get(mirror_region, (mirror_region.name, None))
|
||||
path = (follower_region.name, (mirror_exit, path))
|
||||
item_name = step_location.item.name if step_location.item else 'Pick Up Item'
|
||||
if start_region.name != follower_region.name:
|
||||
path = (start_region.name, (start_region.entrances[0].name, path))
|
||||
path = (f'{step_location.parent_region.name} Exit', ('Leave Item Area', (item_name, path)))
|
||||
else:
|
||||
path = (item_name, path)
|
||||
path = ('Use Mirror Portal', (follower_region.name, path))
|
||||
while len(self.temp_path):
|
||||
exit = self.temp_path.pop(0)
|
||||
path = (exit.name, (exit.parent_region.name, path))
|
||||
item_name = self.connected_region.locations[0].item.name if self.connected_region.locations[0].item else 'Deliver Item'
|
||||
path = (self.parent_region.name, path)
|
||||
state.path[self] = (self.name, path)
|
||||
if found:
|
||||
path = state.path.get(mirror_region, (mirror_region.name, None))
|
||||
path = (follower_region.name, (mirror_exit, path))
|
||||
item_name = step_location.item.name if step_location.item else 'Pick Up Item'
|
||||
if start_region.name != follower_region.name:
|
||||
path = (start_region.name, (start_region.entrances[0].name, path))
|
||||
path = (f'{step_location.parent_region.name} Exit', ('Leave Item Area', (item_name, path)))
|
||||
else:
|
||||
path = (item_name, path)
|
||||
path = ('Use Mirror Portal', (follower_region.name, path))
|
||||
while len(self.temp_path):
|
||||
exit = self.temp_path.pop(0)
|
||||
path = (exit.name, (exit.parent_region.name, path))
|
||||
path = (self.parent_region.name, path)
|
||||
state.path[self] = (self.name, path)
|
||||
|
||||
if not found:
|
||||
# check normal paths
|
||||
@@ -1870,7 +1908,7 @@ class Entrance(object):
|
||||
exit = self.parent_region.world.get_entrance('Links House S&Q', self.player)
|
||||
traverse_paths(exit.connected_region, self.parent_region, [exit])
|
||||
|
||||
if not found and allow_mirror_reentry and state.has('Magic Mirror', self.player):
|
||||
if not found and allow_mirror_reentry and state.has_Mirror(self.player):
|
||||
# check for paths using mirror portal re-entry at location of final destination
|
||||
# this is checked last as this is the most complicated/exhaustive check
|
||||
follower_region = start_region
|
||||
@@ -1881,14 +1919,9 @@ class Entrance(object):
|
||||
if dest_region.type not in [RegionType.LightWorld, RegionType.DarkWorld]:
|
||||
dest_region = start_region.entrances[0].parent_region
|
||||
if (dest_region.world.mode[self.player] != 'inverted') != (dest_region.type == RegionType.LightWorld):
|
||||
from OWEdges import OWTileRegions
|
||||
from OverworldShuffle import ow_connections
|
||||
owid = OWTileRegions[dest_region.name]
|
||||
(mirror_map_orig, other_world) = ow_connections.copy()[owid % 0x40]
|
||||
mirror_map = list(mirror_map_orig).copy()
|
||||
mirror_map.extend(other_world)
|
||||
mirror_map = [(x, d) for (x, d) in mirror_map if x in [e.name for e in dest_region.exits]]
|
||||
# loop thru potential places to leave a mirror portal
|
||||
from OverworldShuffle import get_mirror_edges
|
||||
mirror_map = get_mirror_edges(dest_region.world, dest_region, self.player)
|
||||
while len(mirror_map) and not found:
|
||||
mirror_exit = dest_region.world.get_entrance(mirror_map[0][0], self.player)
|
||||
if mirror_exit.connected_region.type != dest_region.type:
|
||||
@@ -2359,6 +2392,9 @@ class Door(object):
|
||||
return world.get_room(self.roomIndex, self.player).kind(self)
|
||||
return None
|
||||
|
||||
def dungeon_name(self):
|
||||
return self.entrance.parent_region.dungeon.name if self.entrance.parent_region.dungeon else 'Cave'
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__) and self.name == other.name
|
||||
|
||||
@@ -2495,7 +2531,6 @@ class Sector(object):
|
||||
self.item_logic = set()
|
||||
self.chest_location_set = set()
|
||||
|
||||
|
||||
def region_set(self):
|
||||
if self.r_name_set is None:
|
||||
self.r_name_set = dict.fromkeys(map(lambda r: r.name, self.regions))
|
||||
@@ -2777,7 +2812,7 @@ class Location(object):
|
||||
def gen_name(self):
|
||||
name = self.name
|
||||
world = self.parent_region.world if self.parent_region and self.parent_region.world else None
|
||||
if self.parent_region.dungeon and world and world.doorShuffle[self.player] == 'crossed':
|
||||
if self.parent_region.dungeon and world and world.doorShuffle[self.player] not in ['basic', 'vanilla']:
|
||||
name += f' @ {self.parent_region.dungeon.name}'
|
||||
if world and world.players > 1:
|
||||
name += f' ({world.get_player_names(self.player)})'
|
||||
@@ -2862,7 +2897,7 @@ class Item(object):
|
||||
return item_dungeon
|
||||
|
||||
def is_inside_dungeon_item(self, world):
|
||||
return ((self.smallkey and not world.keyshuffle[self.player])
|
||||
return ((self.smallkey and world.keyshuffle[self.player] == 'none')
|
||||
or (self.bigkey and not world.bigkeyshuffle[self.player])
|
||||
or (self.compass and not world.compassshuffle[self.player])
|
||||
or (self.map and not world.mapshuffle[self.player]))
|
||||
@@ -2873,6 +2908,11 @@ class Item(object):
|
||||
def __unicode__(self):
|
||||
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
||||
|
||||
def __eq__(self, other):
|
||||
if type(other) is str:
|
||||
return self.name == other
|
||||
return self.name == other.name and self.player == other.player
|
||||
|
||||
|
||||
# have 6 address that need to be filled
|
||||
class Crystal(Item):
|
||||
@@ -3013,9 +3053,10 @@ class Spoiler(object):
|
||||
'versions': {'Door':ERVersion, 'Overworld':ORVersion},
|
||||
'logic': self.world.logic,
|
||||
'mode': self.world.mode,
|
||||
'retro': self.world.retro,
|
||||
'bombbag': self.world.bombbag,
|
||||
'weapons': self.world.swords,
|
||||
'flute_mode': self.world.flute_mode,
|
||||
'bow_mode': self.world.bow_mode,
|
||||
'goal': self.world.goal,
|
||||
'ow_shuffle': self.world.owShuffle,
|
||||
'ow_terrain': self.world.owTerrain,
|
||||
@@ -3028,9 +3069,15 @@ class Spoiler(object):
|
||||
'shuffle': self.world.shuffle,
|
||||
'shuffleganon': self.world.shuffle_ganon,
|
||||
'shufflelinks': self.world.shufflelinks,
|
||||
'shuffletavern': self.world.shuffletavern,
|
||||
'take_any': self.world.take_any,
|
||||
'overworld_map': self.world.overworld_map,
|
||||
'door_shuffle': self.world.doorShuffle,
|
||||
'intensity': self.world.intensity,
|
||||
'door_type_mode': self.world.door_type_mode,
|
||||
'trap_door_mode': self.world.trap_door_mode,
|
||||
'key_logic': self.world.key_logic_algorithm,
|
||||
'decoupledoors': self.world.decoupledoors,
|
||||
'dungeon_counters': self.world.dungeon_counters,
|
||||
'item_pool': self.world.difficulty,
|
||||
'item_functionality': self.world.difficulty_adjustments,
|
||||
@@ -3216,10 +3263,9 @@ class Spoiler(object):
|
||||
outfile.write('Settings Code:'.ljust(line_width) + '%s\n' % self.metadata["code"][player])
|
||||
outfile.write('Logic:'.ljust(line_width) + '%s\n' % self.metadata['logic'][player])
|
||||
outfile.write('Mode:'.ljust(line_width) + '%s\n' % self.metadata['mode'][player])
|
||||
outfile.write('Retro:'.ljust(line_width) + '%s\n' % yn(self.metadata['retro'][player]))
|
||||
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
|
||||
outfile.write('Goal:'.ljust(line_width) + '%s\n' % self.metadata['goal'][player])
|
||||
if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'z1']:
|
||||
if self.metadata['goal'][player] in ['triforcehunt', 'trinity', 'z1', 'ganonhunt']:
|
||||
outfile.write('Triforce Pieces Required:'.ljust(line_width) + '%s\n' % self.metadata['triforcegoal'][player])
|
||||
outfile.write('Triforce Pieces Total:'.ljust(line_width) + '%s\n' % self.metadata['triforcepool'][player])
|
||||
outfile.write('Crystals Required for GT:'.ljust(line_width) + '%s\n' % str(self.world.crystals_gt_orig[player]))
|
||||
@@ -3229,6 +3275,9 @@ class Spoiler(object):
|
||||
outfile.write('Restricted Boss Items:'.ljust(line_width) + '%s\n' % self.metadata['restricted_boss_items'][player])
|
||||
outfile.write('Difficulty:'.ljust(line_width) + '%s\n' % self.metadata['item_pool'][player])
|
||||
outfile.write('Item Functionality:'.ljust(line_width) + '%s\n' % self.metadata['item_functionality'][player])
|
||||
outfile.write('Flute Mode:'.ljust(line_width) + '%s\n' % self.metadata['flute_mode'][player])
|
||||
outfile.write('Bow Mode:'.ljust(line_width) + '%s\n' % self.metadata['bow_mode'][player])
|
||||
outfile.write('Take Any Caves:'.ljust(line_width) + '%s\n' % self.metadata['take_any'][player])
|
||||
outfile.write('Shopsanity:'.ljust(line_width) + '%s\n' % yn(self.metadata['shopsanity'][player]))
|
||||
outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % yn(self.metadata['bombbag'][player]))
|
||||
outfile.write('Pseudoboots:'.ljust(line_width) + '%s\n' % yn(self.metadata['pseudoboots'][player]))
|
||||
@@ -3246,12 +3295,17 @@ class Spoiler(object):
|
||||
if self.metadata['shuffle'][player] != 'vanilla':
|
||||
outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffleganon'][player]))
|
||||
outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % yn(self.metadata['shufflelinks'][player]))
|
||||
outfile.write('Shuffle Tavern:'.ljust(line_width) + '%s\n' % yn(self.metadata['shuffletavern'][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('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])
|
||||
if self.metadata['door_shuffle'][player] != 'vanilla':
|
||||
outfile.write('Intensity:'.ljust(line_width) + '%s\n' % self.metadata['intensity'][player])
|
||||
outfile.write('Door Type Mode:'.ljust(line_width) + '%s\n' % self.metadata['door_type_mode'][player])
|
||||
outfile.write('Trap Door Mode:'.ljust(line_width) + '%s\n' % self.metadata['trap_door_mode'][player])
|
||||
outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player])
|
||||
outfile.write('Decouple Doors:'.ljust(line_width) + '%s\n' % yn(self.metadata['decoupledoors'][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('Enemy Drop Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['dropshuffle'][player]))
|
||||
@@ -3259,13 +3313,14 @@ class Spoiler(object):
|
||||
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('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' % self.metadata['keyshuffle'][player])
|
||||
outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['bigkeyshuffle'][player]))
|
||||
outfile.write('Boss Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['boss_shuffle'][player])
|
||||
outfile.write('Enemy Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['enemy_shuffle'][player])
|
||||
outfile.write('Enemy Health:'.ljust(line_width) + '%s\n' % self.metadata['enemy_health'][player])
|
||||
outfile.write('Enemy Damage:'.ljust(line_width) + '%s\n' % self.metadata['enemy_damage'][player])
|
||||
outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player]))
|
||||
outfile.write('Race:'.ljust(line_width) + '%s\n' % yn(self.world.settings.world_rep['meta']['race']))
|
||||
|
||||
if self.startinventory:
|
||||
outfile.write('Starting Inventory:'.ljust(line_width))
|
||||
@@ -3532,10 +3587,10 @@ class Pot(object):
|
||||
return hash((self.x, self.y, self.room))
|
||||
|
||||
|
||||
# byte 0: DDOO OEEE (DR, OR, ER)
|
||||
dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0}
|
||||
or_mode = {"vanilla": 0, "parallel": 1, "full": 1}
|
||||
er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "lite": 4, "lean": 5, "crossed": 6, "insanity": 7, "dungeonsfull": 8, "dungeonssimple": 9}
|
||||
# byte 0: DDDE EEEE (DR, ER)
|
||||
dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3}
|
||||
er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8,
|
||||
'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6}
|
||||
|
||||
# byte 1: LLLW WSSS (logic, mode, sword)
|
||||
logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4}
|
||||
@@ -3543,17 +3598,18 @@ world_mode = {"open": 0, "standard": 1, "inverted": 2}
|
||||
sword_mode = {"random": 0, "assured": 1, "swordless": 2, "swordless_hammer": 2, "vanilla": 3, "bombs": 4, "pseudo": 5, "assured_pseudo": 5, "byrna": 6, "somaria": 6, "cane": 6, "bees": 7}
|
||||
|
||||
# byte 2: GGGD DFFH (goal, diff, item_func, hints)
|
||||
goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "crystals": 4, "trinity": 5, "z1": 6}
|
||||
goal_mode = {'ganon': 0, 'pedestal': 1, 'dungeons': 2, 'triforcehunt': 3, 'crystals': 4, 'trinity': 5, 'z1': 6,
|
||||
'ganonhunt': 6, 'completionist': 7}
|
||||
diff_mode = {"normal": 0, "hard": 1, "expert": 2}
|
||||
func_mode = {"normal": 0, "hard": 1, "expert": 2}
|
||||
|
||||
# byte 3: S?MM PIII (shop, unused, mixed, palettes, intensity)
|
||||
# byte 3: SDMM PIII (shop, decouple doors, mixed, palettes, intensity)
|
||||
# keydrop now has it's own byte
|
||||
mixed_travel_mode = {"prevent": 0, "allow": 1, "force": 2}
|
||||
# intensity is 3 bits (reserves 4-7 levels)
|
||||
|
||||
# new byte 4: ?DDD PPPP (unused, drop, pottery)
|
||||
# dropshuffle reserves 2 bits, pottery needs 2 but reserves 2 for future modes)
|
||||
# new byte 4: TDDD PPPP (tavern shuffle, drop, pottery)
|
||||
# dropshuffle reserves 2 bits, pottery needs 4)
|
||||
pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7,
|
||||
'clustered': 8, 'nonempty': 9}
|
||||
|
||||
@@ -3563,7 +3619,8 @@ counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
|
||||
# byte 6: CCCC CPAA (crystals ganon, pyramid, access
|
||||
access_mode = {"items": 0, "locations": 1, "none": 2}
|
||||
|
||||
# byte 7: BSMC ??EE (big, small, maps, compass, bosses, enemies)
|
||||
# byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies)
|
||||
door_type_mode = {'original': 0, 'big': 1, 'all': 2, 'chaos': 3}
|
||||
enemy_mode = {"none": 0, "shuffled": 1, "chaos": 2, "random": 2, "legacy": 3}
|
||||
|
||||
# byte 8: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links)
|
||||
@@ -3577,12 +3634,31 @@ rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2}
|
||||
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}
|
||||
|
||||
# byte 10: settings_version
|
||||
|
||||
# byte 11: OOOT WCCC (OWR layout, free terrain, whirlpools, OWR crossed)
|
||||
or_mode = {"vanilla": 0, "parallel": 1, "full": 2}
|
||||
orcrossed_mode = {"none": 0, "polar": 1, "grouped": 2, "limited": 3, "chaos": 4}
|
||||
|
||||
# byte 12: KMB? FF?? (keep similar, mixed/tile flip, bonk drops, flute spots)
|
||||
flutespot_mode = {"vanilla": 0, "balanced": 1, "random": 2}
|
||||
|
||||
# byte 13: FBBB TTSS (flute_mode, bow_mode, take_any, small_key_mode)
|
||||
flute_mode = {'normal': 0, 'active': 1}
|
||||
keyshuffle_mode = {'none': 0, 'wild': 1, 'universal': 2} # reserved 8 modes?
|
||||
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
|
||||
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3}
|
||||
|
||||
# additions
|
||||
# psuedoboots does not effect code
|
||||
# sfx_shuffle and other adjust items does not effect settings code
|
||||
# byte 14: POOT TKKK (pseudoboots, overworld_map, trap_door_mode, key_logic_algo)
|
||||
overworld_map_mode = {'default': 0, 'compass': 1, 'map': 2}
|
||||
trap_door_mode = {'vanilla': 0, 'optional': 1, 'boss': 2, 'oneway': 3}
|
||||
key_logic_algo = {'default': 0, 'partial': 1, 'strict': 2}
|
||||
|
||||
# sfx_shuffle and other adjust items does not affect settings code
|
||||
|
||||
# Bump this when making changes that are not backwards compatible (nearly all of them)
|
||||
settings_version = 0
|
||||
settings_version = 1
|
||||
|
||||
|
||||
class Settings(object):
|
||||
@@ -3590,42 +3666,56 @@ class Settings(object):
|
||||
@staticmethod
|
||||
def make_code(w, p):
|
||||
code = bytes([
|
||||
(dr_mode[w.doorShuffle[p]] << 6) | (or_mode[w.owShuffle[p]] << 5) | (0x10 if w.owCrossed[p] != 'none' else 0) | (0x08 if w.owMixed[p] else 0) | er_mode[w.shuffle[p]],
|
||||
(dr_mode[w.doorShuffle[p]] << 5) | er_mode[w.shuffle[p]],
|
||||
|
||||
(logic_mode[w.logic[p]] << 5) | (world_mode[w.mode[p]] << 3) | (sword_mode[w.swords[p]]),
|
||||
|
||||
(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),
|
||||
|
||||
(0x80 if w.shopsanity[p] else 0) | (mixed_travel_mode[w.mixed_travel[p]] << 4)
|
||||
(0x80 if w.shopsanity[p] else 0) | (0x40 if w.decoupledoors[p] else 0)
|
||||
| (mixed_travel_mode[w.mixed_travel[p]] << 4)
|
||||
| (0x8 if w.standardize_palettes[p] == "original" else 0)
|
||||
| (0 if w.intensity[p] == "random" else w.intensity[p]),
|
||||
|
||||
(0x10 if w.dropshuffle[p] else 0) | (pottery_mode[w.pottery[p]]),
|
||||
(0x80 if w.shuffletavern[p] else 0) | (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)
|
||||
| (counter_mode[w.dungeon_counters[p]] << 1) | (1 if w.experimental[p] else 0),
|
||||
|
||||
((8 if w.crystals_ganon_orig[p] == "random" else int(w.crystals_ganon_orig[p])) << 3)
|
||||
| (0x4 if w.is_pyramid_open(p) else 0) | access_mode[w.accessibility[p]],
|
||||
| (0x4 if w.open_pyramid[p] else 0) | access_mode[w.accessibility[p]],
|
||||
|
||||
(0x80 if w.bigkeyshuffle[p] else 0) | (0x40 if w.keyshuffle[p] else 0)
|
||||
(0x80 if w.bigkeyshuffle[p] else 0)
|
||||
| (0x20 if w.mapshuffle[p] else 0) | (0x10 if w.compassshuffle[p] else 0)
|
||||
| (enemy_mode[w.enemy_shuffle[p]]),
|
||||
| (door_type_mode[w.door_type_mode[p]] << 2) | (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)
|
||||
| (0x2 if w.bombbag[p] else 0) | (1 if w.shufflelinks[p] else 0),
|
||||
|
||||
(rb_mode[w.restrict_boss_items[p]] << 6) | (algo_mode[w.algorithm] << 3) | (boss_mode[w.boss_shuffle[p]]),
|
||||
|
||||
settings_version])
|
||||
settings_version,
|
||||
|
||||
(or_mode[w.owShuffle[p]] << 5) | (0x10 if w.owTerrain[p] else 0)
|
||||
| (0x08 if w.owWhirlpoolShuffle[p] else 0) | orcrossed_mode[w.owCrossed[p]],
|
||||
|
||||
(0x80 if w.owKeepSimilar[p] else 0) | (0x40 if w.owMixed[p] else 0)
|
||||
| (0x20 if w.shuffle_bonk_drops[p] else 0) | (flutespot_mode[w.owFluteShuffle[p]] << 4),
|
||||
|
||||
(flute_mode[w.flute_mode[p]] << 7 | bow_mode[w.bow_mode[p]] << 4
|
||||
| take_any_mode[w.take_any[p]] << 2 | keyshuffle_mode[w.keyshuffle[p]]),
|
||||
|
||||
((0x80 if w.pseudoboots[p] else 0) | overworld_map_mode[w.overworld_map[p]] << 6
|
||||
| trap_door_mode[w.trap_door_mode[p]] << 4 | key_logic_algo[w.key_logic_algorithm[p]]),
|
||||
])
|
||||
return base64.b64encode(code, "+-".encode()).decode()
|
||||
|
||||
@staticmethod
|
||||
def adjust_args_from_code(code, player, args):
|
||||
settings, p = base64.b64decode(code.encode(), "+-".encode()), player
|
||||
|
||||
if len(settings) < 11:
|
||||
if len(settings) < 14:
|
||||
raise Exception('Provided code is incompatible with this version')
|
||||
if settings[10] != settings_version:
|
||||
raise Exception('Provided code is incompatible with this version')
|
||||
@@ -3633,9 +3723,8 @@ class Settings(object):
|
||||
def r(d):
|
||||
return {y: x for x, y in d.items()}
|
||||
|
||||
args.shuffle[p] = r(er_mode)[settings[0] & 0x0F]
|
||||
args.ow_shuffle[p] = r(or_mode)[(settings[0] & 0x30) >> 4]
|
||||
args.door_shuffle[p] = r(dr_mode)[(settings[0] & 0xC0) >> 6]
|
||||
args.shuffle[p] = r(er_mode)[settings[0] & 0x1F]
|
||||
args.door_shuffle[p] = r(dr_mode)[(settings[0] & 0xE0) >> 5]
|
||||
args.logic[p] = r(logic_mode)[(settings[1] & 0xE0) >> 5]
|
||||
args.mode[p] = r(world_mode)[(settings[1] & 0x18) >> 3]
|
||||
args.swords[p] = r(sword_mode)[(settings[1] & 0x6) >> 1]
|
||||
@@ -3643,16 +3732,16 @@ class Settings(object):
|
||||
args.item_functionality[p] = r(func_mode)[(settings[2] & 0x6) >> 1]
|
||||
args.goal[p] = r(goal_mode)[(settings[2] & 0xE0) >> 5]
|
||||
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.shopsanity[p] = True if settings[3] & 0x80 else False
|
||||
# args.keydropshuffle[p] = True if settings[3] & 0x40 else False
|
||||
args.decoupledoors[p] = True if settings[3] & 0x40 else False
|
||||
args.mixed_travel[p] = r(mixed_travel_mode)[(settings[3] & 0x30) >> 4]
|
||||
args.standardize_palettes[p] = "original" if settings[3] & 0x8 else "standardize"
|
||||
intensity = settings[3] & 0x7
|
||||
args.intensity[p] = "random" if intensity == 0 else intensity
|
||||
|
||||
# args.shuffleswitches[p] = True if settings[4] & 0x80 else False
|
||||
args.shuffletavern[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]
|
||||
|
||||
@@ -3666,10 +3755,10 @@ class Settings(object):
|
||||
args.openpyramid[p] = True if settings[6] & 0x4 else False
|
||||
|
||||
args.bigkeyshuffle[p] = True if settings[7] & 0x80 else False
|
||||
args.keyshuffle[p] = True if settings[7] & 0x40 else False
|
||||
# args.keyshuffle[p] = True if settings[7] & 0x40 else False
|
||||
args.mapshuffle[p] = True if settings[7] & 0x20 else False
|
||||
args.compassshuffle[p] = True if settings[7] & 0x10 else False
|
||||
# args.shufflebosses[p] = r(boss_mode)[(settings[7] & 0xc) >> 2]
|
||||
args.door_type_mode[p] = r(door_type_mode)[(settings[7] & 0xc) >> 2]
|
||||
args.shuffleenemies[p] = r(enemy_mode)[settings[7] & 0x3]
|
||||
|
||||
args.enemy_health[p] = r(e_health)[(settings[8] & 0xE0) >> 5]
|
||||
@@ -3677,11 +3766,34 @@ class Settings(object):
|
||||
args.shufflepots[p] = True if settings[8] & 0x4 else False
|
||||
args.bombbag[p] = True if settings[8] & 0x2 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)]
|
||||
|
||||
args.ow_shuffle[p] = r(or_mode)[(settings[11] & 0xE0) >> 5]
|
||||
args.ow_terrain[p] = True if settings[11] & 0x10 else False
|
||||
args.ow_whirlpool[p] = True if settings[11] & 0x08 else False
|
||||
args.ow_crossed[p] = r(orcrossed_mode)[(settings[11] & 0x07)]
|
||||
|
||||
args.ow_keepsimilar[p] = True if settings[12] & 0x80 else False
|
||||
args.ow_mixed[p] = True if settings[12] & 0x40 else False
|
||||
args.bonk_drops[p] = True if settings[12] & 0x20 else False
|
||||
args.ow_fluteshuffle[p] = r(flutespot_mode)[(settings[12] & 0x0C) >> 2]
|
||||
|
||||
if len(settings) > 13:
|
||||
args.flute_mode[p] = r(flute_mode)[(settings[13] & 0x80) >> 7]
|
||||
args.bow_mode[p] = r(bow_mode)[(settings[13] & 0x70) >> 4]
|
||||
args.take_any[p] = r(take_any_mode)[(settings[13] & 0xC) >> 2]
|
||||
args.keyshuffle[p] = r(keyshuffle_mode)[settings[13] & 0x3]
|
||||
|
||||
if len(settings) > 14:
|
||||
args.pseudoboots[p] = True if settings[14] & 0x80 else False
|
||||
args.overworld_map[p] = r(overworld_map_mode)[(settings[14] & 0x60) >> 6]
|
||||
args.trap_door_mode[p] = r(trap_door_mode)[(settings[14] & 0x14) >> 4]
|
||||
args.key_logic_algorithm[p] = r(key_logic_algo)[settings[14] & 0x07]
|
||||
|
||||
|
||||
class KeyRuleType(FastEnum):
|
||||
WorstCase = 0
|
||||
|
||||
Reference in New Issue
Block a user