Core: Plando Items "Rewrite" (#3046)

This commit is contained in:
Silvris
2025-05-10 17:49:49 -05:00
committed by GitHub
parent 68ed208613
commit a166dc77bc
20 changed files with 448 additions and 262 deletions

View File

@@ -54,16 +54,13 @@ def parse_arguments(argv, no_defaults=False):
ret = parser.parse_args(argv)
# cannot be set through CLI currently
ret.plando_items = []
ret.plando_texts = {}
ret.plando_connections = []
if multiargs.multi:
defaults = copy.deepcopy(ret)
for player in range(1, multiargs.multi + 1):
playerargs = parse_arguments(shlex.split(getattr(ret, f"p{player}")), True)
for name in ["plando_items", "plando_texts", "plando_connections", "game", "sprite", "sprite_pool"]:
for name in ["game", "sprite", "sprite_pool"]:
value = getattr(defaults, name) if getattr(playerargs, name) is None else getattr(playerargs, name)
if player == 1:
setattr(ret, name, {1: value})

View File

@@ -505,20 +505,20 @@ class ALTTPWorld(World):
def pre_fill(self):
from Fill import fill_restrictive, FillError
attempts = 5
world = self.multiworld
player = self.player
all_state = world.get_all_state(use_cache=True)
all_state = self.multiworld.get_all_state(use_cache=False)
crystals = [self.create_item(name) for name in ['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6']]
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)]
for crystal in crystals:
all_state.remove(crystal)
crystal_locations = [self.get_location('Turtle Rock - Prize'),
self.get_location('Eastern Palace - Prize'),
self.get_location('Desert Palace - Prize'),
self.get_location('Tower of Hera - Prize'),
self.get_location('Palace of Darkness - Prize'),
self.get_location('Thieves\' Town - Prize'),
self.get_location('Skull Woods - Prize'),
self.get_location('Swamp Palace - Prize'),
self.get_location('Ice Palace - Prize'),
self.get_location('Misery Mire - Prize')]
placed_prizes = {loc.item.name for loc in crystal_locations if loc.item}
unplaced_prizes = [crystal for crystal in crystals if crystal.name not in placed_prizes]
empty_crystal_locations = [loc for loc in crystal_locations if not loc.item]
@@ -526,8 +526,8 @@ class ALTTPWorld(World):
try:
prizepool = unplaced_prizes.copy()
prize_locs = empty_crystal_locations.copy()
world.random.shuffle(prize_locs)
fill_restrictive(world, all_state, prize_locs, prizepool, True, lock=True,
self.multiworld.random.shuffle(prize_locs)
fill_restrictive(self.multiworld, all_state, prize_locs, prizepool, True, lock=True,
name="LttP Dungeon Prizes")
except FillError as e:
lttp_logger.exception("Failed to place dungeon prizes (%s). Will retry %s more times", e,
@@ -541,7 +541,7 @@ class ALTTPWorld(World):
if self.options.mode == 'standard' and self.options.small_key_shuffle \
and self.options.small_key_shuffle != small_key_shuffle.option_universal and \
self.options.small_key_shuffle != small_key_shuffle.option_own_dungeons:
world.local_early_items[player]["Small Key (Hyrule Castle)"] = 1
self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1
@classmethod
def stage_pre_fill(cls, world):
@@ -811,12 +811,15 @@ class ALTTPWorld(World):
return GetBeemizerItem(self.multiworld, self.player, item)
def get_pre_fill_items(self):
res = []
res = [self.create_item(name) for name in ('Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1',
'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5',
'Crystal 6')]
if self.dungeon_local_item_names:
for dungeon in self.dungeons.values():
for item in dungeon.all_items:
if item.name in self.dungeon_local_item_names:
res.append(item)
return res
def fill_slot_data(self):

View File

@@ -207,7 +207,6 @@ class BlasphemousWorld(World):
if not self.options.skill_randomizer:
self.place_items_from_dict(skill_dict)
def place_items_from_set(self, location_set: Set[str], name: str):
for loc in location_set:
self.get_location(loc).place_locked_item(self.create_item(name))

View File

@@ -127,6 +127,10 @@ class Hylics2World(World):
tv = tvs.pop()
self.get_location(tv).place_locked_item(self.create_item(gesture))
def get_pre_fill_items(self) -> List["Item"]:
if self.options.gesture_shuffle:
return [self.create_item(gesture["name"]) for gesture in Items.gesture_item_table.values()]
return []
def fill_slot_data(self) -> Dict[str, Any]:
slot_data: Dict[str, Any] = {

View File

@@ -436,6 +436,10 @@ class KH2World(World):
for location in keyblade_locations:
location.locked = True
def get_pre_fill_items(self) -> List["Item"]:
return [self.create_item(item) for item in [*DonaldAbility_Table.keys(), *GoofyAbility_Table.keys(),
*SupportAbility_Table.keys()]]
def starting_invo_verify(self):
"""
Making sure the player doesn't put too many abilities in their starting inventory.

View File

@@ -1,6 +1,7 @@
from typing import Optional
from Fill import distribute_planned
from Fill import parse_planned_blocks, distribute_planned_blocks, resolve_early_locations_for_planned
from Options import PlandoItems
from test.general import setup_solo_multiworld
from worlds.AutoWorld import call_all
from . import LADXTestBase
@@ -19,14 +20,17 @@ class PlandoTest(LADXTestBase):
],
}],
}
def world_setup(self, seed: Optional[int] = None) -> None:
self.multiworld = setup_solo_multiworld(
LinksAwakeningWorld,
("generate_early", "create_regions", "create_items", "set_rules", "generate_basic")
)
self.multiworld.plando_items[1] = self.options["plando_items"]
distribute_planned(self.multiworld)
self.multiworld.worlds[1].options.plando_items = PlandoItems.from_any(self.options["plando_items"])
self.multiworld.plando_item_blocks = parse_planned_blocks(self.multiworld)
resolve_early_locations_for_planned(self.multiworld)
distribute_planned_blocks(self.multiworld, [x for player in self.multiworld.plando_item_blocks
for x in self.multiworld.plando_item_blocks[player]])
call_all(self.multiworld, "pre_fill")
def test_planned(self):

View File

@@ -32,7 +32,7 @@ from .Cosmetics import patch_cosmetics
from settings import get_settings
from BaseClasses import MultiWorld, CollectionState, Tutorial, LocationProgressType
from Options import Range, Toggle, VerifyKeys, Accessibility, PlandoConnections
from Options import Range, Toggle, VerifyKeys, Accessibility, PlandoConnections, PlandoItems
from Fill import fill_restrictive, fast_fill, FillError
from worlds.generic.Rules import exclusion_rules, add_item_rule
from worlds.AutoWorld import World, AutoLogicRegister, WebWorld
@@ -220,6 +220,8 @@ class OOTWorld(World):
option_value = result.value
elif isinstance(result, PlandoConnections):
option_value = result.value
elif isinstance(result, PlandoItems):
option_value = result.value
else:
option_value = result.current_key
setattr(self, option_name, option_value)

View File

@@ -321,7 +321,7 @@ class PokemonRedBlueWorld(World):
"Fuchsia Gym - Koga Prize", "Saffron Gym - Sabrina Prize",
"Cinnabar Gym - Blaine Prize", "Viridian Gym - Giovanni Prize"
] if self.multiworld.get_location(loc, self.player).item is None]
state = self.multiworld.get_all_state(False)
state = self.multiworld.get_all_state(False, True, False)
# Give it two tries to place badges with wild Pokemon and learnsets as-is.
# If it can't, then try with all Pokemon collected, and we'll try to fix HM move availability after.
if attempt > 1:
@@ -395,7 +395,7 @@ class PokemonRedBlueWorld(World):
# Delete evolution events for Pokémon that are not in logic in an all_state so that accessibility check does not
# fail. Re-use test_state from previous final loop.
all_state = self.multiworld.get_all_state(False)
all_state = self.multiworld.get_all_state(False, True, False)
evolutions_region = self.multiworld.get_region("Evolution", self.player)
for location in evolutions_region.locations.copy():
if not all_state.can_reach(location, player=self.player):
@@ -448,7 +448,7 @@ class PokemonRedBlueWorld(World):
self.local_locs = locs
all_state = self.multiworld.get_all_state(False)
all_state = self.multiworld.get_all_state(False, True, False)
reachable_mons = set()
for mon in poke_data.pokemon_data:
@@ -516,6 +516,11 @@ class PokemonRedBlueWorld(World):
loc.item = None
loc.place_locked_item(self.pc_item)
def get_pre_fill_items(self) -> typing.List["Item"]:
pool = [self.create_item(mon) for mon in poke_data.pokemon_data]
pool.append(self.pc_item)
return pool
@classmethod
def stage_post_fill(cls, multiworld):
# Convert all but one of each instance of a wild Pokemon to useful classification.

View File

@@ -400,7 +400,7 @@ def verify_hm_moves(multiworld, world, player):
last_intervene = None
while True:
intervene_move = None
test_state = multiworld.get_all_state(False)
test_state = multiworld.get_all_state(False, True, False)
if not logic.can_learn_hm(test_state, world, "Surf", player):
intervene_move = "Surf"
elif not logic.can_learn_hm(test_state, world, "Strength", player):

View File

@@ -66,11 +66,8 @@ def get_plando_locations(world: World) -> List[str]:
if world is None:
return []
plando_locations = []
for plando_setting in world.multiworld.plando_items[world.player]:
plando_locations += plando_setting.get("locations", [])
plando_setting_location = plando_setting.get("location", None)
if plando_setting_location is not None:
plando_locations.append(plando_setting_location)
for plando_setting in world.options.plando_items:
plando_locations += plando_setting.locations
return plando_locations

View File

@@ -245,7 +245,7 @@ class ShiversWorld(World):
storage_items += [self.create_item("Empty") for _ in range(3)]
state = self.multiworld.get_all_state(False)
state = self.multiworld.get_all_state(False, True, False)
self.random.shuffle(storage_locs)
self.random.shuffle(storage_items)
@@ -255,6 +255,27 @@ class ShiversWorld(World):
self.storage_placements = {location.name.replace("Storage: ", ""): location.item.name.replace(" DUPE", "") for
location in storage_locs}
def get_pre_fill_items(self) -> List[Item]:
if self.options.full_pots == "pieces":
return [self.create_item(name) for name, data in item_table.items() if
data.type == ItemType.POT_DUPLICATE]
elif self.options.full_pots == "complete":
return [self.create_item(name) for name, data in item_table.items() if
data.type == ItemType.POT_COMPELTE_DUPLICATE]
else:
pool = []
pieces = [self.create_item(name) for name, data in item_table.items() if
data.type == ItemType.POT_DUPLICATE]
complete = [self.create_item(name) for name, data in item_table.items() if
data.type == ItemType.POT_COMPELTE_DUPLICATE]
for i in range(10):
if self.pot_completed_list[i] == 0:
pool.append(pieces[i])
pool.append(pieces[i + 10])
else:
pool.append(complete[i])
return pool
def fill_slot_data(self) -> dict:
return {
"StoragePlacements": self.storage_placements,

View File

@@ -35,9 +35,6 @@ class TestUniversalTrackerGenerationIsStable(SVTestBase):
args.multi = 1
args.race = None
args.plando_options = self.multiworld.plando_options
args.plando_items = self.multiworld.plando_items
args.plando_texts = self.multiworld.plando_texts
args.plando_connections = self.multiworld.plando_connections
args.game = self.multiworld.game
args.name = self.multiworld.player_name
args.sprite = {}

View File

@@ -214,20 +214,17 @@ class WitnessPlayerItems:
# Remove items that are mentioned in any plando options. (Hopefully, in the future, plando will get resolved
# before create_items so that we'll be able to check placed items instead of just removing all items mentioned
# regardless of whether or not they actually wind up being manually placed.
for plando_setting in self._multiworld.plando_items[self._player_id]:
if plando_setting.get("from_pool", True):
for item_setting_key in [key for key in ["item", "items"] if key in plando_setting]:
if isinstance(plando_setting[item_setting_key], str):
output -= {plando_setting[item_setting_key]}
elif isinstance(plando_setting[item_setting_key], dict):
output -= {item for item, weight in plando_setting[item_setting_key].items() if weight}
else:
# Assume this is some other kind of iterable.
for inner_item in plando_setting[item_setting_key]:
if isinstance(inner_item, str):
output -= {inner_item}
elif isinstance(inner_item, dict):
output -= {item for item, weight in inner_item.items() if weight}
for plando_setting in self._world.options.plando_items:
if plando_setting.from_pool:
if isinstance(plando_setting.items, dict):
output -= {item for item, weight in plando_setting.items.items() if weight}
else:
# Assume this is some other kind of iterable.
for inner_item in plando_setting.items:
if isinstance(inner_item, str):
output -= {inner_item}
elif isinstance(inner_item, dict):
output -= {item for item, weight in inner_item.items() if weight}
# Sort the output for consistency across versions if the implementation changes but the logic does not.
return sorted(output)