Core: add panic_method setting (#3261)
This commit is contained in:
parent
20134d3b1e
commit
0ea20f3929
50
Fill.py
50
Fill.py
|
@ -35,8 +35,8 @@ def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locati
|
||||||
"""
|
"""
|
||||||
:param multiworld: Multiworld to be filled.
|
:param multiworld: Multiworld to be filled.
|
||||||
:param base_state: State assumed before fill.
|
:param base_state: State assumed before fill.
|
||||||
:param locations: Locations to be filled with item_pool
|
:param locations: Locations to be filled with item_pool, gets mutated by removing locations that get filled.
|
||||||
:param item_pool: Items to fill into the locations
|
:param item_pool: Items to fill into the locations, gets mutated by removing items that get placed.
|
||||||
:param single_player_placement: if true, can speed up placement if everything belongs to a single player
|
:param single_player_placement: if true, can speed up placement if everything belongs to a single player
|
||||||
:param lock: locations are set to locked as they are filled
|
:param lock: locations are set to locked as they are filled
|
||||||
:param swap: if true, swaps of already place items are done in the event of a dead end
|
: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
|
||||||
def remaining_fill(multiworld: MultiWorld,
|
def remaining_fill(multiworld: MultiWorld,
|
||||||
locations: typing.List[Location],
|
locations: typing.List[Location],
|
||||||
itempool: typing.List[Item],
|
itempool: typing.List[Item],
|
||||||
name: str = "Remaining") -> None:
|
name: str = "Remaining",
|
||||||
|
move_unplaceable_to_start_inventory: bool = False) -> None:
|
||||||
unplaced_items: typing.List[Item] = []
|
unplaced_items: typing.List[Item] = []
|
||||||
placements: typing.List[Location] = []
|
placements: typing.List[Location] = []
|
||||||
swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter()
|
swapped_items: typing.Counter[typing.Tuple[int, str]] = Counter()
|
||||||
|
@ -284,6 +285,14 @@ def remaining_fill(multiworld: MultiWorld,
|
||||||
|
|
||||||
if unplaced_items and locations:
|
if unplaced_items and locations:
|
||||||
# There are leftover unplaceable items and locations that won't accept them
|
# There are leftover unplaceable items and locations that won't accept them
|
||||||
|
if move_unplaceable_to_start_inventory:
|
||||||
|
last_batch = []
|
||||||
|
for item in unplaced_items:
|
||||||
|
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
|
||||||
|
multiworld.push_precollected(item)
|
||||||
|
last_batch.append(multiworld.worlds[item.player].create_filler())
|
||||||
|
remaining_fill(multiworld, locations, unplaced_items, name + " Start Inventory Retry")
|
||||||
|
else:
|
||||||
raise FillError(f"No more spots to place {len(unplaced_items)} items. Remaining locations are invalid.\n"
|
raise FillError(f"No more spots to place {len(unplaced_items)} items. Remaining locations are invalid.\n"
|
||||||
f"Unplaced items:\n"
|
f"Unplaced items:\n"
|
||||||
f"{', '.join(str(item) for item in unplaced_items)}\n"
|
f"{', '.join(str(item) for item in unplaced_items)}\n"
|
||||||
|
@ -420,7 +429,8 @@ def distribute_early_items(multiworld: MultiWorld,
|
||||||
return fill_locations, itempool
|
return fill_locations, itempool
|
||||||
|
|
||||||
|
|
||||||
def distribute_items_restrictive(multiworld: MultiWorld) -> None:
|
def distribute_items_restrictive(multiworld: MultiWorld,
|
||||||
|
panic_method: typing.Literal["swap", "raise", "start_inventory"] = "swap") -> None:
|
||||||
fill_locations = sorted(multiworld.get_unfilled_locations())
|
fill_locations = sorted(multiworld.get_unfilled_locations())
|
||||||
multiworld.random.shuffle(fill_locations)
|
multiworld.random.shuffle(fill_locations)
|
||||||
# get items to distribute
|
# get items to distribute
|
||||||
|
@ -470,8 +480,29 @@ def distribute_items_restrictive(multiworld: MultiWorld) -> None:
|
||||||
|
|
||||||
if progitempool:
|
if progitempool:
|
||||||
# "advancement/progression fill"
|
# "advancement/progression fill"
|
||||||
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, single_player_placement=multiworld.players == 1,
|
if panic_method == "swap":
|
||||||
name="Progression")
|
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
||||||
|
swap=True,
|
||||||
|
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
|
||||||
|
elif panic_method == "raise":
|
||||||
|
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
||||||
|
swap=False,
|
||||||
|
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
|
||||||
|
elif panic_method == "start_inventory":
|
||||||
|
fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool,
|
||||||
|
swap=False, allow_partial=True,
|
||||||
|
on_place=mark_for_locking, name="Progression", single_player_placement=multiworld.players == 1)
|
||||||
|
if progitempool:
|
||||||
|
for item in progitempool:
|
||||||
|
logging.debug(f"Moved {item} to start_inventory to prevent fill failure.")
|
||||||
|
multiworld.push_precollected(item)
|
||||||
|
filleritempool.append(multiworld.worlds[item.player].create_filler())
|
||||||
|
logging.warning(f"{len(progitempool)} items moved to start inventory,"
|
||||||
|
f" due to failure in Progression fill step.")
|
||||||
|
progitempool[:] = []
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Generator Panic Method {panic_method} not recognized.")
|
||||||
if progitempool:
|
if progitempool:
|
||||||
raise FillError(
|
raise FillError(
|
||||||
f"Not enough locations for progression items. "
|
f"Not enough locations for progression items. "
|
||||||
|
@ -486,7 +517,9 @@ def distribute_items_restrictive(multiworld: MultiWorld) -> None:
|
||||||
|
|
||||||
inaccessible_location_rules(multiworld, multiworld.state, defaultlocations)
|
inaccessible_location_rules(multiworld, multiworld.state, defaultlocations)
|
||||||
|
|
||||||
remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded")
|
remaining_fill(multiworld, excludedlocations, filleritempool, "Remaining Excluded",
|
||||||
|
move_unplaceable_to_start_inventory=panic_method=="start_inventory")
|
||||||
|
|
||||||
if excludedlocations:
|
if excludedlocations:
|
||||||
raise FillError(
|
raise FillError(
|
||||||
f"Not enough filler items for excluded locations. "
|
f"Not enough filler items for excluded locations. "
|
||||||
|
@ -495,7 +528,8 @@ def distribute_items_restrictive(multiworld: MultiWorld) -> None:
|
||||||
|
|
||||||
restitempool = filleritempool + usefulitempool
|
restitempool = filleritempool + usefulitempool
|
||||||
|
|
||||||
remaining_fill(multiworld, defaultlocations, restitempool)
|
remaining_fill(multiworld, defaultlocations, restitempool,
|
||||||
|
move_unplaceable_to_start_inventory=panic_method=="start_inventory")
|
||||||
|
|
||||||
unplaced = restitempool
|
unplaced = restitempool
|
||||||
unfilled = defaultlocations
|
unfilled = defaultlocations
|
||||||
|
|
4
Main.py
4
Main.py
|
@ -13,7 +13,7 @@ import worlds
|
||||||
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
|
from BaseClasses import CollectionState, Item, Location, LocationProgressType, MultiWorld, Region
|
||||||
from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items
|
from Fill import balance_multiworld_progression, distribute_items_restrictive, distribute_planned, flood_items
|
||||||
from Options import StartInventoryPool
|
from Options import StartInventoryPool
|
||||||
from Utils import __version__, output_path, version_tuple
|
from Utils import __version__, output_path, version_tuple, get_settings
|
||||||
from settings import get_settings
|
from settings import get_settings
|
||||||
from worlds import AutoWorld
|
from worlds import AutoWorld
|
||||||
from worlds.generic.Rules import exclusion_rules, locality_rules
|
from worlds.generic.Rules import exclusion_rules, locality_rules
|
||||||
|
@ -272,7 +272,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||||
if multiworld.algorithm == 'flood':
|
if multiworld.algorithm == 'flood':
|
||||||
flood_items(multiworld) # different algo, biased towards early game progress items
|
flood_items(multiworld) # different algo, biased towards early game progress items
|
||||||
elif multiworld.algorithm == 'balanced':
|
elif multiworld.algorithm == 'balanced':
|
||||||
distribute_items_restrictive(multiworld)
|
distribute_items_restrictive(multiworld, get_settings().generator.panic_method)
|
||||||
|
|
||||||
AutoWorld.call_all(multiworld, 'post_fill')
|
AutoWorld.call_all(multiworld, 'post_fill')
|
||||||
|
|
||||||
|
|
|
@ -665,6 +665,14 @@ class GeneratorOptions(Group):
|
||||||
OFF = 0
|
OFF = 0
|
||||||
ON = 1
|
ON = 1
|
||||||
|
|
||||||
|
class PanicMethod(str):
|
||||||
|
"""
|
||||||
|
What to do if the current item placements appear unsolvable.
|
||||||
|
raise -> Raise an exception and abort.
|
||||||
|
swap -> Attempt to fix it by swapping prior placements around. (Default)
|
||||||
|
start_inventory -> Move remaining items to start_inventory, generate additional filler items to fill locations.
|
||||||
|
"""
|
||||||
|
|
||||||
enemizer_path: EnemizerPath = EnemizerPath("EnemizerCLI/EnemizerCLI.Core") # + ".exe" is implied on Windows
|
enemizer_path: EnemizerPath = EnemizerPath("EnemizerCLI/EnemizerCLI.Core") # + ".exe" is implied on Windows
|
||||||
player_files_path: PlayerFilesPath = PlayerFilesPath("Players")
|
player_files_path: PlayerFilesPath = PlayerFilesPath("Players")
|
||||||
players: Players = Players(0)
|
players: Players = Players(0)
|
||||||
|
@ -673,6 +681,7 @@ class GeneratorOptions(Group):
|
||||||
spoiler: Spoiler = Spoiler(3)
|
spoiler: Spoiler = Spoiler(3)
|
||||||
race: Race = Race(0)
|
race: Race = Race(0)
|
||||||
plando_options: PlandoOptions = PlandoOptions("bosses, connections, texts")
|
plando_options: PlandoOptions = PlandoOptions("bosses, connections, texts")
|
||||||
|
panic_method: PanicMethod = PanicMethod("swap")
|
||||||
|
|
||||||
|
|
||||||
class SNIOptions(Group):
|
class SNIOptions(Group):
|
||||||
|
|
Loading…
Reference in New Issue