Skip to content

Commit b9b2268

Browse files
Berserker66qwint
authored andcommitted
Core: add panic_method setting (ArchipelagoMW#3261)
1 parent 15f8ad1 commit b9b2268

File tree

3 files changed

+60
-17
lines changed

3 files changed

+60
-17
lines changed

Fill.py

+49-15
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
3535
"""
3636
:param multiworld: Multiworld to be filled.
3737
:param base_state: State assumed before fill.
38-
:param locations: Locations to be filled with item_pool
39-
:param item_pool: Items to fill into the locations
38+
:param locations: Locations to be filled with item_pool, gets mutated by removing locations that get filled.
39+
:param item_pool: Items to fill into the locations, gets mutated by removing items that get placed.
4040
:param single_player_placement: if true, can speed up placement if everything belongs to a single player
4141
:param lock: locations are set to locked as they are filled
4242
:param swap: if true, swaps of already place items are done in the event of a dead end
@@ -220,7 +220,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
220220
def remaining_fill(multiworld: MultiWorld,
221221
locations: typing.List[Location],
222222
itempool: typing.List[Item],
223-
name: str = "Remaining") -> None:
223+
name: str = "Remaining",
224+
move_unplaceable_to_start_inventory: bool = False) -> None:
224225
unplaced_items: typing.List[Item] = []
225226
placements: typing.List[Location] = []
226227
swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter()
@@ -284,13 +285,21 @@ def remaining_fill(multiworld: MultiWorld,
284285

285286
if unplaced_items and locations:
286287
# There are leftover unplaceable items and locations that won't accept them
287-
raise FillError(f"No more spots to place {len(unplaced_items)} items. Remaining locations are invalid.\n"
288-
f"Unplaced items:\n"
289-
f"{', '.join(str(item) for item in unplaced_items)}\n"
290-
f"Unfilled locations:\n"
291-
f"{', '.join(str(location) for location in locations)}\n"
292-
f"Already placed {len(placements)}:\n"
293-
f"{', '.join(str(place) for place in placements)}")
288+
if move_unplaceable_to_start_inventory:
289+
last_batch = []
290+
for item in unplaced_items:
291+
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
292+
multiworld.push_precollected(item)
293+
last_batch.append(multiworld.worlds[item.player].create_filler())
294+
remaining_fill(multiworld, locations, unplaced_items, name + " Start Inventory Retry")
295+
else:
296+
raise FillError(f"No more spots to place {len(unplaced_items)} items. Remaining locations are invalid.\n"
297+
f"Unplaced items:\n"
298+
f"{', '.join(str(item) for item in unplaced_items)}\n"
299+
f"Unfilled locations:\n"
300+
f"{', '.join(str(location) for location in locations)}\n"
301+
f"Already placed {len(placements)}:\n"
302+
f"{', '.join(str(place) for place in placements)}")
294303

295304
itempool.extend(unplaced_items)
296305

@@ -420,7 +429,8 @@ def distribute_early_items(multiworld: MultiWorld,
420429
return fill_locations, itempool
421430

422431

423-
def distribute_items_restrictive(multiworld: MultiWorld) -> None:
432+
def distribute_items_restrictive(multiworld: MultiWorld,
433+
panic_method: typing.Literal["swap", "raise", "start_inventory"] = "swap") -> None:
424434
fill_locations = sorted(multiworld.get_unfilled_locations())
425435
multiworld.random.shuffle(fill_locations)
426436
# get items to distribute
@@ -470,8 +480,29 @@ def mark_for_locking(location: Location):
470480

471481
if progitempool:
472482
# "advancement/progression fill"
473-
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, single_player_placement=multiworld.players == 1,
474-
name="Progression")
483+
if panic_method == "swap":
484+
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
485+
swap=True,
486+
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
487+
elif panic_method == "raise":
488+
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
489+
swap=False,
490+
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
491+
elif panic_method == "start_inventory":
492+
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
493+
swap=False, allow_partial=True,
494+
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
495+
if progitempool:
496+
for item in progitempool:
497+
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
498+
multiworld.push_precollected(item)
499+
filleritempool.append(multiworld.worlds[item.player].create_filler())
500+
logging.warning(f"{len(progitempool)} items moved to start inventory,"
501+
f" due to failure in Progression fill step.")
502+
progitempool[:] = []
503+
504+
else:
505+
raise ValueError(f"Generator Panic Method {panic_method} not recognized.")
475506
if progitempool:
476507
raise FillError(
477508
f"Not enough locations for progression items. "
@@ -486,7 +517,9 @@ def mark_for_locking(location: Location):
486517

487518
inaccessible_location_rules(multiworld, multiworld.state, defaultlocations)
488519

489-
remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded")
520+
remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded",
521+
move_unplaceable_to_start_inventory=panic_method=="start_inventory")
522+
490523
if excludedlocations:
491524
raise FillError(
492525
f"Not enough filler items for excluded locations. "
@@ -495,7 +528,8 @@ def mark_for_locking(location: Location):
495528

496529
restitempool = filleritempool + usefulitempool
497530

498-
remaining_fill(multiworld, defaultlocations, restitempool)
531+
remaining_fill(multiworld, defaultlocations, restitempool,
532+
move_unplaceable_to_start_inventory=panic_method=="start_inventory")
499533

500534
unplaced = restitempool
501535
unfilled = defaultlocations

Main.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
1414
from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items
1515
from Options import StartInventoryPool
16-
from Utils import __version__, output_path, version_tuple
16+
from Utils import __version__, output_path, version_tuple, get_settings
1717
from settings import get_settings
1818
from worlds import AutoWorld
1919
from worlds.generic.Rules import exclusion_rules, locality_rules
@@ -272,7 +272,7 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
272272
if multiworld.algorithm == 'flood':
273273
flood_items(multiworld) # different algo, biased towards early game progress items
274274
elif multiworld.algorithm == 'balanced':
275-
distribute_items_restrictive(multiworld)
275+
distribute_items_restrictive(multiworld, get_settings().generator.panic_method)
276276

277277
AutoWorld.call_all(multiworld, 'post_fill')
278278

settings.py

+9
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,14 @@ class Race(IntEnum):
665665
OFF = 0
666666
ON = 1
667667

668+
class PanicMethod(str):
669+
"""
670+
What to do if the current item placements appear unsolvable.
671+
raise -> Raise an exception and abort.
672+
swap -> Attempt to fix it by swapping prior placements around. (Default)
673+
start_inventory -> Move remaining items to start_inventory, generate additional filler items to fill locations.
674+
"""
675+
668676
enemizer_path: EnemizerPath = EnemizerPath("EnemizerCLI/EnemizerCLI.Core") # + ".exe" is implied on Windows
669677
player_files_path: PlayerFilesPath = PlayerFilesPath("Players")
670678
players: Players = Players(0)
@@ -673,6 +681,7 @@ class Race(IntEnum):
673681
spoiler: Spoiler = Spoiler(3)
674682
race: Race = Race(0)
675683
plando_options: PlandoOptions = PlandoOptions("bosses, connections, texts")
684+
panic_method: PanicMethod = PanicMethod("swap")
676685

677686

678687
class SNIOptions(Group):

0 commit comments

Comments
 (0)