Initial Prize Shuffle Implementation

commit c89c5d3798e2a777011e90d565d74af792330d9f
commit 4159f2e7097fca648828a60d8f6878211d0ded9e
commit a80e3a4301d69146ccfffe0f2f375adac381e165
commit d8ac588cb904152831f514d8276be4e39a43dcd0
commit 68eb75e3391631355b4f56f1dcb7e9dadadf1fdf
commit ba241b47964eadfb40ad323f87b1117598dd91a6
commit aed2821c7165822f5fd5cc1ff3f58f2af095d915
commit bd1c5d8d35ae3cae5f27f236346fff057b7b8cd7
commit f034e31cc585a1648657fc2c4850ebc0c1d8bf78

Author: codemann8 <codemann8@gmail.com>
This commit is contained in:
codemann8
2024-05-23 18:40:50 -05:00
parent 8b295a74ad
commit 103e098a2e
25 changed files with 573 additions and 331 deletions
+74 -30
View File
@@ -142,6 +142,7 @@ class World(object):
set_player_attr('compassshuffle', False)
set_player_attr('keyshuffle', 'none')
set_player_attr('bigkeyshuffle', False)
set_player_attr('prizeshuffle', 'none')
set_player_attr('restrict_boss_items', 'none')
set_player_attr('bombbag', False)
set_player_attr('flute_mode', False)
@@ -473,7 +474,8 @@ class World(object):
def push_precollected(self, item):
item.world = self
if ((item.smallkey and self.keyshuffle[item.player] != 'none')
if ((item.prize and self.prizeshuffle[item.player] != 'none')
or (item.smallkey and self.keyshuffle[item.player] != 'none')
or (item.bigkey and self.bigkeyshuffle[item.player])):
item.advancement = True
self.precollected_items.append(item)
@@ -1607,7 +1609,8 @@ class Region(object):
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]))
or (item.compass and not self.world.compassshuffle[item.player])
or (item.prize and self.world.prizeshuffle[item.player] == 'dungeon'))
# 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:
@@ -1840,6 +1843,7 @@ class Dungeon(object):
def __init__(self, name, regions, big_key, small_keys, dungeon_items, player, dungeon_id):
self.name = name
self.regions = regions
self.prize = None
self.big_key = big_key
self.small_keys = small_keys
self.dungeon_items = dungeon_items
@@ -1864,9 +1868,12 @@ class Dungeon(object):
@property
def all_items(self):
return self.dungeon_items + self.keys
return self.dungeon_items + self.keys + ([self.prize] if self.prize else [])
def is_dungeon_item(self, item):
if item.prize:
return item.player == self.player and self.prize is None and self.name not in ['Hyrule Castle', 'Agahnims Tower', 'Ganons Tower']
else:
return item.player == self.player and item.name in [dungeon_item.name for dungeon_item in self.all_items]
def count_dungeon_item(self):
@@ -2621,7 +2628,7 @@ class Boss(object):
class Location(object):
def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None, forced_item=None,
def __init__(self, player, name='', address=None, prize=False, hint_text=None, parent=None, forced_item=None,
player_address=None, note=None):
self.name = name
self.parent_region = parent
@@ -2635,7 +2642,7 @@ class Location(object):
self.forced_item = None
self.item = None
self.event = False
self.crystal = crystal
self.prize = prize
self.address = address
self.player_address = player_address
self.spot_type = 'Location'
@@ -2643,14 +2650,14 @@ class Location(object):
self.recursion_count = 0
self.staleness_count = 0
self.locked = False
self.real = not crystal
self.real = not prize
self.always_allow = lambda item, state: False
self.access_rule = lambda state: True
self.verbose_rule = None
self.item_rule = lambda item: True
self.player = player
self.skip = False
self.type = LocationType.Normal if not crystal else LocationType.Prize
self.type = LocationType.Normal if not prize else LocationType.Prize
self.pot = None
self.drop = None
self.note = note
@@ -2738,8 +2745,8 @@ class Item(object):
self.player = player
@property
def crystal(self):
return self.type == 'Crystal'
def prize(self):
return self.type == 'Prize'
@property
def smallkey(self):
@@ -2767,11 +2774,33 @@ class Item(object):
return item_dungeon
def is_inside_dungeon_item(self, world):
return ((self.smallkey and world.keyshuffle[self.player] == 'none')
return ((self.prize and world.prizeshuffle[self.player] in ['none', 'dungeon'])
or (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]))
def get_map_location(self):
if self.location:
if self.location.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]:
return self.location
else:
def explore_region(region):
explored_regions.append(region.name)
for ent in region.entrances:
if ent.parent_region is not None:
if ent.parent_region.type in [RegionType.LightWorld, RegionType.DarkWorld]:
return ent
elif ent.parent_region.name not in explored_regions:
ret = explore_region(ent.parent_region)
if ret:
return ret
return None
explored_regions = list()
return explore_region(self.location.parent_region)
return None
def __str__(self):
return str(self.__unicode__())
@@ -2983,6 +3012,7 @@ class Spoiler(object):
'compassshuffle': self.world.compassshuffle,
'keyshuffle': self.world.keyshuffle,
'bigkeyshuffle': self.world.bigkeyshuffle,
'prizeshuffle': self.world.prizeshuffle,
'boss_shuffle': self.world.boss_shuffle,
'enemy_shuffle': self.world.enemy_shuffle,
'enemy_health': self.world.enemy_health,
@@ -3023,6 +3053,13 @@ class Spoiler(object):
self.medallions[f'Misery Mire ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][0]
self.medallions[f'Turtle Rock ({self.world.get_player_names(player)})'] = self.world.required_medallions[player][1]
self.prizes = OrderedDict()
for player in range(1, self.world.players + 1):
player_name = '' if self.world.players == 1 else str(' (Player ' + str(player) + ')')
for dungeon in self.world.dungeons:
if dungeon.player == player and dungeon.prize:
self.prizes[dungeon.name + player_name] = dungeon.prize.name
self.bottles = OrderedDict()
if self.world.players == 1:
self.bottles['Waterfall Bottle'] = self.world.bottle_refills[1][0]
@@ -3033,7 +3070,7 @@ class Spoiler(object):
self.bottles[f'Pyramid Bottle ({self.world.get_player_names(player)})'] = self.world.bottle_refills[player][1]
def include_item(item):
return 'all' in self.settings or ('items' in self.settings and not item.crystal) or ('prizes' in self.settings and item.crystal)
return 'all' in self.settings or ('items' in self.settings and not item.prize) or ('prizes' in self.settings and item.prize)
self.locations = OrderedDict()
listed_locations = set()
@@ -3214,6 +3251,7 @@ class Spoiler(object):
outfile.write('Compass Shuffle:'.ljust(line_width) + '%s\n' % yn(self.metadata['compassshuffle'][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('Prize Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['prizeshuffle'][player])
outfile.write('Key Logic Algorithm:'.ljust(line_width) + '%s\n' % self.metadata['key_logic'][player])
outfile.write('\n')
outfile.write('Door Shuffle:'.ljust(line_width) + '%s\n' % self.metadata['door_shuffle'][player])
@@ -3282,6 +3320,9 @@ class Spoiler(object):
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])))
outfile.write('\n\nPrizes:\n\n')
for dungeon, prize in self.prizes.items():
outfile.write(str(dungeon + ':').ljust(line_width) + '%s\n' % prize)
if 'all' in self.settings or 'misc' in self.settings:
outfile.write('\n\nBottle Refills:\n\n')
@@ -3539,7 +3580,7 @@ dr_mode = {"basic": 1, "crossed": 2, "vanilla": 0, "partitioned": 3, 'paired': 4
er_mode = {"vanilla": 0, "simple": 1, "restricted": 2, "full": 3, "crossed": 4, "insanity": 5, 'lite': 8,
'lean': 9, "dungeonsfull": 7, "dungeonssimple": 6, "swapped": 10, "district": 11}
# byte 1: LLLW WSS? (logic, mode, sword)
# byte 1: LLLW WSSB (logic, mode, sword, bombbag)
logic_mode = {"noglitches": 0, "minorglitches": 1, "nologic": 2, "owglitches": 3, "majorglitches": 4, "hybridglitches": 5}
world_mode = {"open": 0, "standard": 1, "inverted": 2}
sword_mode = {"random": 0, "assured": 1, "swordless": 2, "vanilla": 3}
@@ -3550,12 +3591,12 @@ 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: SDMM PIII (shop, decouple doors, mixed, palettes, intensity)
# byte 3: SDMM PIII (shop, decouple doors, mixed travel, 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: TDDD PPPP (tavern shuffle, drop, pottery)
# byte 4: TDDD PPPP (tavern shuffle, drop, pottery)
# dropshuffle reserves 2 bits, pottery needs 4)
drop_shuffle_mode = {'none': 0, 'keys': 1, 'underworld': 2}
pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'cavekeys': 6, 'reduced': 7,
@@ -3564,17 +3605,17 @@ pottery_mode = {'none': 0, 'keys': 2, 'lottery': 3, 'dungeon': 4, 'cave': 5, 'ca
# byte 5: SCCC CTTX (self-loop doors, crystals gt, ctr2, experimental)
counter_mode = {"default": 0, "off": 1, "on": 2, "pickup": 3}
# byte 6: ?CCC CPAA (crystals ganon, pyramid, access
# byte 6: LCCC CPAA (shuffle links, crystals ganon, pyramid, access
access_mode = {"items": 0, "locations": 1, "none": 2}
# byte 7: B?MC DDEE (big, ?, maps, compass, door_type, enemies)
# byte 7: B?MC DDPP (big, ?, maps, compass, door_type, prize shuffle)
door_type_mode = {'original': 0, 'big': 1, 'all': 2, 'chaos': 3}
enemy_mode = {"none": 0, "shuffled": 1, "chaos": 2, "random": 2, "legacy": 3}
prizeshuffle_mode = {'none': 0, 'dungeon': 1, 'wild': 3}
# byte 8: HHHD DPBS (enemy_health, enemy_dmg, potshuffle, bomb logic, shuffle links)
# potshuffle decprecated, now unused
# byte 8: HHHD DPEE (enemy_health, enemy_dmg, potshuffle, enemies)
e_health = {"default": 0, "easy": 1, "normal": 2, "hard": 3, "expert": 4}
e_dmg = {"default": 0, "shuffled": 1, "random": 2}
enemy_mode = {"none": 0, "shuffled": 1, "chaos": 2, "random": 2, "legacy": 3}
# byte 9: RRAA ABBB (restrict boss mode, algorithm, boss shuffle)
rb_mode = {"none": 0, "mapcompass": 1, "dungeon": 2}
@@ -3593,9 +3634,9 @@ 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, 'off': 0, 'wild': 1, 'on': 1, 'universal': 2} # reserved 8 modes?
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3} # reserved 8 modes?
take_any_mode = {'none': 0, 'random': 1, 'fixed': 2}
bow_mode = {'progressive': 0, 'silvers': 1, 'retro': 2, 'retro_silvers': 3}
keyshuffle_mode = {'none': 0, 'off': 0, 'wild': 1, 'on': 1, 'universal': 2}
# additions
# byte 14: POOT TKKK (pseudoboots, overworld_map, trap_door_mode, key_logic_algo)
@@ -3617,7 +3658,7 @@ class Settings(object):
(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]] << 1),
| (sword_mode[w.swords[p]] << 1) | (0x1 if w.bombbag[p] else 0),
(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),
@@ -3633,15 +3674,15 @@ class Settings(object):
| ((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)
(0x80 if w.shufflelinks[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]],
(0x80 if w.bigkeyshuffle[p] else 0)
| (0x20 if w.mapshuffle[p] else 0) | (0x10 if w.compassshuffle[p] else 0)
| (door_type_mode[w.door_type_mode[p]] << 2) | (enemy_mode[w.enemy_shuffle[p]]),
| (door_type_mode[w.door_type_mode[p]] << 2) | prizeshuffle_mode[w.prizeshuffle[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),
(e_health[w.enemy_health[p]] << 5) | (e_dmg[w.enemy_damage[p]] << 3)
| (0x4 if w.potshuffle[p] else 0) | (enemy_mode[w.enemy_shuffle[p]]),
(rb_mode[w.restrict_boss_items[p]] << 6) | (algo_mode[w.algorithm] << 3) | (boss_mode[w.boss_shuffle[p]]),
@@ -3675,15 +3716,19 @@ class Settings(object):
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]
args.bombbag[p] = True if settings[1] & 0x1 else False
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[6] & 0x3]
# 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.decoupledoors[p] = True if settings[3] & 0x40 else False
args.mixed_travel[p] = r(mixed_travel_mode)[(settings[3] & 0x30) >> 4]
@@ -3701,22 +3746,21 @@ class Settings(object):
args.crystals_gt[p] = "random" if cgt == 8 else cgt
args.experimental[p] = True if settings[5] & 0x1 else False
args.shufflelinks[p] = True if settings[6] & 0x80 else False
cgan = (settings[6] & 0x78) >> 3
args.crystals_ganon[p] = "random" if cgan == 8 else cgan
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.door_type_mode[p] = r(door_type_mode)[(settings[7] & 0xc) >> 2]
args.shuffleenemies[p] = r(enemy_mode)[settings[7] & 0x3]
args.prizeshuffle[p] = r(prizeshuffle_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
args.shuffleenemies[p] = r(enemy_mode)[settings[8] & 0x3]
if len(settings) > 9:
args.restrict_boss_items[p] = r(rb_mode)[(settings[9] & 0xC0) >> 6]
+2 -1
View File
@@ -135,7 +135,7 @@ def parse_cli(argv, no_defaults=False):
'ow_terrain', 'ow_crossed', 'ow_keepsimilar', 'ow_mixed', 'ow_whirlpool', 'ow_fluteshuffle',
'flute_mode', 'bow_mode', 'take_any', 'boots_hint',
'shuffle', 'door_shuffle', 'intensity', 'crystals_ganon', 'crystals_gt', 'openpyramid',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'startinventory',
'mapshuffle', 'compassshuffle', 'keyshuffle', 'bigkeyshuffle', 'prizeshuffle', 'startinventory',
'usestartinventory', 'bombbag', 'shuffleganon', 'overworld_map', 'restrict_boss_items',
'triforce_pool_min', 'triforce_pool_max', 'triforce_goal_min', 'triforce_goal_max', 'triforce_max_difference',
'triforce_min_difference', 'triforce_goal', 'triforce_pool', 'shufflelinks', 'shuffletavern',
@@ -223,6 +223,7 @@ def parse_settings():
"compassshuffle": False,
"keyshuffle": "none",
"bigkeyshuffle": False,
"prizeshuffle": "none",
"keysanity": False,
"door_shuffle": "vanilla",
"intensity": 3,
+1 -1
View File
@@ -1629,7 +1629,7 @@ def refine_hints(dungeon_builders):
for name, builder in dungeon_builders.items():
for region in builder.master_sector.regions:
for location in region.locations:
if not location.event and '- Boss' not in location.name and '- Prize' not in location.name and location.name != 'Sanctuary':
if not location.event and '- Boss' not in location.name and not location.prize and location.name != 'Sanctuary':
if location.type == LocationType.Pot and location.pot:
hint_text = ('under a block' if location.pot.flags & PotFlags.Block else 'in a pot')
location.hint_text = f'{hint_text} {dungeon_hints[name]}'
+10 -8
View File
@@ -11,7 +11,7 @@ from typing import List
from BaseClasses import DoorType, Direction, CrystalBarrier, RegionType, Polarity, PolSlot, flooded_keys, Sector
from BaseClasses import Hook, hook_from_door, Door
from Regions import dungeon_events, flooded_keys_reverse
from Regions import location_events, flooded_keys_reverse
from Dungeons import split_region_starts
from RoomData import DoorKind
@@ -884,19 +884,19 @@ class ExplorationState(object):
if key_checks and location not in self.found_locations:
if location.forced_item and 'Small Key' in location.item.name:
self.key_locations += 1
if location.name not in dungeon_events and '- Prize' not in location.name and location.name not in ['Agahnim 1', 'Agahnim 2']:
if location.name not in location_events and not location.prize:
self.ttl_locations += 1
if location not in self.found_locations:
self.found_locations.append(location)
if not bk_flag and (not location.forced_item or 'Big Key' in location.item.name):
self.bk_found.add(location)
if location.name in dungeon_events and location.name not in self.events:
if location.name in location_events and location.name not in self.events:
if self.flooded_key_check(location):
self.perform_event(location.name, key_region)
if location.name in flooded_keys_reverse.keys() and self.location_found(
flooded_keys_reverse[location.name]):
self.perform_event(flooded_keys_reverse[location.name], key_region)
if '- Prize' in location.name:
if location.prize:
self.prize_received = True
def flooded_key_check(self, location):
@@ -1096,7 +1096,7 @@ def count_locations_exclude_big_chest(locations, world, player):
def prize_or_event(loc):
return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
return loc.name in location_events or loc.prize
def reserved_location(loc, world, player):
@@ -1557,13 +1557,13 @@ def define_sector_features(sectors):
for sector in sectors:
for region in sector.regions:
for loc in region.locations:
if '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']:
if loc.prize or loc.name in ['Agahnim 1', 'Agahnim 2']:
pass
elif loc.forced_item and 'Small Key' in loc.item.name:
sector.key_only_locations += 1
elif loc.forced_item and loc.forced_item.bigkey:
sector.bk_provided = True
elif loc.name not in dungeon_events and not loc.forced_item:
elif loc.name not in location_events and not loc.forced_item:
sector.chest_locations += 1
sector.chest_location_set.add(loc.name)
if '- Big Chest' in loc.name or loc.name in ["Hyrule Castle - Zelda's Chest",
@@ -1773,6 +1773,8 @@ def build_orig_location_set(dungeon_map):
def requested_dungeon_items(world, player):
num = 0
if world.prizeshuffle[player] == 'dungeon':
num += 1
if not world.bigkeyshuffle[player]:
num += 1
if not world.compassshuffle[player]:
@@ -4055,7 +4057,7 @@ def calc_door_equation(door, sector, look_for_entrance, sewers_flag=None):
crystal_barrier = CrystalBarrier.Either
# todo: backtracking from double switch with orange on--
for loc in region.locations:
if loc.name in dungeon_events:
if loc.name in location_events:
found_events.add(loc.name)
for d in event_doors:
if loc.name == d.req_event:
+26 -25
View File
@@ -34,16 +34,16 @@ def create_dungeons(world, player):
world.dungeons += [ES, EP, DP, ToH, AT, PoD, TT, SW, SP, IP, MM, TR, GT]
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
'Desert Palace - Prize': [0x1559B, 0x1559C, 0x1559D, 0x1559E],
'Tower of Hera - Prize': [0x155C5, 0x1107A, 0x10B8C],
'Palace of Darkness - Prize': [0x155B8],
'Swamp Palace - Prize': [0x155B7],
'Thieves\' Town - Prize': [0x155C6],
'Skull Woods - Prize': [0x155BA, 0x155BB, 0x155BC, 0x155BD, 0x15608, 0x15609, 0x1560A, 0x1560B],
'Ice Palace - Prize': [0x155BF],
'Misery Mire - Prize': [0x155B9],
'Turtle Rock - Prize': [0x155C7, 0x155A7, 0x155AA, 0x155AB]}
dungeon_music_addresses = {'Eastern Palace': [0x1559A],
'Desert Palace': [0x1559B, 0x1559C, 0x1559D, 0x1559E],
'Tower of Hera': [0x155C5, 0x1107A, 0x10B8C],
'Palace of Darkness': [0x155B8],
'Swamp Palace': [0x155B7],
'Thieves Town': [0x155C6],
'Skull Woods': [0x155BA, 0x155BB, 0x155BC, 0x155BD, 0x15608, 0x15609, 0x1560A, 0x1560B],
'Ice Palace': [0x155BF],
'Misery Mire': [0x155B9],
'Turtle Rock': [0x155C7, 0x155A7, 0x155AA, 0x155AB]}
hyrule_castle_regions = [
'Hyrule Castle Lobby', 'Hyrule Castle West Lobby', 'Hyrule Castle East Lobby', 'Hyrule Castle East Hall',
@@ -270,7 +270,7 @@ flexible_starts = {
class DungeonInfo:
def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize, midx):
def __init__(self, free, keys, bk, map, compass, bk_drop, drops, prize, dungeon_idx, extra_map_idx):
# todo reduce static maps ideas: prize, bk_name, sm_name, cmp_name, map_name):
self.free_items = free
self.key_num = keys
@@ -281,23 +281,24 @@ class DungeonInfo:
self.key_drops = drops
self.prize = prize
self.map_index = midx
self.dungeon_index = dungeon_idx
self.extra_map_index = extra_map_idx
dungeon_table = {
'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None, 0xc),
'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, 'Eastern Palace - Prize', 0x0),
'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, 'Desert Palace - Prize', 0x2),
'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, 'Tower of Hera - Prize', 0x1),
'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None, 0xb),
'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, 'Palace of Darkness - Prize', 0x3),
'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, 'Swamp Palace - Prize', 0x9),
'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, 'Skull Woods - Prize', 0x4),
'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, "Thieves' Town - Prize", 0x6),
'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, 'Ice Palace - Prize', 0x8),
'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, 'Misery Mire - Prize', 0x7),
'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, 'Turtle Rock - Prize', 0x5),
'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None, 0xa),
'Hyrule Castle': DungeonInfo(6, 1, False, True, False, True, 3, None, 0x00, 0x00),
'Eastern Palace': DungeonInfo(3, 0, True, True, True, False, 2, [0x1209D, 0x53F3A, 0x53F3B, 0x180052, 0x180070, 0x186FE2], 0x04, None),
'Desert Palace': DungeonInfo(2, 1, True, True, True, False, 3, [0x1209E, 0x53F3C, 0x53F3D, 0x180053, 0x180072, 0x186FE3], 0x06, 0x12),
'Tower of Hera': DungeonInfo(2, 1, True, True, True, False, 0, [0x120A5, 0x53F4A, 0x53F4B, 0x18005A, 0x180071, 0x186FEA], 0x14, None),
'Agahnims Tower': DungeonInfo(0, 2, False, False, False, False, 2, None, 0x08, None),
'Palace of Darkness': DungeonInfo(5, 6, True, True, True, False, 0, [0x120A1, 0x53F22, 0x53F43, 0x180056, 0x180073, 0x186FE6], 0x0C, None),
'Swamp Palace': DungeonInfo(6, 1, True, True, True, False, 5, [0x120A0, 0x53F40, 0x53F41, 0x180055, 0x180079, 0x186FE5], 0x0A, None),
'Skull Woods': DungeonInfo(2, 3, True, True, True, False, 2, [0x120A3, 0x53F46, 0x53F47, 0x180058, 0x180074, 0x186FE8], 0x10, 0x20),
'Thieves Town': DungeonInfo(4, 1, True, True, True, False, 2, [0x120A6, 0x53F4C, 0x53F4D, 0x18005B, 0x180076, 0x186FEB], 0x16, None),
'Ice Palace': DungeonInfo(3, 2, True, True, True, False, 4, [0x120A4, 0x53F48, 0x53F49, 0x180059, 0x180078, 0x186FE9], 0x12, None),
'Misery Mire': DungeonInfo(2, 3, True, True, True, False, 3, [0x120A2, 0x53F44, 0x53F45, 0x180057, 0x180077, 0x186FE7], 0x0E, None),
'Turtle Rock': DungeonInfo(5, 4, True, True, True, False, 2, [0x120A7, 0x53F4E, 0x53F4F, 0x18005C, 0x180075, 0x186FEC], 0x18, 0x3E),
'Ganons Tower': DungeonInfo(20, 4, True, True, True, False, 4, None, 0x1A, None),
}
+1 -1
View File
@@ -316,7 +316,7 @@ Library: near books
Potion Shop: near potions
Spike Cave: beyond spikes
Mimic Cave: in a cave of mimicry
Chest Game: as a prize
Chest Game: as a game reward
Chicken House: near poultry
Aginah's Cave: with Aginah
Ice Rod Cave: in a frozen cave
+22 -20
View File
@@ -2675,11 +2675,16 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d),
'Desert Palace Entrance (North)': (0x0e1, 0xba0), 'Desert Palace Entrance (East)': (0x191, 0xca0),
'Eastern Palace': (0xf31, 0x620), 'Tower of Hera': (0x8D0, 0x080),
'Hyrule Castle Entrance (South)': (0x820, 0x730), 'Hyrule Castle Entrance (West)': (0x740, 0x5D0),
'Hyrule Castle Entrance (East)': (0x8f0, 0x5D0), 'Inverted Pyramid Entrance': (0x6C0, 0x5D0),
'Hyrule Castle Entrance (East)': (0x8f0, 0x5D0),
'Agahnims Tower': (0x820, 0x5D0),
'Thieves Town': (0x1d0, 0x780), 'Skull Woods First Section Door': (0x240, 0x280),
'Skull Woods Second Section Door (East)': (0x1a0, 0x240),
'Skull Woods Second Section Door (West)': (0x0c0, 0x1c0), 'Skull Woods Final Section': (0x082, 0x0b0),
'Thieves Town': (0x1d0, 0x780), 'Skull Woods First Section Door': (0x2e0, 0x280),
'Skull Woods Second Section Door (East)': (0x200, 0x240),
'Skull Woods Second Section Door (West)': (0x0c0, 0x1c0),
'Skull Woods Final Section': (0x082, 0x0b0),
'Skull Woods First Section Hole (West)': (0x200, 0x2b0),
'Skull Woods First Section Hole (East)': (0x340, 0x2e0),
'Skull Woods First Section Hole (North)': (0x320, 0x1e0),
'Skull Woods Second Section Hole': (0x0f0, 0x0b0),
'Ice Palace': (0xca0, 0xda0),
'Misery Mire': (0x100, 0xca0),
'Palace of Darkness': (0xf40, 0x620), 'Swamp Palace': (0x759, 0xED0),
@@ -2687,16 +2692,23 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d),
'Dark Death Mountain Ledge (West)': (0xb80, 0x180),
'Dark Death Mountain Ledge (East)': (0xc80, 0x180),
'Turtle Rock Isolated Ledge Entrance': (0xc00, 0x240),
'Hyrule Castle Secret Entrance Drop': (0x9D0, 0x680),
'Hyrule Castle Secret Entrance Stairs': (0x8D0, 0x700),
'Kakariko Well Drop': (0x030, 0x680),
'Kakariko Well Cave': (0x060, 0x680),
'Bat Cave Cave': (0x540, 0x8f0),
'Bat Cave Drop': (0x520, 0x8f0),
'Bat Cave Cave': (0x560, 0x940),
'Elder House (East)': (0x2b0, 0x6a0),
'Elder House (West)': (0x230, 0x6a0),
'North Fairy Cave Drop': (0xa40, 0x500),
'North Fairy Cave': (0xa80, 0x440),
'Lost Woods Hideout Drop': (0x290, 0x200),
'Lost Woods Hideout Stump': (0x240, 0x280),
'Lumberjack Tree Cave': (0x4e0, 0x004),
'Lumberjack Tree Tree': (0x4e0, 0x140),
'Lumberjack Tree Cave': (0x560, 0x004),
'Two Brothers House (East)': (0x200, 0x0b60),
'Two Brothers House (West)': (0x180, 0x0b60),
'Sanctuary Grave': (0x820, 0x4c0),
'Sanctuary': (0x720, 0x4a0),
'Old Man Cave (West)': (0x580, 0x2c0),
'Old Man Cave (East)': (0x620, 0x2c0),
@@ -2721,17 +2733,13 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d),
'Hookshot Cave': (0xc80, 0x0c0),
'Hookshot Cave Back Entrance': (0xcf0, 0x004),
'Ganons Tower': (0x8D0, 0x080),
'Pyramid Hole': (0x820, 0x680),
'Inverted Pyramid Hole': (0x820, 0x680),
'Pyramid Entrance': (0x640, 0x7c0),
'Skull Woods First Section Hole (West)': None,
'Skull Woods First Section Hole (East)': None,
'Skull Woods First Section Hole (North)': None,
'Skull Woods Second Section Hole': None,
'Pyramid Hole': None,
'Inverted Pyramid Hole': None,
'Inverted Pyramid Entrance': (0x6C0, 0x5D0),
'Waterfall of Wishing': (0xe80, 0x280),
'Dam': (0x759, 0xED0),
'Blinds Hideout': (0x190, 0x6c0),
'Hyrule Castle Secret Entrance Drop': None,
'Bonk Fairy (Light)': (0x740, 0xa80),
'Lake Hylia Fairy': (0xd40, 0x9f0),
'Light Hype Fairy': (0x940, 0xc80),
@@ -2743,11 +2751,8 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d),
'Sahasrahlas Hut': (0xcf0, 0x6c0),
'Lake Hylia Shop': (0xbc0, 0xc00),
'Capacity Upgrade': (0xca0, 0xda0),
'Kakariko Well Drop': None,
'Blacksmiths Hut': (0x4a0, 0x880),
'Bat Cave Drop': None,
'Sick Kids House': (0x220, 0x880),
'North Fairy Cave Drop': None,
'Lost Woods Gamble': (0x240, 0x080),
'Fortune Teller (Light)': (0x2c0, 0x4c0),
'Snitch Lady (East)': (0x310, 0x7a0),
@@ -2756,8 +2761,6 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d),
'Tavern (Front)': (0x270, 0x980),
'Light World Bomb Hut': (0x070, 0x980),
'Kakariko Shop': (0x170, 0x980),
'Lost Woods Hideout Drop': None,
'Lumberjack Tree Tree': None,
'Cave 45': (0x440, 0xca0), 'Graveyard Cave': (0x8f0, 0x430),
'Checkerboard Cave': (0x260, 0xc00),
'Mini Moldorm Cave': (0xa40, 0xe80),
@@ -2769,7 +2772,6 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d),
'Bonk Rock Cave': (0x5f0, 0x460),
'Library': (0x270, 0xaa0),
'Potion Shop': (0xc80, 0x4c0),
'Sanctuary Grave': None,
'Hookshot Fairy': (0xd00, 0x180),
'Pyramid Fairy': (0x740, 0x740),
'East Dark World Hint': (0xf60, 0xb00),
@@ -2798,6 +2800,6 @@ ow_prize_table = {'Links House': (0x8b1, 0xb2d),
'Mimic Cave': (0xc80, 0x180),
'Big Bomb Shop': (0x8b1, 0xb2d),
'Dark Lake Hylia Shop': (0xa40, 0xc40),
'Lumberjack House': (0x4e0, 0x0d0),
'Lumberjack House': (0x580, 0x100),
'Lake Hylia Fortune Teller': (0xa40, 0xc40),
'Kakariko Gamble Game': (0x2f0, 0xaf0)}
+62 -24
View File
@@ -14,14 +14,19 @@ from source.item.FillUtil import filter_special_locations, valid_pot_items
def get_dungeon_item_pool(world):
return [item for dungeon in world.dungeons for item in dungeon.all_items if item.location is None]
dungeon_items = [item for dungeon in world.dungeons for item in dungeon.all_items if item.location is None]
for player in range(1, world.players+1):
if world.prizeshuffle[player] != 'none':
dungeon_items.extend(ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'], player))
return dungeon_items
def promote_dungeon_items(world):
world.itempool += get_dungeon_item_pool(world)
for item in world.get_items():
if item.smallkey or item.bigkey:
if item.smallkey or item.bigkey or item.prize:
item.advancement = True
elif item.map or item.compass:
item.priority = True
@@ -38,36 +43,62 @@ def fill_dungeons_restrictive(world, shuffled_locations):
# with shuffled dungeon items they are distributed as part of the normal item pool
for item in world.get_items():
if ((item.smallkey and world.keyshuffle[item.player] != 'none')
if ((item.prize and world.prizeshuffle[item.player] != 'none')
or (item.smallkey and world.keyshuffle[item.player] != 'none')
or (item.bigkey and world.bigkeyshuffle[item.player])):
item.advancement = True
elif (item.map and world.mapshuffle[item.player]) or (item.compass and world.compassshuffle[item.player]):
item.priority = True
dungeon_items = [item for item in get_dungeon_item_pool(world) if item.is_inside_dungeon_item(world)]
bigs, smalls, others = [], [], []
bigs, smalls, prizes, others = [], [], [], []
for i in dungeon_items:
(bigs if i.bigkey else smalls if i.smallkey else others).append(i)
(bigs if i.bigkey else smalls if i.smallkey else prizes if i.prize else others).append(i)
unplaced_smalls = list(smalls)
for i in world.itempool:
if i.smallkey and world.keyshuffle[i.player] != 'none':
unplaced_smalls.append(i)
def fill(base_state, items, key_pool):
fill_restrictive(world, base_state, shuffled_locations, items, key_pool, True)
def fill(base_state, items, locations, key_pool=None):
fill_restrictive(world, base_state, locations, items, key_pool, True)
all_state_base = world.get_all_state()
big_state_base = all_state_base.copy()
for x in smalls + others:
for x in smalls + prizes + others:
big_state_base.collect(x, True)
fill(big_state_base, bigs, unplaced_smalls)
fill(big_state_base, bigs, shuffled_locations, unplaced_smalls)
random.shuffle(shuffled_locations)
small_state_base = all_state_base.copy()
for x in others:
for x in prizes + others:
small_state_base.collect(x, True)
fill(small_state_base, smalls, unplaced_smalls)
fill(small_state_base, smalls, shuffled_locations, unplaced_smalls)
prizes_copy = prizes.copy()
for attempt in range(15):
try:
random.shuffle(prizes)
random.shuffle(shuffled_locations)
fill(all_state_base, others, None)
prize_state_base = all_state_base.copy()
for x in others:
prize_state_base.collect(x, True)
fill(prize_state_base, prizes, shuffled_locations)
except FillError as e:
logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times", e, 14 - attempt)
prizes = prizes_copy.copy()
for dungeon in world.dungeons:
if world.prizeshuffle[dungeon.player] == 'dungeon':
dungeon.prize = None
for prize in prizes:
if prize.location:
prize.location.item = None
prize.location = None
continue
break
else:
raise FillError(f'Unable to place dungeon prizes {", ".join(list(map(lambda d: d.hint_text, prize_locs)))}')
random.shuffle(shuffled_locations)
fill(all_state_base, others, shuffled_locations)
def fill_restrictive(world, base_state, locations, itempool, key_pool=None, single_player_placement=False,
@@ -141,7 +172,7 @@ def fill_restrictive(world, base_state, locations, itempool, key_pool=None, sing
def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_placement, perform_access_check,
key_pool, world):
if item_to_place.smallkey or item_to_place.bigkey: # a better test to see if a key can go there
if item_to_place.smallkey or item_to_place.bigkey or item_to_place.prize: # a better test to see if a key can go there
location.item = item_to_place
location.event = True
if item_to_place.smallkey:
@@ -155,9 +186,9 @@ def verify_spot_to_fill(location, item_to_place, max_exp_state, single_player_pl
test_state.sweep_for_events()
if location.can_fill(test_state, item_to_place, perform_access_check):
if valid_key_placement(item_to_place, location, key_pool, test_state, world):
if item_to_place.crystal or valid_dungeon_placement(item_to_place, location, world):
if item_to_place.prize or valid_dungeon_placement(item_to_place, location, world):
return location
if item_to_place.smallkey or item_to_place.bigkey:
if item_to_place.smallkey or item_to_place.bigkey or item_to_place.prize:
location.item = None
location.event = False
if item_to_place.smallkey:
@@ -181,8 +212,8 @@ def valid_key_placement(item, location, key_pool, collection_state, world):
key_logic = world.key_logic[item.player][dungeon.name]
unplaced_keys = len([x for x in key_pool if x.name == key_logic.small_key_name and x.player == item.player])
prize_loc = None
if key_logic.prize_location:
prize_loc = world.get_location(key_logic.prize_location, location.player)
if key_logic.prize_location and dungeon.prize and dungeon.prize.location and dungeon.prize.location.player == item.player:
prize_loc = dungeon.prize.location
cr_count = world.crystals_needed_for_gt[location.player]
wild_keys = world.keyshuffle[item.player] != 'none'
if wild_keys:
@@ -228,16 +259,19 @@ def track_outside_keys(item, location, world):
def track_dungeon_items(item, location, world):
if location.parent_region.dungeon and not item.crystal:
if location.parent_region.dungeon and (not item.prize or world.prizeshuffle[item.player] == 'dungeon'):
layout = world.dungeon_layouts[location.player][location.parent_region.dungeon.name]
if is_dungeon_item(item, world) and item.player == location.player:
layout.dungeon_items -= 1
else:
layout.free_items -= 1
if item.prize:
location.parent_region.dungeon.prize = item
def is_dungeon_item(item, world):
return ((item.smallkey and world.keyshuffle[item.player] == 'none')
return ((item.prize and world.prizeshuffle[item.player] == 'none')
or (item.smallkey and world.keyshuffle[item.player] == 'none')
or (item.bigkey and not world.bigkeyshuffle[item.player])
or (item.compass and not world.compassshuffle[item.player])
or (item.map and not world.mapshuffle[item.player]))
@@ -250,8 +284,8 @@ def recovery_placement(item_to_place, locations, world, state, base_state, itemp
return last_ditch_placement(item_to_place, locations, world, state, base_state, itempool, key_pool,
single_player_placement)
elif world.algorithm == 'vanilla_fill':
if item_to_place.type == 'Crystal':
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
if item_to_place.prize:
possible_swaps = [x for x in state.locations_checked if x.item.prize]
return try_possible_swaps(possible_swaps, item_to_place, locations, world, base_state, itempool,
key_pool, single_player_placement)
else:
@@ -310,11 +344,15 @@ def last_ditch_placement(item_to_place, locations, world, state, base_state, ite
return 3
return 4
if item_to_place.type == 'Crystal':
possible_swaps = [x for x in state.locations_checked if x.item.type == 'Crystal']
# TODO: Verify correctness in using item player in multiworld situations
if item_to_place.prize and world.prizeshuffle[item_to_place.player] == 'none':
possible_swaps = [x for x in state.locations_checked if x.item.prize]
else:
ignored_types = ['Event']
if world.prizeshuffle[item_to_place.player] == 'none':
ignored_types.append('Prize')
possible_swaps = [x for x in state.locations_checked
if x.item.type not in ['Event', 'Crystal'] and not x.forced_item and not x.locked]
if x.item.type not in ignored_types and not x.forced_item and not x.locked]
swap_locations = sorted(possible_swaps, key=location_preference)
return try_possible_swaps(swap_locations, item_to_place, locations, world, base_state, itempool,
key_pool, single_player_placement)
+20 -43
View File
@@ -5,7 +5,7 @@ import RaceRandom as random
from BaseClasses import LocationType, Region, RegionType, Shop, ShopType, Location, CollectionState, PotItem
from EntranceShuffle import connect_entrance
from Regions import shop_to_location_table, retro_shops, shop_table_by_location, valid_pot_location
from Regions import location_events, shop_to_location_table, retro_shops, shop_table_by_location, valid_pot_location
from Fill import FillError, fill_restrictive, get_dungeon_item_pool, track_dungeon_items, track_outside_keys
from PotShuffle import vanilla_pots
from Tables import bonk_prize_lookup
@@ -240,40 +240,8 @@ def generate_itempool(world, player):
old_man.forced_item = old_man.item
old_man.skip = True
event_items = {
'Agahnim 1': 'Beat Agahnim 1',
'Agahnim 2': 'Beat Agahnim 2',
'Eastern Palace - Boss Kill': 'Beat Boss',
'Desert Palace - Boss Kill': 'Beat Boss',
'Tower of Hera - Boss Kill': 'Beat Boss',
'Palace of Darkness - Boss Kill': 'Beat Boss',
'Swamp Palace - Boss Kill': 'Beat Boss',
'Skull Woods - Boss Kill': 'Beat Boss',
'Thieves\' Town - Boss Kill': 'Beat Boss',
'Ice Palace - Boss Kill': 'Beat Boss',
'Misery Mire - Boss Kill': 'Beat Boss',
'Turtle Rock - Boss Kill': 'Beat Boss',
'Lost Old Man': 'Escort Old Man',
'Old Man Drop Off': 'Return Old Man',
'Floodgate': 'Open Floodgate',
'Big Bomb': 'Pick Up Big Bomb',
'Pyramid Crack': 'Detonate Big Bomb',
'Frog': 'Get Frog',
'Missing Smith': 'Return Smith',
'Dark Blacksmith Ruins': 'Pick Up Purple Chest',
'Middle Aged Man': 'Deliver Purple Chest',
'Trench 1 Switch': 'Trench 1 Filled',
'Trench 2 Switch': 'Trench 2 Filled',
'Swamp Drain': 'Drained Swamp',
'Turtle Medallion Pad': 'Turtle Opened',
'Attic Cracked Floor': 'Shining Light',
'Suspicious Maiden': 'Maiden Rescued',
'Revealing Light': 'Maiden Unmasked',
'Ice Block Drop': 'Convenient Block',
'Skull Star Tile': 'Hidden Pits'
}
for loc, item in event_items.items():
for loc, item in location_events.items():
if item:
set_event_item(loc, item)
if world.mode[player] == 'standard':
@@ -375,7 +343,8 @@ def generate_itempool(world, player):
world.treasure_hunt_icon[player] = 'Triforce Piece'
world.itempool.extend([item for item in get_dungeon_item_pool(world) if item.player == player
and ((item.smallkey and world.keyshuffle[player] != 'none')
and ((item.prize and world.prizeshuffle[player] == 'wild')
or (item.smallkey and world.keyshuffle[player] != 'none')
or (item.bigkey and world.bigkeyshuffle[player])
or (item.map and world.mapshuffle[player])
or (item.compass and world.compassshuffle[player]))])
@@ -486,7 +455,7 @@ def generate_itempool(world, player):
world.itempool = [beemizer(item) for item in world.itempool]
# increase pool if not enough items
ttl_locations = sum(1 for x in world.get_unfilled_locations(player) if '- Prize' not in x.name)
ttl_locations = sum(1 for x in world.get_unfilled_locations(player) if world.prizeshuffle[player] != 'none' or not x.prize)
pool_size = count_player_dungeon_item_pool(world, player)
pool_size += sum(1 for x in world.itempool if x.player == player)
@@ -725,9 +694,12 @@ def create_dynamic_bonkdrop_locations(world, player):
def fill_prizes(world, attempts=15):
from Items import prize_item_table
all_state = world.get_all_state(keys=True)
for player in range(1, world.players + 1):
crystals = ItemFactory(['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'], player)
if world.prizeshuffle[player] != 'none':
continue
crystals = ItemFactory(list(prize_item_table.keys()), player)
crystal_locations = [world.get_location('Turtle Rock - Prize', player), world.get_location('Eastern Palace - Prize', player), world.get_location('Desert Palace - Prize', player), world.get_location('Tower of Hera - Prize', player), world.get_location('Palace of Darkness - Prize', player),
world.get_location('Thieves\' Town - Prize', player), world.get_location('Skull Woods - Prize', player), world.get_location('Swamp Palace - Prize', player), world.get_location('Ice Palace - Prize', player),
world.get_location('Misery Mire - Prize', player)]
@@ -742,6 +714,8 @@ def fill_prizes(world, attempts=15):
random.shuffle(prizepool)
random.shuffle(prize_locs)
fill_restrictive(world, all_state, prize_locs, prizepool, single_player_placement=True)
for prize_loc in crystal_locations:
prize_loc.parent_region.dungeon.prize = prize_loc.item
except FillError as e:
logging.getLogger('').info("Failed to place dungeon prizes (%s). Will retry %s more times", e, attempts - attempt - 1)
for location in empty_crystal_locations:
@@ -1419,9 +1393,11 @@ def make_customizer_pool(world, player):
target_amount = max(amount, len(dungeon.small_keys))
additional_amount = target_amount - len(dungeon.small_keys)
dungeon.small_keys.extend([d_item] * additional_amount)
elif item_name.startswith('Big Key') or item_name.startswith('Map') or item_name.startswith('Compass'):
elif (item_name.startswith('Big Key') or item_name.startswith('Map') or item_name.startswith('Compass')
or item_name.startswith('Crystal') or item_name.endswith('Pendant')):
d_item = ItemFactory(item_name, player)
if ((d_item.bigkey and not world.bigkeyshuffle[player])
if ((d_item.prize and world.prizeshuffle[player] in ['none', 'dungeon'])
or (d_item.bigkey and not world.bigkeyshuffle[player])
or (d_item.compass and not world.compassshuffle[player])
or (d_item.map and not world.mapshuffle[player])):
d_name = d_item.dungeon
@@ -1566,11 +1542,11 @@ def set_default_triforce(goal, custom_goal, custom_total):
def fill_specific_items(world):
if world.customizer:
from Items import prize_item_table
placements = world.customizer.get_placements()
dungeon_pool = get_dungeon_item_pool(world)
prize_pool = []
prize_set = {'Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2',
'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6'}
prize_set = set(prize_item_table.keys())
for p in range(1, world.players + 1):
prize_pool.extend(prize_set)
if placements:
@@ -1672,7 +1648,8 @@ def get_item_and_event_flag(item, world, player, dungeon_pool, prize_set, prize_
def is_dungeon_item(item, world, player):
return ((item.startswith('Small Key') and world.keyshuffle[player] == 'none')
return (((item.startswith('Crystal') or item.endswith('Pendant')) and world.prizeshuffle[player] in ['none', 'dungeon'])
or (item.startswith('Small Key') and world.keyshuffle[player] == 'none')
or (item.startswith('Big Key') and not world.bigkeyshuffle[player])
or (item.startswith('Compass') and not world.compassshuffle[player])
or (item.startswith('Map') and not world.mapshuffle[player]))
+23 -10
View File
@@ -60,19 +60,19 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
'Progressive Sword': (True, False, 'Sword', 0x5E, 150, 'A better copy\nof your sword\nfor your time', 'the unknown sword', 'sword-wielding kid', 'sword for sale', 'fungus for some slasher', 'sword boy fights again', 'a Sword'),
'Progressive Glove': (True, False, None, 0x61, 150, 'A way to lift\nheavier things', 'and the lift upgrade', 'body-building kid', 'some glove for sale', 'fungus for gloves', 'body-building boy lifts again', 'a Glove'),
'Silver Arrows': (True, False, None, 0x58, 100, 'Do you fancy\nsilver tipped\narrows?', 'and the ganonsbane', 'ganon-killing kid', 'ganon doom for sale', 'fungus for pork', 'archer boy shines again', 'the Silver Arrows'),
'Green Pendant': (True, False, 'Crystal', [0x04, 0x38, 0x62, 0x00, 0x69, 0x37, 0x08], 999, None, None, None, None, None, None, None),
'Blue Pendant': (True, False, 'Crystal', [0x02, 0x34, 0x60, 0x00, 0x69, 0x39, 0x09], 999, None, None, None, None, None, None, None),
'Red Pendant': (True, False, 'Crystal', [0x01, 0x32, 0x60, 0x00, 0x69, 0x38, 0x0a], 999, None, None, None, None, None, None, None),
'Green Pendant': (True, False, 'Prize', 0x37, 250, 'A pendant that\nsome old guy\nwill never see', 'and the green pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'),
'Blue Pendant': (True, False, 'Prize', 0x39, 150, 'A pendant that you\'ll never get', 'and the pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'),
'Red Pendant': (True, False, 'Prize', 0x38, 150, 'A pendant that you\'ll never get', 'and the pendant', 'pendant kid', 'pendant for sale', 'fungus for pendant', 'pendant boy dabs again', 'a Prize'),
'Triforce': (True, False, None, 0x6A, 777, '\n YOU WIN!', 'and the triforce', 'victorious kid', 'victory for sale', 'fungus for the win', 'greedy boy wins game again', 'the Triforce'),
'Power Star': (True, False, None, 0x6B, 100, 'A small victory', 'and the power star', 'star-struck kid', 'star for sale', 'see stars with shroom', 'mario powers up again', 'a Power Star'),
'Triforce Piece': (True, False, None, 0x6C, 100, 'A small victory', 'and the thirdforce', 'triangular kid', 'triangle for sale', 'fungus for triangle', 'wise boy has triangle again', 'a Triforce piece'),
'Crystal 1': (True, False, 'Crystal', [0x02, 0x34, 0x64, 0x40, 0x7F, 0x20, 0x01], 999, None, None, None, None, None, None, None),
'Crystal 2': (True, False, 'Crystal', [0x10, 0x34, 0x64, 0x40, 0x79, 0x20, 0x02], 999, None, None, None, None, None, None, None),
'Crystal 3': (True, False, 'Crystal', [0x40, 0x34, 0x64, 0x40, 0x6C, 0x20, 0x03], 999, None, None, None, None, None, None, None),
'Crystal 4': (True, False, 'Crystal', [0x20, 0x34, 0x64, 0x40, 0x6D, 0x20, 0x04], 999, None, None, None, None, None, None, None),
'Crystal 5': (True, False, 'Crystal', [0x04, 0x32, 0x64, 0x40, 0x6E, 0x20, 0x05], 999, None, None, None, None, None, None, None),
'Crystal 6': (True, False, 'Crystal', [0x01, 0x32, 0x64, 0x40, 0x6F, 0x20, 0x06], 999, None, None, None, None, None, None, None),
'Crystal 7': (True, False, 'Crystal', [0x08, 0x34, 0x64, 0x40, 0x7C, 0x20, 0x07], 999, None, None, None, None, None, None, None),
'Crystal 1': (True, False, 'Prize', 0xB1, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 2': (True, False, 'Prize', 0xB4, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 3': (True, False, 'Prize', 0xB6, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 4': (True, False, 'Prize', 0xB5, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 5': (True, False, 'Prize', 0xB2, 250, 'A crystal that\nunlocks a bomb', 'and the red crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 6': (True, False, 'Prize', 0xB0, 250, 'A crystal that\nunlocks a bomb', 'and the red crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Crystal 7': (True, False, 'Prize', 0xB3, 200, 'A pretty nice,\nshiny crystal', 'and the crystal', 'crystal kid', 'crystal for sale', 'fungus for crystal', 'crystal boy sparkles again', 'a Prize'),
'Single Arrow': (False, False, None, 0x43, 3, 'A lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
'Arrows (10)': (False, False, None, 0x44, 30, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack', 'stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again', 'ten arrows'),
'Arrow Upgrade (+10)': (False, False, None, 0x54, 100, 'Increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
@@ -203,3 +203,16 @@ item_table = {'Bow': (True, False, None, 0x0B, 200, 'You have\nchosen the\narche
'Farmable Bombs': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
'Farmable Rupees': (True, False, 'Event', 999, None, None, None, None, None, None, None, None),
}
prize_item_table = {
'Green Pendant': [0x04, 0x38, 0x62, 0x00, 0x69, 0x08],
'Blue Pendant': [0x02, 0x34, 0x60, 0x00, 0x69, 0x09],
'Red Pendant': [0x01, 0x32, 0x60, 0x00, 0x69, 0x0a],
'Crystal 1': [0x02, 0x34, 0x64, 0x40, 0x7F, 0x01],
'Crystal 2': [0x10, 0x34, 0x64, 0x40, 0x79, 0x02],
'Crystal 3': [0x40, 0x34, 0x64, 0x40, 0x6C, 0x03],
'Crystal 4': [0x20, 0x34, 0x64, 0x40, 0x6D, 0x04],
'Crystal 5': [0x04, 0x32, 0x64, 0x40, 0x6E, 0x05],
'Crystal 6': [0x01, 0x32, 0x64, 0x40, 0x6F, 0x06],
'Crystal 7': [0x08, 0x34, 0x64, 0x40, 0x7C, 0x07]
}
+9 -9
View File
@@ -3,7 +3,7 @@ import logging
from collections import defaultdict, deque
from BaseClasses import DoorType, dungeon_keys, KeyRuleType, RegionType
from Regions import dungeon_events
from Regions import location_events
from Dungeons import dungeon_keys, dungeon_bigs, dungeon_table
from DungeonGenerator import ExplorationState, get_special_big_key_doors, count_locations_exclude_big_chest, prize_or_event
from DungeonGenerator import reserved_location, blind_boss_unavail
@@ -1116,7 +1116,7 @@ def location_is_bk_locked(loc, key_logic):
# todo: verfiy this code is defunct
# def prize_or_event(loc):
# return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
# return loc.name in location_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
#
#
# def reserved_location(loc, world, player):
@@ -1504,7 +1504,7 @@ def validate_key_layout_sub_loop(key_layout, state, checked_states, flat_proposa
bk_done = state.big_key_opened or num_bigs == 0 or (available_big_locations == 0 and not found_forced_bk)
# prize door should not be opened if the boss is reachable - but not reached yet
allow_for_prize_lock = (key_layout.prize_can_lock and
not any(x for x in state.found_locations if '- Prize' in x.name))
not any(x for x in state.found_locations if x.prize))
prize_done = not key_layout.prize_relevant or state.prize_doors_opened or allow_for_prize_lock
if smalls_done and bk_done and prize_done:
return False
@@ -1623,7 +1623,7 @@ def determine_prize_lock(key_layout, world, player):
elif len(state.small_doors) > 0:
open_a_door(state.small_doors[0].door, state, flat_proposal, world, player)
expand_key_state(state, flat_proposal, world, player)
if any(x for x in state.found_locations if '- Prize' in x.name):
if any(x for x in state.found_locations if x.prize):
key_layout.prize_can_lock = True
@@ -1776,7 +1776,7 @@ def create_key_counter(state, key_layout, world, player):
key_counter.key_only_locations[loc] = None
elif loc.forced_item and loc.item.name == key_layout.key_logic.bk_name:
key_counter.other_locations[loc] = None
elif loc.name not in dungeon_events:
elif loc.name not in location_events:
key_counter.free_locations[loc] = None
else:
key_counter.other_locations[loc] = None
@@ -1785,7 +1785,7 @@ def create_key_counter(state, key_layout, world, player):
key_counter.big_key_opened = state.big_key_opened
if len(state.prize_door_set) > 0 and state.prize_doors_opened:
key_counter.prize_doors_opened = True
if any(x for x in key_counter.important_locations if '- Prize' in x.name):
if any(x for x in key_counter.important_locations if x.prize):
key_counter.prize_received = True
return key_counter
@@ -1805,7 +1805,7 @@ def imp_locations_factory(world, player):
def important_location(loc, world, player):
return '- Prize' in loc.name or loc.name in imp_locations_factory(world, player) or (loc.forced_big_key())
return loc.prize or loc.name in imp_locations_factory(world, player) or (loc.forced_big_key())
def create_odd_key_counter(door, parent_counter, key_layout, world, player):
@@ -2135,9 +2135,9 @@ def validate_key_placement(key_layout, world, player):
found_keys = sum(1 for i in found_locations if i.item is not None and i.item.name == smallkey_name and i.item.player == player) + \
len(counter.key_only_locations) + keys_outside
if key_layout.prize_relevant:
found_prize = any(x for x in counter.important_locations if '- Prize' in x.name)
found_prize = any(x for x in counter.important_locations if x.prize)
if not found_prize and dungeon_table[key_layout.sector.name].prize:
prize_loc = world.get_location(dungeon_table[key_layout.sector.name].prize, player)
prize_loc = dungeon_table[key_layout.sector.name].prize.location
if key_layout.prize_relevant == 'BigBomb':
found_prize = prize_loc.item.name not in ['Crystal 5', 'Crystal 6']
elif key_layout.prize_relevant == 'GT':
+4 -1
View File
@@ -460,6 +460,7 @@ def init_world(args, fish):
world.compassshuffle = args.compassshuffle.copy()
world.keyshuffle = args.keyshuffle.copy()
world.bigkeyshuffle = args.bigkeyshuffle.copy()
world.prizeshuffle = args.prizeshuffle.copy()
world.bombbag = args.bombbag.copy()
world.flute_mode = args.flute_mode.copy()
world.bow_mode = args.bow_mode.copy()
@@ -555,6 +556,7 @@ def copy_world(world):
ret.compassshuffle = world.compassshuffle.copy()
ret.keyshuffle = world.keyshuffle.copy()
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
ret.prizeshuffle = world.prizeshuffle.copy()
ret.bombbag = world.bombbag.copy()
ret.flute_mode = world.flute_mode.copy()
ret.bow_mode = world.bow_mode.copy()
@@ -751,6 +753,7 @@ def copy_world_premature(world, player):
ret.compassshuffle = world.compassshuffle.copy()
ret.keyshuffle = world.keyshuffle.copy()
ret.bigkeyshuffle = world.bigkeyshuffle.copy()
ret.prizeshuffle = world.prizeshuffle.copy()
ret.bombbag = world.bombbag.copy()
ret.flute_mode = world.flute_mode.copy()
ret.bow_mode = world.bow_mode.copy()
@@ -889,7 +892,7 @@ def copy_dynamic_regions_and_locations(world, ret):
for location in world.dynamic_locations:
new_reg = ret.get_region(location.parent_region.name, location.parent_region.player)
new_loc = Location(location.player, location.name, location.address, location.crystal, location.hint_text, new_reg)
new_loc = Location(location.player, location.name, location.address, location.prize, location.hint_text, new_reg)
new_loc.type = location.type
new_reg.locations.append(new_loc)
+18
View File
@@ -340,6 +340,16 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
'Ganons Tower - Mini Helmasaur Key Drop': (0x3d, 0x400),
'Ganons Tower - Pre-Moldorm Chest': (0x3d, 0x40),
'Ganons Tower - Validation Chest': (0x4d, 0x10)}
location_table_boss = {'Eastern Palace - Prize': 0x2000,
'Desert Palace - Prize': 0x1000,
'Tower of Hera - Prize': 0x0020,
'Palace of Darkness - Prize': 0x0200,
'Thieves Town - Prize': 0x0010,
'Skull Woods - Prize': 0x0080,
'Swamp Palace - Prize': 0x0400,
'Ice Palace - Prize': 0x0040,
'Misery Mire - Prize': 0x0100,
'Turtle Rock - Prize': 0x0008}
location_table_npc = {'Mushroom': 0x1000,
'King Zora': 0x2,
'Sahasrahla': 0x10,
@@ -949,6 +959,14 @@ async def track_locations(ctx : Context, roomid, roomdata):
if roomdata & mask != 0:
new_check(location)
if not all([location in ctx.locations_checked for location in location_table_boss.keys()]):
boss_data = await snes_read(ctx, SAVEDATA_START + 0x472, 2)
if boss_data is not None:
boss_value = boss_data[0] | (boss_data[1] << 8)
for location, mask in location_table_boss.items():
if boss_value & mask != 0 and location not in ctx.locations_checked:
new_check(location)
ow_begin = 0x82
ow_end = 0
ow_unchecked = {}
+56 -32
View File
@@ -1124,8 +1124,8 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None
ko_hint = key_drop_data[location][2]
ret.locations.append(Location(player, location, None, False, ko_hint, ret, key_drop_data[location][3]))
else:
address, player_address, crystal, hint_text = location_table[location]
ret.locations.append(Location(player, location, address, crystal, hint_text, ret, None, player_address))
address, player_address, prize, hint_text = location_table[location]
ret.locations.append(Location(player, location, address, prize, hint_text, ret, None, player_address))
return ret
def mark_light_dark_world_regions(world, player):
@@ -1240,14 +1240,14 @@ def adjust_locations(world, player):
# player address? it is in the shop table
index += 1
setup_enemy_locations(world, player)
# disable forced prize locations
if world.prizeshuffle[player] != 'none':
for l in [name for name, data in location_table.items() if data[2]]:
location = world.get_location_unsafe(l, player)
if location:
location.prize = False
# unreal events:
for l in ['Ganon', 'Agahnim 1', 'Agahnim 2', 'Frog', 'Missing Smith', 'Dark Blacksmith Ruins', 'Middle Aged Man',
'Floodgate', 'Trench 1 Switch', 'Trench 2 Switch', 'Swamp Drain', 'Turtle Medallion Pad',
'Attic Cracked Floor', 'Suspicious Maiden', 'Revealing Light', 'Big Bomb', 'Pyramid Crack',
'Ice Block Drop', 'Lost Old Man', 'Old Man Drop Off', 'Zelda Pickup', 'Zelda Drop Off', 'Skull Star Tile',
'Eastern Palace - Boss Kill', 'Desert Palace - Boss Kill', 'Tower of Hera - Boss Kill',
'Palace of Darkness - Boss Kill', 'Swamp Palace - Boss Kill', 'Skull Woods - Boss Kill',
'Thieves\' Town - Boss Kill', 'Ice Palace - Boss Kill', 'Misery Mire - Boss Kill', 'Turtle Rock - Boss Kill']:
for l in ['Ganon', 'Zelda Pickup', 'Zelda Drop Off'] + list(location_events):
location = world.get_location_unsafe(l, player)
if location:
location.type = LocationType.Logical
@@ -1397,18 +1397,42 @@ shop_table_by_location_id = {0x400000+cnt: x for cnt, x in enumerate(flat_normal
shop_table_by_location_id = {**shop_table_by_location_id, **{0x400020+cnt: x for cnt, x in enumerate(flat_retro_shops)}}
shop_table_by_location = {y: x for x, y in shop_table_by_location_id.items()}
dungeon_events = [
'Trench 1 Switch',
'Trench 2 Switch',
'Swamp Drain',
'Attic Cracked Floor',
'Suspicious Maiden',
'Revealing Light',
'Ice Block Drop',
'Skull Star Tile',
'Zelda Pickup',
'Zelda Drop Off'
]
location_events = {
'Agahnim 1': 'Beat Agahnim 1',
'Agahnim 2': 'Beat Agahnim 2',
'Eastern Palace - Boss Kill': 'Beat Boss',
'Desert Palace - Boss Kill': 'Beat Boss',
'Tower of Hera - Boss Kill': 'Beat Boss',
'Palace of Darkness - Boss Kill': 'Beat Boss',
'Swamp Palace - Boss Kill': 'Beat Boss',
'Skull Woods - Boss Kill': 'Beat Boss',
'Thieves\' Town - Boss Kill': 'Beat Boss',
'Ice Palace - Boss Kill': 'Beat Boss',
'Misery Mire - Boss Kill': 'Beat Boss',
'Turtle Rock - Boss Kill': 'Beat Boss',
'Lost Old Man': 'Escort Old Man',
'Old Man Drop Off': 'Return Old Man',
'Floodgate': 'Open Floodgate',
'Big Bomb': 'Pick Up Big Bomb',
'Pyramid Crack': 'Detonate Big Bomb',
'Frog': 'Get Frog',
'Missing Smith': 'Return Smith',
'Dark Blacksmith Ruins': 'Pick Up Purple Chest',
'Middle Aged Man': 'Deliver Purple Chest',
'Trench 1 Switch': 'Trench 1 Filled',
'Trench 2 Switch': 'Trench 2 Filled',
'Swamp Drain': 'Drained Swamp',
'Turtle Medallion Pad': 'Turtle Opened',
'Attic Cracked Floor': 'Shining Light',
'Suspicious Maiden': 'Maiden Rescued',
'Revealing Light': 'Maiden Unmasked',
'Ice Block Drop': 'Convenient Block',
'Skull Star Tile': 'Hidden Pits',
'Zelda Pickup': None,
'Zelda Drop Off': None
}
flooded_keys_reverse = {
'Swamp Palace - Trench 1 Pot Key': 'Trench 1 Switch',
@@ -1524,7 +1548,7 @@ location_table = {'Mushroom': (0x180013, 0x186df8, False, 'in the woods'),
'Pyramid Fairy - Right': (0xe983, 0x186c17, False, 'near a fairy'),
'Brewery': (0xe9ec, 0x186c80, False, 'alone in a home'),
'C-Shaped House': (0xe9ef, 0x186c83, False, 'alone in a home'),
'Chest Game': (0xeda8, 0x186e2b, False, 'as a prize'),
'Chest Game': (0xeda8, 0x186e2b, False, 'as a game reward'),
'Bumper Cave Ledge': (0x180146, 0x186e15, False, 'on a ledge'),
'Mire Shed - Left': (0xea73, 0x186d07, False, 'near sparks'),
'Mire Shed - Right': (0xea76, 0x186d0a, False, 'near sparks'),
@@ -1665,16 +1689,16 @@ location_table = {'Mushroom': (0x180013, 0x186df8, False, 'in the woods'),
'Skull Star Tile': (None, None, False, None),
'Zelda Pickup': (None, None, False, None),
'Zelda Drop Off': (None, None, False, None),
'Eastern Palace - Prize': ([0x1209D, 0x53E76, 0x53E77, 0x180052, 0x180070, 0xC6FE, 0x186FE2], None, True, 'Eastern Palace'),
'Desert Palace - Prize': ([0x1209E, 0x53E7A, 0x53E7B, 0x180053, 0x180072, 0xC6FF, 0x186FE3], None, True, 'Desert Palace'),
'Tower of Hera - Prize': ([0x120A5, 0x53E78, 0x53E79, 0x18005A, 0x180071, 0xC706, 0x186FEA], None, True, 'Tower of Hera'),
'Palace of Darkness - Prize': ([0x120A1, 0x53E7C, 0x53E7D, 0x180056, 0x180073, 0xC702, 0x186FE6], None, True, 'Palace of Darkness'),
'Swamp Palace - Prize': ([0x120A0, 0x53E88, 0x53E89, 0x180055, 0x180079, 0xC701, 0x186FE5], None, True, 'Swamp Palace'),
'Thieves\' Town - Prize': ([0x120A6, 0x53E82, 0x53E83, 0x18005B, 0x180076, 0xC707, 0x186FEB], None, True, 'Thieves Town'),
'Skull Woods - Prize': ([0x120A3, 0x53E7E, 0x53E7F, 0x180058, 0x180074, 0xC704, 0x186FE8], None, True, 'Skull Woods'),
'Ice Palace - Prize': ([0x120A4, 0x53E86, 0x53E87, 0x180059, 0x180078, 0xC705, 0x186FE9], None, True, 'Ice Palace'),
'Misery Mire - Prize': ([0x120A2, 0x53E84, 0x53E85, 0x180057, 0x180077, 0xC703, 0x186FE7], None, True, 'Misery Mire'),
'Turtle Rock - Prize': ([0x120A7, 0x53E80, 0x53E81, 0x18005C, 0x180075, 0xC708, 0x186FEC], None, True, 'Turtle Rock'),
'Eastern Palace - Prize': (0xC6FE, 0x186E2C, True, 'with the Armos'),
'Desert Palace - Prize': (0xC6FF, 0x186E2D, True, 'with Lanmolas'),
'Tower of Hera - Prize': (0xC706, 0x186E2E, True, 'with Moldorm'),
'Palace of Darkness - Prize': (0xC702, 0x186E2F, True, 'with Helmasaur King'),
'Swamp Palace - Prize': (0xC701, 0x186E30, True, 'with Arrghus'),
'Skull Woods - Prize': (0xC704, 0x186E31, True, 'with Mothula'),
'Thieves\' Town - Prize': (0xC707, 0x186E32, True, 'with Blind'),
'Ice Palace - Prize': (0xC705, 0x186E33, True, 'with Kholdstare'),
'Misery Mire - Prize': (0xC703, 0x186E34, True, 'with Vitreous'),
'Turtle Rock - Prize': (0xC708, 0x186E35, True, 'with Trinexx'),
'Kakariko Shop - Left': (None, None, False, 'for sale in Kakariko'),
'Kakariko Shop - Middle': (None, None, False, 'for sale in Kakariko'),
'Kakariko Shop - Right': (None, None, False, 'for sale in Kakariko'),
+154 -89
View File
@@ -29,7 +29,7 @@ from Text import KingsReturn_texts, Sanctuary_texts, Kakariko_texts, Blacksmiths
from Text import LostWoods_texts, WishingWell_texts, DesertPalace_texts, MountainTower_texts, LinksHouse_texts
from Text import Lumberjacks_texts, SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from Utils import output_path, local_path, int16_as_bytes, int32_as_bytes, snes_to_pc
from Items import ItemFactory
from Items import ItemFactory, prize_item_table
from EntranceShuffle import door_addresses, exit_ids, ow_prize_table
from OverworldShuffle import default_flute_connections, flute_data
from InitialSram import InitialSram
@@ -43,7 +43,7 @@ from source.enemizer.Enemizer import write_enemy_shuffle_settings
JAP10HASH = '03a63945398191337e896e5771f77173'
RANDOMIZERBASEHASH = 'ddc8fd3a8c7265b8748c0df4382f8f0e'
RANDOMIZERBASEHASH = 'd0d67e62722fc7c2192c1f3b1cd8284b'
class JsonRom(object):
@@ -462,7 +462,6 @@ def patch_rom(world, rom, player, team, is_mystery=False):
if location.address is None or (type(location.address) is int and location.address >= 0x400000):
continue
if not location.crystal:
if location.item is not None:
# Keys in their native dungeon should use the original item code for keys
itemid = handle_native_dungeon(location, itemid)
@@ -476,18 +475,18 @@ def patch_rom(world, rom, player, team, is_mystery=False):
else:
itemid = 0x5A
rom.write_byte(location.address, itemid)
else:
for dungeon in [d for d in world.dungeons if d.player == player]:
if dungeon.prize:
# crystals
for address, value in zip(location.address, itemid):
for address, value in zip(dungeon_table[dungeon.name].prize, prize_item_table[dungeon.prize.name]):
rom.write_byte(address, value)
# patch music
music_addresses = dungeon_music_addresses[location.name]
if world.mapshuffle[player]:
music = random.choice([0x11, 0x16])
else:
music = 0x11 if 'Pendant' in location.item.name else 0x16
for music_address in music_addresses:
music = 0x11 if 'Pendant' in dungeon.prize.name else 0x16
for music_address in dungeon_music_addresses[dungeon.name]:
rom.write_byte(music_address, music)
if world.mapshuffle[player]:
@@ -724,7 +723,8 @@ def patch_rom(world, rom, player, team, is_mystery=False):
or (l.type == LocationType.Drop and not l.forced_item)
or (l.type == LocationType.Normal and not l.forced_item)
or (l.type == LocationType.Bonk and not l.forced_item)
or (l.type == LocationType.Shop and world.shopsanity[player]))]
or (l.type == LocationType.Shop and world.shopsanity[player])
or (l.type == LocationType.Prize and not l.prize))]
valid_loc_by_dungeon = valid_dungeon_locations(valid_locations)
# fix hc big key problems (map and compass too)
@@ -1077,7 +1077,11 @@ def patch_rom(world, rom, player, team, is_mystery=False):
0xFF, 0xFF, 0xFF, 0xFF, # end of table sentinel
])
# item GFX changes
# item property changes
if world.prizeshuffle[player] != 'none':
# allows prizes to contribute to collection rate
write_int16s(rom, snes_to_pc(0x22C06E), [0x01]*3) # pendants
write_int16s(rom, snes_to_pc(0x22C160), [0x81]*7) # crystals
if world.bombbag[player]:
rom.write_byte(snes_to_pc(0x22C8A4), 0xE0) # use new bomb bag gfx
rom.write_byte(snes_to_pc(0x22BD52), 0x02)
@@ -1207,6 +1211,8 @@ def patch_rom(world, rom, player, team, is_mystery=False):
rom.write_byte(0x1800A1, 0x01) # enable overworld screen transition draining for water level inside swamp
rom.write_byte(0x180174, 0x01 if world.fix_fake_world[player] else 0x00)
rom.write_byte(0x18017E, 0x01) # Fairy fountains only trade in bottles
# fix for allowing prize itemgets being able to update the HUD (buying prizes in shops and updating rupees)
rom.write_bytes(snes_to_pc(0x0799FB), [0x80, 0x11, 0xEA])
# Starting equipment
if world.pseudoboots[player]:
@@ -1262,94 +1268,152 @@ def patch_rom(world, rom, player, team, is_mystery=False):
# Bitfield - enable text box to show with free roaming items
#
# ---o bmcs
# --po bmcs
# p - enabled for non-prize crystals
# o - enabled for outside dungeon items
# b - enabled for inside big keys
# m - enabled for inside maps
# c - enabled for inside compasses
# s - enabled for inside small keys
rom.write_byte(0x18016A, 0x10 | ((0x01 if world.keyshuffle[player] == 'wild' else 0x00)
rom.write_byte(0x18016A, 0x10 | ((0x20 if world.prizeshuffle[player] == 'wild' else 0x00)
| (0x01 if world.keyshuffle[player] == 'wild' else 0x00)
| (0x02 if world.compassshuffle[player] else 0x00)
| (0x04 if world.mapshuffle[player] else 0x00)
| (0x08 if world.bigkeyshuffle[player] else 0x00))) # free roaming item text boxes
rom.write_byte(0x18003B, 0x01 if world.mapshuffle[player] else 0x00) # maps showing crystals on overworld
# compasses showing dungeon count
compass_mode = 0x00
compass_mode = 0x80 if world.compassshuffle[player] else 0x00
if world.clock_mode != 'none' or world.dungeon_counters[player] == 'off':
compass_mode = 0x00 # Currently must be off if timer is on, because they use same HUD location
rom.write_byte(0x18003C, 0x00)
pass
elif world.dungeon_counters[player] == 'on':
compass_mode = 0x02 # always on
compass_mode |= 0x02 # always on
elif (world.compassshuffle[player] or world.doorShuffle[player] != 'vanilla' or world.dropshuffle[player] != 'none'
or world.dungeon_counters[player] == 'pickup' or world.pottery[player] not in ['none', 'cave']):
compass_mode = 0x01 # show on pickup
if (world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default') or world.owMixed[player]:
compass_mode |= 0x80 # turn on locating dungeons
if world.overworld_map[player] == 'compass':
compass_mode |= 0x20 # show icon if compass is collected, 0x00 for maps
if world.compassshuffle[player]:
compass_mode |= 0x40 # dungeon item that enables icon is wild
elif world.overworld_map[player] == 'map' or world.owMixed[player]:
if world.mapshuffle[player]:
compass_mode |= 0x40 # dungeon item that enables icon is wild
compass_mode |= 0x01 # show on pickup
if world.overworld_map[player] == 'map':
compass_mode |= 0x10 # show icon if map is collected
elif world.overworld_map[player] == 'compass':
compass_mode |= 0x20 # show icon if compass is collected
if world.prizeshuffle[player] == 'wild':
compass_mode |= 0x40 # show icon if boss is defeated, hide if collected
rom.write_byte(0x18003C, compass_mode)
if world.shuffle[player] != 'vanilla' and world.overworld_map[player] != 'default':
x_map_position_generic = [0x3c0, 0xbc0, 0x7c0, 0x1c0, 0x5c0, 0xdc0, 0x7c0, 0xbc0, 0x9c0, 0x3c0]
for idx, x_map in enumerate(x_map_position_generic):
rom.write_bytes(0x53df6+idx*2, int16_as_bytes(x_map))
rom.write_bytes(0x53e16+idx*2, int16_as_bytes(0xFC0))
elif world.overworld_map[player] == 'default':
def get_entrance_coords(ent):
if type(ent) is Location:
from OverworldShuffle import OWTileRegions
if ent.name == 'Hobo':
coords = (0xb80, 0xb80)
elif ent.name == 'Master Sword Pedestal':
coords = (0x06d, 0x070)
else:
owid = OWTileRegions[ent.parent_region.name]
if owid == 0x81:
coords = (0x220, 0xf40)
else:
owid = owid % 0x40
coords = (0x200 * (owid % 0x08) + 0x100, 0x200 * int(owid / 0x08) + 0x100)
if owid in [0x00, 0x03, 0x05, 0x18, 0x1b, 0x1e, 0x30, 0x35]:
coords = (coords[0] + 0x100, coords[1] + 0x100)
else:
coords = ow_prize_table[ent.name]
coords = ((0x8000 if ent.parent_region.type == RegionType.DarkWorld else 0x0000) | coords[0], coords[1])
return coords
if world.overworld_map[player] == 'default':
# disable HC/AT/GT icons
if not world.owMixed[player]:
rom.write_bytes(0x53E8A, int16_as_bytes(0xFF00)) # GT
rom.write_bytes(0x53E8C, int16_as_bytes(0xFF00)) # AT
rom.write_bytes(0x53E8E, int16_as_bytes(0xFF00)) # HC
write_int16(rom, snes_to_pc(0x0ABF52)+0x1A, 0x0000) # GT
write_int16(rom, snes_to_pc(0x0ABF52)+0x08, 0x0000) # AT
write_int16(rom, snes_to_pc(0x0ABF52)+0x00, 0x0000) # HC
for dungeon, portal_list in dungeon_portals.items():
ow_map_index = dungeon_table[dungeon].map_index
if world.shuffle[player] != 'vanilla' and world.overworld_map[player] == 'default':
dungeon_index = dungeon_table[dungeon].dungeon_index
extra_map_index = dungeon_table[dungeon].extra_map_index
map_index = max(0, dungeon_index - 2)
# write out dislocated coords
if map_index >= 0x02 and map_index < 0x18 and (world.overworld_map[player] != 'default' or world.prizeshuffle[player] == 'wild'):
owid_map = [0x1E, 0x30, 0xFF, 0x7B, 0x5E, 0x70, 0x40, 0x75, 0x03, 0x58, 0x47]
x_map_position_generic = [0x03c0, 0x0740, 0xff00, 0x03c0, 0x01c0, 0x0bc0, 0x05c0, 0x09c0, 0x0ac0, 0x07c0, 0x0dc0]
y_map_position_generic = [0xff00, 0xff00, 0xff00, 0x0fc0, 0x0fc0, 0x0fc0, 0x0fc0, 0x0fc0, 0xff00, 0x0fc0, 0x0fc0]
world_indicator = 0x0000
idx = int((map_index-2)/2)
owid = owid_map[idx]
if owid != 0xFF:
if (owid < 0x40) == (world.is_tile_swapped(owid, player)):
world_indicator = 0x8000
write_int16(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+4, world_indicator | x_map_position_generic[idx])
write_int16(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+6, y_map_position_generic[idx])
# write out icon coord data
if world.prizeshuffle[player] == 'wild' and dungeon_table[dungeon].prize:
dungeon_obj = world.get_dungeon(dungeon, player)
entrance = dungeon_obj.prize.get_map_location()
coords = get_entrance_coords(entrance)
# prize location
write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+8, coords)
if world.shuffle[player] == 'vanilla' or world.overworld_map[player] == 'default':
# TODO: I think this is logically the same as some of the vanilla stuff below
vanilla_entrances = { 'Hyrule Castle': 'Hyrule Castle Entrance (South)',
'Desert Palace': 'Desert Palace Entrance (North)',
'Skull Woods': 'Skull Woods Final Section'
}
'Skull Woods': 'Skull Woods Final Section' }
entrance_name = vanilla_entrances[dungeon] if dungeon in vanilla_entrances else dungeon
if world.is_atgt_swapped(player):
swap_entrances = { 'Agahnims Tower': 'Ganons Tower',
'Ganons Tower': 'Agahnims Tower' }
entrance_name = swap_entrances[dungeon] if dungeon in swap_entrances else entrance_name
entrance = world.get_entrance(entrance_name, player)
else:
if world.shuffle[player] != 'vanilla':
if len(portal_list) == 1:
portal_idx = 0
else:
if world.doorShuffle[player] not in ['vanilla', 'basic']:
# the random choice excludes sanctuary
portal_idx = next((i for i, elem in enumerate(portal_list)
if world.get_portal(elem, player).chosen), random.choice([1, 2, 3]))
else:
portal_idx = {'Hyrule Castle': 0, 'Desert Palace': 0, 'Skull Woods': 3, 'Turtle Rock': 3}[dungeon]
else:
if dungeon in ['Hyrule Castle', 'Agahnims Tower', 'Ganons Tower']:
portal_idx = -1
elif len(portal_list) == 1:
portal_idx = 0
else:
portal_idx = {'Desert Palace': 1, 'Skull Woods': 3, 'Turtle Rock': 0}[dungeon]
portal = world.get_portal(portal_list[0 if portal_idx == -1 else portal_idx], player)
vanilla_portal_idx = {'Hyrule Castle': 0, 'Desert Palace': 0, 'Skull Woods': 3, 'Turtle Rock': 3}
extra_map_offsets = {'Hyrule Castle': 0, 'Desert Palace': 0x12, 'Skull Woods': 0x20, 'Turtle Rock': 0x3E}
portal_idx = vanilla_portal_idx[dungeon]
offset = 0
if (world.overworld_map[player] != 'default' and world.shuffle[player] not in ['vanilla', 'dungeonssimple']
and (dungeon != 'Skull Woods' or world.shuffle[player] in ['district', 'insanity'])):
for i, elem in enumerate(portal_list):
if i != portal_idx and (elem != 'Sanctuary' or world.shuffle[player] in ['district', 'insanity']):
portal = world.get_portal(elem, player)
entrance = portal.find_portal_entrance()
world_indicator = 0x01 if entrance.parent_region.type == RegionType.DarkWorld else 0x00
coords = ow_prize_table[entrance.name]
coords = get_entrance_coords(entrance)
write_int16s(rom, snes_to_pc(0x8ABECA+extra_map_offsets[dungeon]+offset), coords)
offset += 4
portal = world.get_portal(portal_list[portal_idx], player)
entrance = portal.find_portal_entrance()
coords = get_entrance_coords(entrance)
# figure out compass entrances and what world (light/dark)
if world.overworld_map[player] != 'default' or world.owMixed[player]:
rom.write_bytes(0x53E36+ow_map_index*2, int16_as_bytes(coords[0]))
rom.write_bytes(0x53E56+ow_map_index*2, int16_as_bytes(coords[1]))
rom.write_byte(0x53EA6+ow_map_index, world_indicator)
write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6), coords)
if world.prizeshuffle[player] != 'wild' and dungeon_table[dungeon].prize:
# prize location
write_int16s(rom, snes_to_pc(0x0ABE2E)+(map_index*6)+8, coords)
# Map reveals
reveal_bytes = {
"Hyrule Castle": 0xC000,
"Eastern Palace": 0x2000,
"Desert Palace": 0x1000,
"Tower of Hera": 0x0020,
"Agahnims Tower": 0x800,
"Palace of Darkness": 0x0200,
"Thieves Town": 0x0010,
"Skull Woods": 0x0080,
"Swamp Palace": 0x0400,
"Ice Palace": 0x0040,
"Misery Mire": 0x0100,
"Turtle Rock": 0x0008,
"Ganons Tower": 0x0004
}
# in crossed doors - flip the compass exists flags
if world.doorShuffle[player] not in ['vanilla', 'basic']:
compass_exists = 0x0000
for dungeon, portal_list in dungeon_portals.items():
ow_map_index = dungeon_table[dungeon].map_index
exists_flag = any(x for x in world.get_dungeon(dungeon, player).dungeon_items if x.type == 'Compass')
rom.write_byte(0x53E96+ow_map_index, 0x1 if exists_flag else 0x0)
rom.write_byte(0x18003C, compass_mode)
dungeon_index = dungeon_table[dungeon].dungeon_index
if any(x for x in world.get_dungeon(dungeon, player).dungeon_items if x.type == 'Compass'):
compass_exists |= reveal_bytes.get(dungeon, 0x0000)
write_int16(rom, snes_to_pc(0x0ABF6E), compass_exists)
# Bitfield - enable free items to show up in menu
#
@@ -1360,34 +1424,17 @@ def patch_rom(world, rom, player, team, is_mystery=False):
# b - Big Key
# a - Small Key
#
enable_menu_map_check = world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla'
enable_menu_map_check = (world.overworld_map[player] != 'default' and world.shuffle[player] != 'vanilla') or world.prizeshuffle[player] == 'wild'
rom.write_byte(0x180045, ((0x01 if world.keyshuffle[player] == 'wild' else 0x00)
| (0x02 if world.bigkeyshuffle[player] else 0x00)
| (0x04 if world.mapshuffle[player] or enable_menu_map_check else 0x00)
| (0x08 if world.compassshuffle[player] else 0x00) # free roaming items in menu
| (0x10 if world.logic[player] == 'nologic' else 0))) # boss icon
# Map reveals
reveal_bytes = {
"Eastern Palace": 0x2000,
"Desert Palace": 0x1000,
"Tower of Hera": 0x0020,
"Palace of Darkness": 0x0200,
"Thieves Town": 0x0010,
"Skull Woods": 0x0080,
"Swamp Palace": 0x0400,
"Ice Palace": 0x0040,
"Misery Mire'": 0x0100,
"Turtle Rock": 0x0008,
}
def get_reveal_bytes(itemName):
locations = world.find_items(itemName, player)
if len(locations) < 1:
return 0x0000
location = locations[0]
if location.parent_region and location.parent_region.dungeon:
return reveal_bytes.get(location.parent_region.dungeon.name, 0x0000)
for dungeon in world.dungeons:
if dungeon.player == player and dungeon.prize and dungeon.prize.name == itemName:
return reveal_bytes.get(dungeon.name, 0x0000)
return 0x0000
write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant') if world.mapshuffle[player] else 0x0000) # Sahasrahla reveal
@@ -2160,6 +2207,8 @@ def write_strings(rom, world, player, team):
items_to_hint.extend(SmallKeys)
if world.bigkeyshuffle[player]:
items_to_hint.extend(BigKeys)
if world.prizeshuffle[player] == 'wild':
items_to_hint.extend(Prizes)
random.shuffle(items_to_hint)
hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'district', 'swapped'] else 8
hint_count += 2 if world.doorShuffle[player] not in ['vanilla', 'basic'] else 0
@@ -2273,10 +2322,14 @@ def write_strings(rom, world, player, team):
crystal5 = world.find_items('Crystal 5', player)[0]
crystal6 = world.find_items('Crystal 6', player)[0]
tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5.hint_text, crystal6.hint_text)
greenpendant = world.find_items('Green Pendant', player)[0]
tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant.hint_text
if world.prizeshuffle[player] == 'none':
(crystal5, crystal6, greenpendant) = tuple([x.parent_region.dungeon.name for x in [crystal5, crystal6, greenpendant]])
tt['bomb_shop'] = 'Big Bomb?\nMy supply is blocked until you clear %s and %s.' % (crystal5, crystal6)
tt['sahasrahla_bring_courage'] = 'I lost my family heirloom in %s' % greenpendant
else:
tt['bomb_shop'] = 'Big Bomb?\nThe crystals can be found %s and %s.' % (crystal5.hint_text, crystal6.hint_text)
tt['sahasrahla_bring_courage'] = 'My family heirloom can be found %s' % greenpendant.hint_text
tt['sign_ganons_tower'] = ('You need %d crystal to enter.' if world.crystals_needed_for_gt[player] == 1 else 'You need %d crystals to enter.') % world.crystals_needed_for_gt[player]
@@ -2966,6 +3019,18 @@ BigKeys = ['Big Key (Eastern Palace)',
'Big Key (Ganons Tower)'
]
Prizes = ['Green Pendant',
'Blue Pendant',
'Red Pendant',
'Crystal 1',
'Crystal 2',
'Crystal 3',
'Crystal 4',
'Crystal 5',
'Crystal 6',
'Crystal 7'
]
hash_alphabet = [
"Bow", "Boomerang", "Hookshot", "Bomb", "Mushroom", "Powder", "Rod", "Pendant", "Bombos", "Ether", "Quake",
"Lamp", "Hammer", "Shovel", "Ocarina", "Bug Net", "Book", "Bottle", "Potion", "Cane", "Cape", "Mirror", "Boots",
Binary file not shown.
+7
View File
@@ -387,6 +387,13 @@
"action": "store_true",
"type": "bool"
},
"prizeshuffle": {
"choices": [
"none",
"dungeon",
"wild"
]
},
"keysanity": {
"action": "store_true",
"type": "bool",
+1
View File
@@ -350,6 +350,7 @@
"compassshuffle": [ "Compasses are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
"keyshuffle": [ "Small Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
"bigkeyshuffle": [ "Big Keys are no longer restricted to their dungeons, but can be anywhere. (default: %(default)s)" ],
"prizeshuffle": [ "Prizes are no longer restricted to the bosses, but can be anywhere. (default: %(default)s)" ],
"shopsanity": ["Shop contents are shuffle in the main item pool and other items can take their place. (default: %(default)s)"],
"dropshuffle": [ "Controls how enemies drop items (default: %(default)s)",
"None: Enemies drops prize packs or keys as normal",
+4
View File
@@ -59,6 +59,10 @@
"randomizer.dungeon.smallkeyshuffle.wild": "Randomized",
"randomizer.dungeon.smallkeyshuffle.universal": "Universal",
"randomizer.dungeon.bigkeyshuffle": "Big Keys",
"randomizer.dungeon.prizeshuffle": "Prizes",
"randomizer.dungeon.prizeshuffle.none": "On Boss",
"randomizer.dungeon.prizeshuffle.dungeon": "In Dungeon",
"randomizer.dungeon.prizeshuffle.wild": "Randomized",
"randomizer.dungeon.decoupledoors": "Decouple Doors",
"randomizer.dungeon.door_self_loops": "Allow Self-Looping Spiral Stairs",
@@ -1,5 +1,17 @@
{
"widgets": {
"prizeshuffle": {
"type": "selectbox",
"default": "default",
"options": [
"none",
"dungeon",
"wild"
],
"config": {
"padx": [20,0]
}
},
"key_logic_algorithm": {
"type": "selectbox",
"default": "default",
+2
View File
@@ -138,6 +138,7 @@ class CustomSettings(object):
args.experimental[p] = get_setting(settings['experimental'], args.experimental[p])
args.collection_rate[p] = get_setting(settings['collection_rate'], args.collection_rate[p])
args.openpyramid[p] = get_setting(settings['openpyramid'], args.openpyramid[p])
args.prizeshuffle[p] = get_setting(settings['prizeshuffle'], args.prizeshuffle[p])
args.bigkeyshuffle[p] = get_setting(settings['bigkeyshuffle'], args.bigkeyshuffle[p])
args.keyshuffle[p] = get_setting(settings['keyshuffle'], args.keyshuffle[p])
args.mapshuffle[p] = get_setting(settings['mapshuffle'], args.mapshuffle[p])
@@ -320,6 +321,7 @@ class CustomSettings(object):
settings_dict[p]['experimental'] = world.experimental[p]
settings_dict[p]['collection_rate'] = world.collection_rate[p]
settings_dict[p]['openpyramid'] = world.open_pyramid[p]
settings_dict[p]['prizeshuffle'] = world.prizeshuffle[p]
settings_dict[p]['bigkeyshuffle'] = world.bigkeyshuffle[p]
settings_dict[p]['keyshuffle'] = world.keyshuffle[p]
settings_dict[p]['mapshuffle'] = world.mapshuffle[p]
+1
View File
@@ -111,6 +111,7 @@ SETTINGSTOPROCESS = {
"mapshuffle": "mapshuffle",
"compassshuffle": "compassshuffle",
"bigkeyshuffle": "bigkeyshuffle",
"prizeshuffle": "prizeshuffle",
"key_logic_algorithm": "key_logic_algorithm",
"dungeondoorshuffle": "door_shuffle",
"dungeonintensity": "intensity",
+5 -5
View File
@@ -5,7 +5,7 @@ import time
from BaseClasses import CrystalBarrier, DoorType, Hook, RegionType, Sector
from BaseClasses import hook_from_door, flooded_keys
from Regions import dungeon_events, flooded_keys_reverse
from Regions import location_events, flooded_keys_reverse
def pre_validate(builder, entrance_region_names, split_dungeon, world, player):
@@ -556,7 +556,7 @@ class ExplorationState(object):
if key_checks and location not in self.found_locations:
if location.forced_item and 'Small Key' in location.item.name:
self.key_locations += 1
if location.name not in dungeon_events and '- Prize' not in location.name and location.name not in ['Agahnim 1', 'Agahnim 2']:
if location.name not in location_events and not ('- Prize' in location.name and location.prize) and location.name not in ['Agahnim 1', 'Agahnim 2']:
self.ttl_locations += 1
if location not in self.found_locations:
self.found_locations.append(location)
@@ -568,13 +568,13 @@ class ExplorationState(object):
else:
self.bk_found.add(location)
self.re_add_big_key_doors()
if location.name in dungeon_events and location.name not in self.events:
if location.name in location_events and location.name not in self.events:
if self.flooded_key_check(location):
self.perform_event(location.name, key_region)
if location.name in flooded_keys_reverse.keys() and self.location_found(
flooded_keys_reverse[location.name]):
self.perform_event(flooded_keys_reverse[location.name], key_region)
if '- Prize' in location.name:
if location.prize:
self.prize_received = True
def flooded_key_check(self, location):
@@ -837,7 +837,7 @@ def count_locations_exclude_big_chest(locations, world, player):
def prize_or_event(loc):
return loc.name in dungeon_events or '- Prize' in loc.name or loc.name in ['Agahnim 1', 'Agahnim 2']
return loc.name in location_events or loc.prize
def reserved_location(loc, world, player):
+29 -3
View File
@@ -59,6 +59,8 @@ def create_item_pool_config(world):
if info.prize:
d_name = "Thieves' Town" if dungeon.startswith('Thieves') else dungeon
config.reserved_locations[player].add(f'{d_name} - Boss')
if world.prizeshuffle[player] != 'none':
config.reserved_locations[player].add(f'{d_name} - Prize')
for dungeon in world.dungeons:
if world.restrict_boss_items[dungeon.player] != 'none':
for item in dungeon.all_items:
@@ -118,6 +120,9 @@ def create_item_pool_config(world):
LocationGroup('bkgt').locs(mode_grouping['GT Trash'])]
for loc_name in mode_grouping['Big Chests'] + mode_grouping['Heart Containers']:
config.reserved_locations[player].add(loc_name)
if world.prizeshuffle[player] != 'none':
for loc_name in mode_grouping['Prizes']:
config.reserved_locations[player].add(loc_name)
elif world.algorithm == 'major_only':
config.location_groups = [
LocationGroup('MajorItems'),
@@ -127,6 +132,8 @@ def create_item_pool_config(world):
init_set = mode_grouping['Overworld Major'] + mode_grouping['Big Chests'] + mode_grouping['Heart Containers']
for player in range(1, world.players + 1):
groups = LocationGroup('Major').locs(init_set)
if world.prizeshuffle[player] != 'none':
groups.locations.extend(mode_grouping['Prizes'])
if world.bigkeyshuffle[player]:
groups.locations.extend(mode_grouping['Big Keys'])
if world.dropshuffle[player] != 'none':
@@ -251,21 +258,33 @@ def location_prefilled(location, world, player):
def previously_reserved(location, world, player):
if '- Boss' in location.name:
if '- Boss' in location.name or '- Prize' in location.name:
if world.restrict_boss_items[player] == 'mapcompass' and (not world.compassshuffle[player]
or not world.mapshuffle[player]):
return True
if world.restrict_boss_items[player] == 'dungeon' and (not world.compassshuffle[player]
or not world.mapshuffle[player]
or not world.bigkeyshuffle[player]
or world.keyshuffle[player] == 'none'):
or world.keyshuffle[player] == 'none'
or world.prizeshuffle[player] in ['none', 'dungeon']):
return True
return False
def massage_item_pool(world):
player_pool = defaultdict(list)
dungeon_pool = defaultdict(list)
for dungeon in world.dungeons:
if dungeon_table[dungeon.name].prize:
dungeon_pool[dungeon.player].append(dungeon)
for player in dungeon_pool:
dungeons = list(dungeon_pool[player])
random.shuffle(dungeons)
dungeon_pool[player] = dungeons
for item in world.itempool:
if item.prize:
dungeon = dungeon_pool[item.player].pop()
dungeon.prize = item
player_pool[item.player].append(item)
for dungeon in world.dungeons:
for item in dungeon.all_items:
@@ -273,7 +292,7 @@ def massage_item_pool(world):
player_pool[item.player].append(item)
player_locations = defaultdict(list)
for player in player_pool:
player_locations[player] = [x for x in world.get_unfilled_locations(player) if '- Prize' not in x.name]
player_locations[player] = [x for x in world.get_unfilled_locations(player) if not x.prize]
discrepancy = len(player_pool[player]) - len(player_locations[player])
if discrepancy:
trash_options = [x for x in player_pool[player] if x.name in trash_items]
@@ -342,6 +361,8 @@ def determine_major_items(world, player):
major_item_set = set(major_items)
if world.progressive == 'off':
pass # now what?
if world.prizeshuffle[player] not in ['none', 'dungeon']:
major_item_set.update({x for x, y in item_table.items() if y[2] == 'Prize'})
if world.bigkeyshuffle[player]:
major_item_set.update({x for x, y in item_table.items() if y[2] == 'BigKey'})
if world.keyshuffle[player] != 'none':
@@ -687,6 +708,11 @@ mode_grouping = {
'Graveyard Cave', 'Kakariko Well - Top', "Blind's Hideout - Top", 'Bonk Rock Cave', "Aginah's Cave",
'Chest Game', 'Digging Game', 'Mire Shed - Left', 'Mimic Cave'
],
'Prizes': [
'Eastern Palace - Prize', 'Desert Palace - Prize', 'Tower of Hera - Prize',
'Palace of Darkness - Prize', 'Swamp Palace - Prize', 'Skull Woods - Prize',
"Thieves' Town - Prize", 'Ice Palace - Prize', 'Misery Mire - Prize', 'Turtle Rock - Prize',
],
'Big Keys': [
'Eastern Palace - Big Key Chest', 'Ganons Tower - Big Key Chest',
'Desert Palace - Big Key Chest', 'Tower of Hera - Big Key Chest', 'Palace of Darkness - Big Key Chest',
+1
View File
@@ -106,6 +106,7 @@ def roll_settings(weights):
if 'u' in dungeon_items:
ret.keyshuffle = 'universal'
ret.bigkeyshuffle = get_choice_bool('bigkey_shuffle') if 'bigkey_shuffle' in weights else 'b' in dungeon_items
ret.prizeshuffle = get_choice('prize_shuffle')
ret.accessibility = get_choice('accessibility')
ret.restrict_boss_items = get_choice('restrict_boss_items')