Merge remote-tracking branch 'origin/OverworldShuffle' into OverworldShuffle
This commit is contained in:
452
BaseClasses.py
452
BaseClasses.py
@@ -15,6 +15,8 @@ from source.classes.BabelFish import BabelFish
|
||||
from Utils import int16_as_bytes
|
||||
from Tables import normal_offset_table, spiral_offset_table, multiply_lookup, divisor_lookup
|
||||
from RoomData import Room
|
||||
from source.dungeon.RoomObject import RoomObject
|
||||
|
||||
|
||||
class World(object):
|
||||
|
||||
@@ -52,7 +54,7 @@ class World(object):
|
||||
self._entrance_cache = {}
|
||||
self._location_cache = {}
|
||||
self.required_locations = []
|
||||
self.shuffle_bonk_prizes = False
|
||||
self.shuffle_bonk_drops = {}
|
||||
self.light_world_light_cone = False
|
||||
self.dark_world_light_cone = False
|
||||
self.clock_mode = 'none'
|
||||
@@ -60,8 +62,9 @@ class World(object):
|
||||
self.aga_randomness = True
|
||||
self.lock_aga_door_in_escape = False
|
||||
self.save_and_quit_from_boss = True
|
||||
self.override_bomb_check = False
|
||||
self.is_copied_world = False
|
||||
self.accessibility = accessibility.copy()
|
||||
self.initial_overworld_flags = {}
|
||||
self.fix_skullwoods_exit = {}
|
||||
self.fix_palaceofdarkness_exit = {}
|
||||
self.fix_trock_exit = {}
|
||||
@@ -98,6 +101,7 @@ class World(object):
|
||||
self._portal_cache = {}
|
||||
self.sanc_portal = {}
|
||||
self.fish = BabelFish()
|
||||
self.pot_contents = {}
|
||||
|
||||
for player in range(1, players + 1):
|
||||
# If World State is Retro, set to Open and set Retro flag
|
||||
@@ -119,8 +123,7 @@ class World(object):
|
||||
set_player_attr('ganon_at_pyramid', True)
|
||||
set_player_attr('ganonstower_vanilla', True)
|
||||
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.mode[player] == 'inverted') != 0x05 in self.owswaps[player][0]))
|
||||
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_palaceofdarkness_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
|
||||
set_player_attr('fix_trock_exit', self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
|
||||
@@ -133,6 +136,7 @@ class World(object):
|
||||
set_player_attr('compassshuffle', False)
|
||||
set_player_attr('keyshuffle', False)
|
||||
set_player_attr('bigkeyshuffle', False)
|
||||
set_player_attr('restrict_boss_items', 'none')
|
||||
set_player_attr('bombbag', False)
|
||||
set_player_attr('difficulty_requirements', None)
|
||||
set_player_attr('boss_shuffle', 'none')
|
||||
@@ -147,16 +151,18 @@ class World(object):
|
||||
set_player_attr('crystals_gt_orig', {})
|
||||
set_player_attr('ganon_item', 'default')
|
||||
set_player_attr('ganon_item_orig', 'default')
|
||||
set_player_attr('open_pyramid', False)
|
||||
set_player_attr('open_pyramid', 'auto')
|
||||
set_player_attr('treasure_hunt_icon', 'Triforce Piece')
|
||||
set_player_attr('treasure_hunt_count', 0)
|
||||
set_player_attr('treasure_hunt_total', 0)
|
||||
set_player_attr('potshuffle', False)
|
||||
set_player_attr('pot_contents', None)
|
||||
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('keydropshuffle', 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})
|
||||
@@ -258,6 +264,11 @@ class World(object):
|
||||
return r_location
|
||||
raise RuntimeError('No such location %s for player %d' % (location, player))
|
||||
|
||||
def get_location_unsafe(self, location, player):
|
||||
if (location, player) in self._location_cache:
|
||||
return self._location_cache[(location, player)]
|
||||
return None
|
||||
|
||||
def get_dungeon(self, dungeonname, player):
|
||||
if isinstance(dungeonname, Dungeon):
|
||||
return dungeonname
|
||||
@@ -307,11 +318,24 @@ class World(object):
|
||||
return (self.mode[player] == 'inverted') != (owid in self.owswaps[player][0] and self.owMixed[player])
|
||||
|
||||
def is_atgt_swapped(self, player):
|
||||
return (0x03 in self.owswaps[player][0]) == (0x1b in self.owswaps[player][0]) == (self.mode[player] != 'inverted')
|
||||
return self.is_tile_swapped(0x03, player) and self.is_tile_swapped(0x1b, player)
|
||||
|
||||
def is_bombshop_start(self, player):
|
||||
return self.is_tile_swapped(0x2c, player) and (self.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull'] or not self.shufflelinks[player])
|
||||
|
||||
def is_pyramid_open(self, player):
|
||||
if self.open_pyramid[player] == 'yes':
|
||||
return True
|
||||
elif self.open_pyramid[player] == 'no':
|
||||
return False
|
||||
else:
|
||||
if self.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
||||
return False
|
||||
elif self.goal[player] in ['crystals', 'trinity']:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def check_for_door(self, doorname, player):
|
||||
if isinstance(doorname, Door):
|
||||
return doorname
|
||||
@@ -417,7 +441,7 @@ class World(object):
|
||||
elif item.name.startswith('Bottle'):
|
||||
if ret.bottle_count(item.player) < self.difficulty_requirements[item.player].progressive_bottle_limit:
|
||||
ret.prog_items[item.name, item.player] += 1
|
||||
elif item.advancement or item.smallkey or item.bigkey:
|
||||
elif item.advancement or item.smallkey or item.bigkey or item.compass or item.map:
|
||||
ret.prog_items[item.name, item.player] += 1
|
||||
if item.name.endswith(' Cane'):
|
||||
if ret.world.swords[item.player] == 'byrna' and not ret.has('Cane of Byrna', item.player):
|
||||
@@ -437,6 +461,8 @@ class World(object):
|
||||
key_list += [dungeon.big_key.name]
|
||||
if len(dungeon.small_keys) > 0:
|
||||
key_list += [x.name for x in dungeon.small_keys]
|
||||
# map/compass may be required now
|
||||
key_list += [x.name for x in dungeon.dungeon_items]
|
||||
from Items import ItemFactory
|
||||
for item in ItemFactory(key_list, p):
|
||||
soft_collect(item)
|
||||
@@ -467,6 +493,8 @@ class World(object):
|
||||
location.item = item
|
||||
item.location = location
|
||||
item.world = self
|
||||
if location.player != item.player and location.type == LocationType.Pot:
|
||||
self.pot_contents[location.player].multiworld_count += 1
|
||||
if collect:
|
||||
self.state.collect(item, location.event, location)
|
||||
|
||||
@@ -948,7 +976,7 @@ class CollectionState(object):
|
||||
reduced = Counter()
|
||||
for item, cnt in self.prog_items.items():
|
||||
item_name, item_player = item
|
||||
if item_player == player and self.check_if_progressive(item_name):
|
||||
if item_player == player and self.check_if_progressive(item_name, player):
|
||||
if item_name.startswith('Bottle'): # I think magic requirements can require multiple bottles
|
||||
bottle_count += cnt
|
||||
elif item_name in ['Boss Heart Container', 'Sanctuary Heart Container', 'Piece of Heart']:
|
||||
@@ -964,8 +992,7 @@ class CollectionState(object):
|
||||
reduced[('Heart Container', player)] = 1
|
||||
return frozenset(reduced.items())
|
||||
|
||||
@staticmethod
|
||||
def check_if_progressive(item_name):
|
||||
def check_if_progressive(self, item_name, player):
|
||||
return (item_name in
|
||||
['Bow', 'Progressive Bow', 'Progressive Bow (Alt)', 'Book of Mudora', 'Hammer', 'Hookshot',
|
||||
'Magic Mirror', 'Ocarina', 'Pegasus Boots', 'Power Glove', 'Cape', 'Mushroom', 'Shovel',
|
||||
@@ -974,10 +1001,11 @@ 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',
|
||||
'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')))
|
||||
or item_name.startswith(('Bottle', 'Small Key', 'Big Key'))
|
||||
or (self.world.restrict_boss_items[player] != 'none' and item_name.startswith(('Map', 'Compass'))))
|
||||
|
||||
def can_reach(self, spot, resolution_hint=None, player=None):
|
||||
try:
|
||||
@@ -1087,6 +1115,9 @@ class CollectionState(object):
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_collect_bonkdrops(self, player):
|
||||
return self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))
|
||||
|
||||
def can_farm_rupees(self, player):
|
||||
tree_pulls = ['Lost Woods East Area',
|
||||
'Snitch Lady (East)',
|
||||
@@ -1100,19 +1131,24 @@ class CollectionState(object):
|
||||
|
||||
rupee_farms = ['Archery Game', '50 Rupee Cave', '20 Rupee Cave']
|
||||
|
||||
bush_crabs = ['Lost Woods East Area', 'Mountain Entry Area']
|
||||
pre_aga_bush_crabs = ['Lumberjack Area', 'South Pass Area']
|
||||
rock_crabs = ['Desert Pass Area']
|
||||
|
||||
def can_reach_non_bunny(regionname):
|
||||
region = self.world.get_region(regionname, player)
|
||||
return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player))
|
||||
|
||||
for region in rupee_farms:
|
||||
for region in rupee_farms if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else ['Archery Game']:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
if any(i in [0xda, 0xdb] for i in self.world.prizes[player]['pull']):
|
||||
# tree pulls
|
||||
if self.can_kill_most_things(player) and any(i in [0xda, 0xdb] for i in self.world.prizes[player]['pull']):
|
||||
for region in tree_pulls:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
if not self.has('Beat Agahnim 1', player):
|
||||
if not self.has_beaten_aga(player):
|
||||
for region in pre_aga_tree_pulls:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
@@ -1120,6 +1156,22 @@ class CollectionState(object):
|
||||
for region in post_aga_tree_pulls:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
# bush crabs (final item isn't considered)
|
||||
if self.world.enemy_shuffle[player] == 'none':
|
||||
if self.world.prizes[player]['crab'][0] in [0xda, 0xdb]:
|
||||
for region in bush_crabs:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
if not self.has_beaten_aga(player):
|
||||
for region in pre_aga_bush_crabs:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
if self.can_lift_rocks(player) and self.world.prizes[player]['crab'][0] in [0xda, 0xdb]:
|
||||
for region in rock_crabs:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def can_farm_bombs(self, player):
|
||||
@@ -1169,7 +1221,7 @@ class CollectionState(object):
|
||||
return region.can_reach(self) and ((self.world.mode[player] != 'inverted' and region.is_light_world) or (self.world.mode[player] == 'inverted' and region.is_dark_world) or self.has('Pearl', player))
|
||||
|
||||
# bomb pickups
|
||||
for region in bush_bombs + bomb_caves:
|
||||
for region in bush_bombs + (bomb_caves if self.world.pottery[player] in ['none', 'keys', 'dungeon'] else []):
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
@@ -1178,17 +1230,17 @@ class CollectionState(object):
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
if self.has_Boots(player):
|
||||
if not self.world.shuffle_bonk_drops[player] and self.can_collect_bonkdrops(player):
|
||||
for region in bonk_bombs:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
|
||||
# tree pulls
|
||||
if any(i in [0xdc, 0xdd, 0xde] for i in self.world.prizes[player]['pull']):
|
||||
if self.can_kill_most_things(player) and any(i in [0xdc, 0xdd, 0xde] for i in self.world.prizes[player]['pull']):
|
||||
for region in tree_pulls:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
if not self.has('Beat Agahnim 1', player):
|
||||
if not self.has_beaten_aga(player):
|
||||
for region in pre_aga_tree_pulls:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
@@ -1198,12 +1250,12 @@ class CollectionState(object):
|
||||
return True
|
||||
|
||||
# bush crabs (final item isn't considered)
|
||||
if self.world.enemy_shuffle[player] != 'none':
|
||||
if self.world.enemy_shuffle[player] == 'none':
|
||||
if self.world.prizes[player]['crab'][0] in [0xdc, 0xdd, 0xde]:
|
||||
for region in bush_crabs:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
if not self.has('Beat Agahnim 1', player):
|
||||
if not self.has_beaten_aga(player):
|
||||
for region in pre_aga_bush_crabs:
|
||||
if can_reach_non_bunny(region):
|
||||
return True
|
||||
@@ -1291,7 +1343,7 @@ class CollectionState(object):
|
||||
def can_use_bombs(self, player):
|
||||
if self.world.swords[player] == 'bombs':
|
||||
return self.has_special_weapon_level(player, 1)
|
||||
return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player)) and ((hasattr(self.world, "override_bomb_check") and self.world.override_bomb_check) or self.can_farm_bombs(player))
|
||||
return (not self.world.bombbag[player] or self.has('Bomb Upgrade (+10)', player) or self.has('Bomb Upgrade (+5)', player, 2)) and (self.world.override_bomb_check or self.can_farm_bombs(player))
|
||||
|
||||
def can_kill_with_bombs(self, player):
|
||||
if self.world.swords[player] in ['cane', 'somaria', 'byrna']:
|
||||
@@ -1325,16 +1377,19 @@ class CollectionState(object):
|
||||
return self.has('Bow', player) and (self.can_buy_unlimited('Single Arrow', player) or self.has('Single Arrow', player))
|
||||
return self.has('Bow', player)
|
||||
|
||||
def can_get_good_bee(self, player):
|
||||
cave = self.world.get_region('Good Bee Cave', player)
|
||||
return (
|
||||
self.can_use_bombs(player) and
|
||||
self.has_bottle(player) and
|
||||
self.has('Bug Catching Net', player) and
|
||||
(self.has_Boots(player) or (self.can_use_medallions(player) and self.has('Quake', player))) and
|
||||
cave.can_reach(self) and
|
||||
self.is_not_bunny(cave, player)
|
||||
)
|
||||
# def can_get_good_bee(self, player):
|
||||
# cave = self.world.get_region('Good Bee Cave', player)
|
||||
# return (
|
||||
# self.can_use_bombs(player) and
|
||||
# self.has_bottle(player) and
|
||||
# self.has('Bug Catching Net', player) and
|
||||
# (self.has_Boots(player) or (self.has_sword(player) and self.has('Quake', player))) and
|
||||
# cave.can_reach(self) and
|
||||
# self.is_not_bunny(cave, player)
|
||||
# )
|
||||
|
||||
def has_beaten_aga(self, player):
|
||||
return self.has('Beat Agahnim 1', player) and (self.world.mode[player] != 'standard' or self.has('Zelda Delivered', player))
|
||||
|
||||
def has_sword(self, player, level=1):
|
||||
if level == 4:
|
||||
@@ -1447,10 +1502,12 @@ class CollectionState(object):
|
||||
return self.has('Fire Rod', player) or self.has('Lamp', 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)):
|
||||
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))
|
||||
@@ -1757,7 +1814,7 @@ class Region(object):
|
||||
def can_reach(self, state):
|
||||
from Utils import stack_size3a
|
||||
from DungeonGenerator import GenerationException
|
||||
if stack_size3a() > 500:
|
||||
if stack_size3a() > self.world.players * 500:
|
||||
raise GenerationException(f'Infinite loop detected for "{self.name}" located at \'Region.can_reach\'')
|
||||
|
||||
if state.stale[self.player]:
|
||||
@@ -2044,6 +2101,10 @@ class Dungeon(object):
|
||||
return self.world.get_name_string_for_object(self) if self.world else f'{self.name} (Player {self.player})'
|
||||
|
||||
|
||||
class FillError(RuntimeError):
|
||||
pass
|
||||
|
||||
|
||||
@unique
|
||||
class DoorType(Enum):
|
||||
Normal = 1
|
||||
@@ -2418,7 +2479,6 @@ class Door(object):
|
||||
class WorldType(IntEnum):
|
||||
Light = 0
|
||||
Dark = 1
|
||||
Special = 2
|
||||
|
||||
|
||||
@unique
|
||||
@@ -2434,6 +2494,8 @@ class OWEdge(object):
|
||||
self.type = DoorType.Open
|
||||
self.direction = direction
|
||||
self.terrain = terrain
|
||||
self.specialEntrance = False
|
||||
self.specialExit = False
|
||||
self.deadEnd = False
|
||||
|
||||
# rom properties
|
||||
@@ -2447,6 +2509,7 @@ class OWEdge(object):
|
||||
self.zeroHzCam = False
|
||||
self.zeroVtCam = False
|
||||
self.edge_id = edge_id
|
||||
self.specialID = 0x0
|
||||
|
||||
self.midpoint = 0x0
|
||||
self.linkOpp = 0x0
|
||||
@@ -2458,12 +2521,10 @@ class OWEdge(object):
|
||||
self.unknownX = 0x0
|
||||
self.unknownY = 0x0
|
||||
|
||||
if self.owIndex < 0x40:
|
||||
if self.owIndex < 0x40 or self.owIndex >= 0x80:
|
||||
self.worldType = WorldType.Light
|
||||
elif self.owIndex < 0x80:
|
||||
self.worldType = WorldType.Dark
|
||||
else:
|
||||
self.worldType = WorldType.Special
|
||||
self.worldType = WorldType.Dark
|
||||
|
||||
# logical properties
|
||||
# self.connected = False # combine with Dest?
|
||||
@@ -2481,7 +2542,7 @@ class OWEdge(object):
|
||||
return base_address[self.direction] + (self.edge_id * 16)
|
||||
|
||||
def getTarget(self):
|
||||
return self.dest.edge_id
|
||||
return self.dest.specialID if self.dest.specialExit else self.dest.edge_id
|
||||
|
||||
def dead_end(self):
|
||||
self.deadEnd = True
|
||||
@@ -2491,6 +2552,16 @@ class OWEdge(object):
|
||||
self.vramLoc = vram_loc
|
||||
return self
|
||||
|
||||
def special_entrance(self, special_id):
|
||||
self.specialEntrance = True
|
||||
self.specialID = special_id
|
||||
return self
|
||||
|
||||
def special_exit(self, special_id):
|
||||
self.specialExit = True
|
||||
self.specialID = special_id
|
||||
return self
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__) and self.name == other.name
|
||||
|
||||
@@ -2525,6 +2596,8 @@ class Sector(object):
|
||||
self.destination_entrance = False
|
||||
self.equations = None
|
||||
self.item_logic = set()
|
||||
self.chest_location_set = set()
|
||||
|
||||
|
||||
def region_set(self):
|
||||
if self.r_name_set is None:
|
||||
@@ -2633,6 +2706,12 @@ class Portal(object):
|
||||
self.dependent = None
|
||||
self.deadEnd = False
|
||||
self.light_world = False
|
||||
self.chosen = False
|
||||
|
||||
def find_portal_entrance(self):
|
||||
p_region = self.door.entrance.connected_region
|
||||
return next((x for x in p_region.entrances
|
||||
if x.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]), None)
|
||||
|
||||
def change_boss_exit(self, exit_idx):
|
||||
self.default = False
|
||||
@@ -2767,15 +2846,25 @@ class Location(object):
|
||||
self.recursion_count = 0
|
||||
self.staleness_count = 0
|
||||
self.locked = False
|
||||
self.real = not crystal
|
||||
self.always_allow = lambda item, state: False
|
||||
self.access_rule = lambda state: True
|
||||
self.item_rule = lambda item: True
|
||||
self.player = player
|
||||
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):
|
||||
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)))
|
||||
|
||||
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):
|
||||
return self.parent_region.can_reach(state) and self.access_rule(state)
|
||||
|
||||
@@ -2811,6 +2900,16 @@ class Location(object):
|
||||
return hash((self.name, self.player))
|
||||
|
||||
|
||||
class LocationType(FastEnum):
|
||||
Normal = 0
|
||||
Prize = 1
|
||||
Logical = 2
|
||||
Shop = 3
|
||||
Pot = 4
|
||||
Drop = 5
|
||||
Bonk = 6
|
||||
|
||||
|
||||
class Item(object):
|
||||
|
||||
def __init__(self, name='', advancement=False, priority=False, type=None, code=None, price=999, pedestal_hint=None,
|
||||
@@ -2862,6 +2961,12 @@ class Item(object):
|
||||
item_dungeon = 'Hyrule Castle'
|
||||
return item_dungeon
|
||||
|
||||
def is_inside_dungeon_item(self, world):
|
||||
return ((self.smallkey and not world.keyshuffle[self.player])
|
||||
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]))
|
||||
|
||||
def __str__(self):
|
||||
return str(self.__unicode__())
|
||||
|
||||
@@ -3018,11 +3123,14 @@ class Spoiler(object):
|
||||
'ow_mixed': self.world.owMixed,
|
||||
'ow_whirlpool': self.world.owWhirlpoolShuffle,
|
||||
'ow_fluteshuffle': self.world.owFluteShuffle,
|
||||
'bonk_drops': self.world.shuffle_bonk_drops,
|
||||
'shuffle': self.world.shuffle,
|
||||
'shuffleganon': self.world.shuffle_ganon,
|
||||
'shufflelinks': self.world.shufflelinks,
|
||||
'overworld_map': self.world.overworld_map,
|
||||
'door_shuffle': self.world.doorShuffle,
|
||||
'intensity': self.world.intensity,
|
||||
'dungeon_counters': self.world.dungeon_counters,
|
||||
'item_pool': self.world.difficulty,
|
||||
'item_functionality': self.world.difficulty_adjustments,
|
||||
'gt_crystals': self.world.crystals_needed_for_gt,
|
||||
@@ -3030,6 +3138,7 @@ class Spoiler(object):
|
||||
'ganon_vulnerability_item': self.world.ganon_item,
|
||||
'open_pyramid': self.world.open_pyramid,
|
||||
'accessibility': self.world.accessibility,
|
||||
'restricted_boss_items': self.world.restrict_boss_items,
|
||||
'hints': self.world.hints,
|
||||
'mapshuffle': self.world.mapshuffle,
|
||||
'compassshuffle': self.world.compassshuffle,
|
||||
@@ -3039,16 +3148,19 @@ class Spoiler(object):
|
||||
'enemy_shuffle': self.world.enemy_shuffle,
|
||||
'enemy_health': self.world.enemy_health,
|
||||
'enemy_damage': self.world.enemy_damage,
|
||||
'potshuffle': self.world.potshuffle,
|
||||
'players': self.world.players,
|
||||
'teams': self.world.teams,
|
||||
'experimental': self.world.experimental,
|
||||
'keydropshuffle': self.world.keydropshuffle,
|
||||
'dropshuffle': self.world.dropshuffle,
|
||||
'pottery': self.world.pottery,
|
||||
'potshuffle': self.world.potshuffle,
|
||||
'shopsanity': self.world.shopsanity,
|
||||
'pseudoboots': self.world.pseudoboots,
|
||||
'triforcegoal': self.world.treasure_hunt_count,
|
||||
'triforcepool': self.world.treasure_hunt_total,
|
||||
'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):
|
||||
from ItemList import set_default_triforce
|
||||
if self.world.custom and p in self.world.customitemarray:
|
||||
@@ -3076,7 +3188,7 @@ class Spoiler(object):
|
||||
for player in range(1, self.world.players + 1):
|
||||
self.bottles[f'Waterfall Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][0]
|
||||
self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1]
|
||||
|
||||
|
||||
self.locations = OrderedDict()
|
||||
listed_locations = set()
|
||||
|
||||
@@ -3093,11 +3205,11 @@ class Spoiler(object):
|
||||
listed_locations.update(cave_locations)
|
||||
|
||||
for dungeon in self.world.dungeons:
|
||||
dungeon_locations = [loc for loc in self.world.get_locations() if loc not in listed_locations and loc.parent_region and loc.parent_region.dungeon == dungeon and not loc.forced_item]
|
||||
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])
|
||||
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:
|
||||
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)
|
||||
@@ -3172,11 +3284,26 @@ class Spoiler(object):
|
||||
|
||||
return json.dumps(out)
|
||||
|
||||
def meta_to_file(self, filename):
|
||||
def mystery_meta_to_file(self, filename):
|
||||
self.parse_meta()
|
||||
with open(filename, 'w') as outfile:
|
||||
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():
|
||||
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 yn(flag):
|
||||
return 'Yes' if flag else 'No'
|
||||
|
||||
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)
|
||||
outfile.write('Filling Algorithm:'.ljust(line_width) + '%s\n' % self.world.algorithm)
|
||||
@@ -3188,7 +3315,7 @@ 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' % ('Yes' if self.metadata['retro'][player] else 'No'))
|
||||
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']:
|
||||
@@ -3198,56 +3325,80 @@ class Spoiler(object):
|
||||
outfile.write('Crystals Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.crystals_ganon_orig[player]))
|
||||
outfile.write('Ganon Vulnerability Item:'.ljust(line_width) + '%s\n' % str(self.metadata['ganon_vulnerability_item'][player]))
|
||||
outfile.write('Accessibility:'.ljust(line_width) + '%s\n' % self.metadata['accessibility'][player])
|
||||
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('Shopsanity:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shopsanity'][player] else 'No'))
|
||||
outfile.write('Bombbag:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['bombbag'][player] else 'No'))
|
||||
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]))
|
||||
outfile.write('Overworld Layout Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_shuffle'][player])
|
||||
if self.metadata['ow_shuffle'][player] != 'vanilla':
|
||||
outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_keepsimilar'][player] else 'No'))
|
||||
outfile.write('Crossed OW:'.ljust(line_width) + '%s\n' % self.metadata['ow_crossed'][player])
|
||||
outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_mixed'][player] else 'No'))
|
||||
outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['ow_whirlpool'][player] else 'No'))
|
||||
if self.metadata['ow_shuffle'][player] != 'vanilla' or self.metadata['ow_crossed'][player] != 'none':
|
||||
outfile.write('Keep Similar OW Edges Together:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_keepsimilar'][player]))
|
||||
outfile.write('Swapped OW (Mixed):'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_mixed'][player]))
|
||||
outfile.write('Whirlpool Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['ow_whirlpool'][player]))
|
||||
outfile.write('Flute Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['ow_fluteshuffle'][player])
|
||||
outfile.write('Bonk Drops:'.ljust(line_width) + '%s\n' % yn(self.metadata['bonk_drops'][player]))
|
||||
outfile.write('Entrance Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['shuffle'][player])
|
||||
if self.metadata['shuffle'][player] != 'vanilla':
|
||||
outfile.write('Shuffle GT/Ganon:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shuffleganon'][player] else 'No'))
|
||||
outfile.write('Shuffle Links:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['shufflelinks'][player] else 'No'))
|
||||
if self.metadata['goal'][player] != 'trinity':
|
||||
outfile.write('Pyramid Hole Pre-opened:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['open_pyramid'][player] else 'No'))
|
||||
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]))
|
||||
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('Experimental:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['experimental'][player] else 'No'))
|
||||
outfile.write('Pot Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['potshuffle'][player] else 'No'))
|
||||
outfile.write('Key Drop Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['keydropshuffle'][player] else 'No'))
|
||||
outfile.write('Map Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['mapshuffle'][player] else 'No'))
|
||||
outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['compassshuffle'][player] else 'No'))
|
||||
outfile.write('Small Key Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['keyshuffle'][player] else 'No'))
|
||||
outfile.write('Big Key Shuffle:'.ljust(line_width) + '%s\n' % ('Yes' if self.metadata['bigkeyshuffle'][player] else 'No'))
|
||||
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]))
|
||||
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('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('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' % ('Yes' if self.metadata['hints'][player] else 'No'))
|
||||
outfile.write('Hints:'.ljust(line_width) + '%s\n' % yn(self.metadata['hints'][player]))
|
||||
|
||||
if self.startinventory:
|
||||
outfile.write('Starting Inventory:'.ljust(line_width))
|
||||
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):
|
||||
self.parse_data()
|
||||
with open(filename, 'a') as outfile:
|
||||
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')
|
||||
for dungeon, medallion in self.medallions.items():
|
||||
outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion)
|
||||
@@ -3262,7 +3413,7 @@ class Spoiler(object):
|
||||
for fairy, bottle in self.bottles.items():
|
||||
outfile.write(f'{fairy}: {bottle}\n')
|
||||
|
||||
if self.overworlds:
|
||||
if self.overworlds or self.maps:
|
||||
outfile.write('\n\nOverworld:\n\n')
|
||||
|
||||
# flute shuffle
|
||||
@@ -3286,7 +3437,19 @@ class Spoiler(object):
|
||||
if self.world.players > 1:
|
||||
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
|
||||
outfile.write(self.maps[('swaps', player)]['text'] + '\n\n')
|
||||
|
||||
|
||||
# crossed groups
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('groups', player) in self.maps:
|
||||
outfile.write('OW Crossed Groups:\n')
|
||||
break
|
||||
for player in range(1, self.world.players + 1):
|
||||
if ('groups', player) in self.maps:
|
||||
if self.world.players > 1:
|
||||
outfile.write(str('(Player ' + str(player) + ')\n')) # player name
|
||||
outfile.write(self.maps[('groups', player)]['text'] + '\n\n')
|
||||
|
||||
if self.overworlds:
|
||||
# overworld transitions
|
||||
outfile.write('\n'.join(['%s%s %s %s' % (f'{self.world.get_player_names(entry["player"])}: ' if self.world.players > 1 else '', self.world.fish.translate("meta","overworlds",entry['entrance']), '<=>' if entry['direction'] == 'both' else '<=' if entry['direction'] == 'exit' else '=>', self.world.fish.translate("meta","overworlds",entry['exit'])) for entry in self.overworlds.values()]))
|
||||
|
||||
@@ -3332,7 +3495,7 @@ class Spoiler(object):
|
||||
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']]))
|
||||
|
||||
def playthru_to_file(self, filename):
|
||||
def playthrough_to_file(self, filename):
|
||||
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
|
||||
# items: Item names
|
||||
@@ -3430,15 +3593,40 @@ class PotFlags(FastEnum):
|
||||
Normal = 0x0
|
||||
NoSwitch = 0x1 # A switch should never go here
|
||||
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):
|
||||
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.y = y
|
||||
self.item = item
|
||||
self.room = room
|
||||
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)
|
||||
@@ -3456,24 +3644,44 @@ goal_mode = {"ganon": 0, "pedestal": 1, "dungeons": 2, "triforcehunt": 3, "cryst
|
||||
diff_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}
|
||||
# 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}
|
||||
|
||||
# byte 5: CCCC CPAA (crystals ganon, pyramid, access
|
||||
# byte 6: CCCC CPAA (crystals ganon, pyramid, access
|
||||
access_mode = {"items": 0, "locations": 1, "none": 2}
|
||||
|
||||
# byte 6: BSMC BBEE (big, small, maps, compass, bosses, enemies)
|
||||
boss_mode = {"none": 0, "simple": 1, "full": 2, "random": 3, "chaos": 3}
|
||||
enemy_mode = {"none": 0, "shuffled": 1, "random": 2, "chaos": 2, "legacy": 3}
|
||||
# byte 7: BSMC ??EE (big, small, maps, compass, bosses, enemies)
|
||||
enemy_mode = {"none": 0, "shuffled": 1, "chaos": 2, "random": 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_dmg = {"default": 0, "shuffled": 1, "random": 2}
|
||||
|
||||
# byte 9: RRAA ABBB (restrict boss mode, algorithm, boss shuffle)
|
||||
rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2}
|
||||
# algorithm:
|
||||
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
|
||||
# psuedoboots does not effect 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):
|
||||
|
||||
@staticmethod
|
||||
@@ -3486,28 +3694,39 @@ class Settings(object):
|
||||
(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) | (0x40 if w.keydropshuffle[p] else 0)
|
||||
| (mixed_travel_mode[w.mixed_travel[p]] << 4) | (0x8 if w.standardize_palettes[p] == "original" else 0)
|
||||
(0x80 if w.shopsanity[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]]),
|
||||
|
||||
((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.open_pyramid[p] else 0) | access_mode[w.accessibility[p]],
|
||||
| (0x4 if w.is_pyramid_open(p) else 0) | access_mode[w.accessibility[p]],
|
||||
|
||||
(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)
|
||||
| (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)
|
||||
| (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) | (algo_mode[w.algorithm] << 3) | (boss_mode[w.boss_shuffle[p]]),
|
||||
|
||||
settings_version])
|
||||
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:
|
||||
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):
|
||||
return {y: x for x, y in d.items()}
|
||||
|
||||
@@ -3520,34 +3739,45 @@ class Settings(object):
|
||||
args.difficulty[p] = r(diff_mode)[(settings[2] & 0x18) >> 3]
|
||||
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[5] & 0x3]
|
||||
args.accessibility[p] = r(access_mode)[settings[6] & 0x3]
|
||||
args.retro[p] = True if settings[1] & 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.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.standardize_palettes[p] = "original" if settings[3] & 0x8 else "standardize"
|
||||
intensity = settings[3] & 0x7
|
||||
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.experimental[p] = True if settings[4] & 0x1 else False
|
||||
cgan = (settings[5] & 0xf8) >> 3
|
||||
args.experimental[p] = True if settings[5] & 0x1 else False
|
||||
|
||||
cgan = (settings[6] & 0xf8) >> 3
|
||||
args.crystals_ganon[p] = "random" if cgan == 8 else cgan
|
||||
args.openpyramid[p] = True if settings[5] & 0x4 else False
|
||||
args.bigkeyshuffle[p] = True if settings[6] & 0x80 else False
|
||||
args.keyshuffle[p] = True if settings[6] & 0x40 else False
|
||||
args.mapshuffle[p] = True if settings[6] & 0x20 else False
|
||||
args.compassshuffle[p] = True if settings[6] & 0x10 else False
|
||||
args.shufflebosses[p] = r(boss_mode)[(settings[6] & 0xc) >> 2]
|
||||
args.shuffleenemies[p] = r(enemy_mode)[settings[6] & 0x3]
|
||||
args.enemy_health[p] = r(e_health)[(settings[7] & 0xE0) >> 5]
|
||||
args.enemy_damage[p] = r(e_dmg)[(settings[7] & 0x18) >> 3]
|
||||
args.shufflepots[p] = True if settings[7] & 0x4 else False
|
||||
args.bombbag[p] = True if settings[7] & 0x2 else False
|
||||
args.shufflelinks[p] = True if settings[7] & 0x1 else False
|
||||
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.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.shuffleenemies[p] = r(enemy_mode)[settings[7] & 0x3]
|
||||
|
||||
args.enemy_health[p] = r(e_health)[(settings[8] & 0xE0) >> 5]
|
||||
args.enemy_damage[p] = r(e_dmg)[(settings[8] & 0x18) >> 3]
|
||||
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)]
|
||||
|
||||
|
||||
class KeyRuleType(FastEnum):
|
||||
|
||||
Reference in New Issue
Block a user