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

88
Fill.py
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)
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, None)
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:
@@ -193,7 +224,7 @@ def valid_key_placement(item, location, key_pool, collection_state, world):
self_locking_keys = sum(1 for d, rule in key_logic.door_rules.items() if rule.allow_small
and rule.small_location.item and rule.small_location.item.name == key_logic.small_key_name)
return key_logic.check_placement(unplaced_keys, wild_keys, reached_keys, self_locking_keys,
location if item.bigkey else None, prize_loc, cr_count)
location if item.bigkey else None, prize_loc, cr_count)
else:
return not item.is_inside_dungeon_item(world)
@@ -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)