Merge branch 'OverworldShuffle' into GwaaKiwi

This commit is contained in:
2025-12-14 09:14:20 -06:00
188 changed files with 1479 additions and 6109 deletions

View File

@@ -65,7 +65,7 @@ class World(object):
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.is_premature_copied_world = False
self.accessibility = accessibility.copy()
self.fix_skullwoods_exit = {}
self.fix_palaceofdarkness_exit = {}
@@ -81,11 +81,11 @@ class World(object):
self.can_take_damage = {}
self.hints = hints.copy()
self.prizes = {}
self.default_zelda_region = {}
self.dynamic_regions = []
self.dynamic_locations = []
self.spoiler_mode = spoiler_mode
self.spoiler = Spoiler(self)
self.lamps_needed_for_dark_rooms = 1
self.owedges = []
self._owedge_cache = {}
self.owswaps = {}
@@ -153,12 +153,15 @@ class World(object):
set_player_attr('bombbag', False)
set_player_attr('flute_mode', False)
set_player_attr('bow_mode', False)
set_player_attr('free_lamp_cone', False)
set_player_attr('difficulty_requirements', None)
set_player_attr('boss_shuffle', 'none')
set_player_attr('enemy_shuffle', 'none')
set_player_attr('enemy_health', 'default')
set_player_attr('enemy_damage', 'default')
set_player_attr('any_enemy_logic', 'allow_all')
set_player_attr('force_enemy', None)
set_player_attr('beemizer', '0')
set_player_attr('escape_assist', [])
set_player_attr('crystals_needed_for_ganon', 7)
@@ -197,6 +200,8 @@ class World(object):
set_player_attr('standardize_palettes', 'standardize')
set_player_attr('force_fix', {'gt': False, 'sw': False, 'pod': False, 'tr': False})
set_player_attr('prizes', {'dig;': [], 'pull': [0, 0, 0], 'crab': [0, 0], 'stun': 0, 'fish': 0, 'enemies': []})
set_player_attr('default_zelda_region', 'Hyrule Dungeon Cellblock')
set_player_attr('custom_goals', {'gtentry': None, 'ganongoal': None, 'pedgoal': None, 'murahgoal': None})
set_player_attr('exp_cache', defaultdict(dict))
set_player_attr('enabled_entrances', {})
@@ -1221,16 +1226,35 @@ 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])
def everything(self, player, all_except=0):
all_locations = [x for x in self.world.get_filled_locations(player) if not x.locked]
return (len([x for x in self.locations_checked if x.player == player and not x.locked]) + all_except
>= 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
def has_pendants(self, count, player):
pendants = ['Green Pendant', 'Red Pendant', 'Blue Pendant']
return len([pendant for pendant in pendants if self.has(pendant, player)]) >= count
def has_bosses(self, count, player, prize_type=None):
dungeons = 'Eastern Palace', 'Desert Palace', 'Tower of Hera', 'Palace of Darkness', 'Swamp Palace', "Thieves' Town", 'Skull Woods', 'Ice Palace', 'Misery Mire', 'Turtle Rock'
reachable_bosses = 0
for d in dungeons:
region = self.world.get_region(f'{d} - Boss Kill', player)
if prize_type is None or prize_type in region.dungeon.prize.name:
if self.can_reach(region, None, player):
reachable_bosses += 1
return reachable_bosses >= count
def has_crystal_bosses(self, count, player):
return self.has_bosses(count, player, 'Crystal')
def has_pendant_bosses(self, count, player):
return self.has_bosses(count, player, 'Pendant')
def can_lift_rocks(self, player):
return self.has('Power Glove', player) or self.has('Titans Mitts', player)
@@ -2728,7 +2752,7 @@ class Location(object):
self.staleness_count = 0
self.locked = False
self.real = True
self.always_allow = lambda item, state: False
self.always_allow = None
self.access_rule = lambda state: True
self.verbose_rule = None
self.item_rule = lambda item: True
@@ -2742,7 +2766,7 @@ class Location(object):
def can_fill(self, state, item, check_access=True):
if not self.valid_multiworld(state, item):
return False
return self.always_allow(state, item) or (self.parent_region.can_fill(item) and self.item_rule(item) and (not check_access or self.can_reach(state)))
return (self.always_allow and 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:
@@ -3061,6 +3085,7 @@ class Spoiler(object):
'flute_mode': self.world.flute_mode,
'bow_mode': self.world.bow_mode,
'goal': self.world.goal,
'custom_goals': self.world.custom_goals,
'ow_shuffle': self.world.owShuffle,
'ow_terrain': self.world.owTerrain,
'ow_crossed': self.world.owCrossed,
@@ -3302,8 +3327,23 @@ class Spoiler(object):
if self.metadata['goal'][player] in ['triforcehunt', 'trinity', '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]))
outfile.write('Crystals Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.crystals_ganon_orig[player]))
custom = self.metadata['custom_goals'][player]
if custom['gtentry'] and 'requirements' in custom['gtentry']:
outfile.write('GT Entry Requirement:'.ljust(line_width) + 'custom\n')
outfile.write(' %s\n' % custom['gtentry']['goaltext'])
else:
outfile.write('GT Entry Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_gt_orig[player]))
if custom['ganongoal'] and 'requirements' in custom['ganongoal']:
outfile.write('Ganon Requirement:'.ljust(line_width) + 'custom\n')
outfile.write(' %s\n' % custom['ganongoal']['goaltext'])
else:
outfile.write('Ganon Requirement:'.ljust(line_width) + '%s crystals\n' % str(self.world.crystals_ganon_orig[player]))
if custom['pedgoal'] and 'requirements' in custom['pedgoal']:
outfile.write('Pedestal Requirement:'.ljust(line_width) + 'custom\n')
outfile.write(' %s\n' % custom['pedgoal']['goaltext'])
if custom['murahgoal'] and 'requirements' in custom['murahgoal']:
outfile.write('Murahdahla Requirement:'.ljust(line_width) + 'custom\n')
outfile.write(' %s\n' % custom['murahgoal']['goaltext'])
outfile.write('Item Required for Ganon:'.ljust(line_width) + '%s\n' % str(self.world.ganon_item_orig[player]))
outfile.write('Swords:'.ljust(line_width) + '%s\n' % self.metadata['weapons'][player])
outfile.write('\n')
@@ -3419,10 +3459,24 @@ class Spoiler(object):
outfile.write(f'{dungeon}:'.ljust(line_width) + '%s Medallion\n' % medallion)
for player in range(1, self.world.players + 1):
player_name = '' if self.world.players == 1 else str(' (Player ' + str(player) + ')')
if self.world.crystals_gt_orig[player] == 'random':
goal = self.world.custom_goals[player]['gtentry']
if goal and 'requirements' in goal and goal['requirements'][0]['condition'] != 0x00:
pass
# outfile.write(str('GT Entry Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
elif self.world.crystals_gt_orig[player] == 'random':
outfile.write(str('Crystals Required for GT' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['gt_crystals'][player])))
if self.world.crystals_ganon_orig[player] == 'random':
outfile.write(str('Crystals Required for Ganon' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player])))
goal = self.world.custom_goals[player]['ganongoal']
if goal and 'requirements' in goal and goal['requirements'][0]['condition'] != 0x00:
pass
# outfile.write(str('Ganon Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
elif self.world.crystals_ganon_orig[player] == 'random':
outfile.write(str('Crystals Required for Ganon' + player_name + ':').ljust(line_width) + '%s\n' % (str(self.metadata['ganon_crystals'][player])))
# goal = self.world.custom_goals[player]['pedgoal']
# if goal and 'requirements' in goal and goal['requirements'][0]['condition'] != 0x00:
# outfile.write(str('Pedestal Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
# goal = self.world.custom_goals[player]['murahgoal']
# if goal and 'requirements' in goal and goal['requirements'][0]['condition'] != 0x00:
# outfile.write(str('Murahdahla Sign Text' + player_name + ':').ljust(line_width) + '%s\n' % goal['goaltext'])
outfile.write('\n\nPrizes:\n\n')
for dungeon, prize in self.prizes.items():
outfile.write(str(dungeon + ':').ljust(line_width) + '%s\n' % prize)