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:39:02 -05:00
parent 8b295a74ad
commit 103e098a2e
25 changed files with 573 additions and 331 deletions

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,10 +1868,13 @@ 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):
return item.player == self.player and item.name in [dungeon_item.name for dungeon_item in self.all_items]
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):
return len(self.dungeon_items) + 1 if self.big_key_required else 0 + self.key_number
@@ -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,7 +3320,10 @@ 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')
for fairy, bottle in self.bottles.items():
@@ -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]