Early Items option (#1086)
* Early Items option * Early Items description update * Change Early Items to dict * Rewrite early items as extra fill steps * Move if statement up * Document early_items * Move early_items handling before fill_hook * Apply suggestions from code review Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com> * Subnautica pre-fill moved to early_items * Subnautica early items fix * Remove unit test bug workaround * beauxq's pr Co-authored-by: Doug Hoskisson <beauxq@users.noreply.github.com>
This commit is contained in:
parent
700fe8b75e
commit
4b18920819
|
@ -48,6 +48,7 @@ class MultiWorld():
|
||||||
state: CollectionState
|
state: CollectionState
|
||||||
|
|
||||||
accessibility: Dict[int, Options.Accessibility]
|
accessibility: Dict[int, Options.Accessibility]
|
||||||
|
early_items: Dict[int, Options.EarlyItems]
|
||||||
local_items: Dict[int, Options.LocalItems]
|
local_items: Dict[int, Options.LocalItems]
|
||||||
non_local_items: Dict[int, Options.NonLocalItems]
|
non_local_items: Dict[int, Options.NonLocalItems]
|
||||||
progression_balancing: Dict[int, Options.ProgressionBalancing]
|
progression_balancing: Dict[int, Options.ProgressionBalancing]
|
||||||
|
|
39
Fill.py
39
Fill.py
|
@ -258,6 +258,45 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
|
||||||
usefulitempool: typing.List[Item] = []
|
usefulitempool: typing.List[Item] = []
|
||||||
filleritempool: typing.List[Item] = []
|
filleritempool: typing.List[Item] = []
|
||||||
|
|
||||||
|
early_items_count: typing.Dict[typing.Tuple[str, int], int] = {}
|
||||||
|
for player in world.player_ids:
|
||||||
|
for item, count in world.early_items[player].value.items():
|
||||||
|
early_items_count[(item, player)] = count
|
||||||
|
if early_items_count:
|
||||||
|
early_locations: typing.List[Location] = []
|
||||||
|
early_priority_locations: typing.List[Location] = []
|
||||||
|
for loc in reversed(fill_locations):
|
||||||
|
if loc.can_reach(world.state):
|
||||||
|
if loc.progress_type == LocationProgressType.PRIORITY:
|
||||||
|
early_priority_locations.append(loc)
|
||||||
|
else:
|
||||||
|
early_locations.append(loc)
|
||||||
|
fill_locations.remove(loc)
|
||||||
|
|
||||||
|
early_prog_items: typing.List[Item] = []
|
||||||
|
early_rest_items: typing.List[Item] = []
|
||||||
|
for item in reversed(itempool):
|
||||||
|
if (item.name, item.player) in early_items_count:
|
||||||
|
if item.advancement:
|
||||||
|
early_prog_items.append(item)
|
||||||
|
else:
|
||||||
|
early_rest_items.append(item)
|
||||||
|
itempool.remove(item)
|
||||||
|
early_items_count[(item.name, item.player)] -= 1
|
||||||
|
if early_items_count[(item.name, item.player)] == 0:
|
||||||
|
del early_items_count[(item.name, item.player)]
|
||||||
|
fill_restrictive(world, world.state, early_locations, early_rest_items, lock=True)
|
||||||
|
early_locations += early_priority_locations
|
||||||
|
fill_restrictive(world, world.state, early_locations, early_prog_items, lock=True)
|
||||||
|
unplaced_early_items = early_rest_items + early_prog_items
|
||||||
|
if unplaced_early_items:
|
||||||
|
logging.warning(f"Ran out of early locations for early items. Failed to place \
|
||||||
|
{len(unplaced_early_items)} items early.")
|
||||||
|
itempool += unplaced_early_items
|
||||||
|
|
||||||
|
fill_locations += early_locations + early_priority_locations
|
||||||
|
world.random.shuffle(fill_locations)
|
||||||
|
|
||||||
for item in itempool:
|
for item in itempool:
|
||||||
if item.advancement:
|
if item.advancement:
|
||||||
progitempool.append(item)
|
progitempool.append(item)
|
||||||
|
|
|
@ -883,6 +883,11 @@ class NonLocalItems(ItemSet):
|
||||||
display_name = "Not Local Items"
|
display_name = "Not Local Items"
|
||||||
|
|
||||||
|
|
||||||
|
class EarlyItems(ItemDict):
|
||||||
|
"""Force the specified items to be in locations that are reachable from the start."""
|
||||||
|
display_name = "Early Items"
|
||||||
|
|
||||||
|
|
||||||
class StartInventory(ItemDict):
|
class StartInventory(ItemDict):
|
||||||
"""Start with these items."""
|
"""Start with these items."""
|
||||||
verify_item_name = True
|
verify_item_name = True
|
||||||
|
@ -981,6 +986,7 @@ per_game_common_options = {
|
||||||
**common_options, # can be overwritten per-game
|
**common_options, # can be overwritten per-game
|
||||||
"local_items": LocalItems,
|
"local_items": LocalItems,
|
||||||
"non_local_items": NonLocalItems,
|
"non_local_items": NonLocalItems,
|
||||||
|
"early_items": EarlyItems,
|
||||||
"start_inventory": StartInventory,
|
"start_inventory": StartInventory,
|
||||||
"start_hints": StartHints,
|
"start_hints": StartHints,
|
||||||
"start_location_hints": StartLocationHints,
|
"start_location_hints": StartLocationHints,
|
||||||
|
|
|
@ -106,7 +106,7 @@ settings. If a game can be rolled it **must** have a settings section even if it
|
||||||
|
|
||||||
Some options in Archipelago can be used by every game but must still be placed within the relevant game's section.
|
Some options in Archipelago can be used by every game but must still be placed within the relevant game's section.
|
||||||
|
|
||||||
Currently, these options are `start_inventory`, `start_hints`, `local_items`, `non_local_items`, `start_location_hints`
|
Currently, these options are `start_inventory`, `early_items`, `start_hints`, `local_items`, `non_local_items`, `start_location_hints`
|
||||||
, `exclude_locations`, and various plando options.
|
, `exclude_locations`, and various plando options.
|
||||||
|
|
||||||
See the plando guide for more info on plando options. Plando
|
See the plando guide for more info on plando options. Plando
|
||||||
|
@ -115,6 +115,8 @@ guide: [Archipelago Plando Guide](/tutorial/Archipelago/plando/en)
|
||||||
* `start_inventory` will give any items defined here to you at the beginning of your game. The format for this must be
|
* `start_inventory` will give any items defined here to you at the beginning of your game. The format for this must be
|
||||||
the name as it appears in the game files and the amount you would like to start with. For example `Rupees(5): 6` which
|
the name as it appears in the game files and the amount you would like to start with. For example `Rupees(5): 6` which
|
||||||
will give you 30 rupees.
|
will give you 30 rupees.
|
||||||
|
* `early_items` is formatted in the same way as `start_inventory` and will force the number of each item specified to be
|
||||||
|
forced into locations that are reachable from the start, before obtaining any items.
|
||||||
* `start_hints` gives you free server hints for the defined item/s at the beginning of the game allowing you to hint for
|
* `start_hints` gives you free server hints for the defined item/s at the beginning of the game allowing you to hint for
|
||||||
the location without using any hint points.
|
the location without using any hint points.
|
||||||
* `local_items` will force any items you want to be in your world instead of being in another world.
|
* `local_items` will force any items you want to be in your world instead of being in another world.
|
||||||
|
@ -172,6 +174,8 @@ A Link to the Past:
|
||||||
- Quake
|
- Quake
|
||||||
non_local_items:
|
non_local_items:
|
||||||
- Moon Pearl
|
- Moon Pearl
|
||||||
|
early_items:
|
||||||
|
Flute: 1
|
||||||
start_location_hints:
|
start_location_hints:
|
||||||
- Spike Cave
|
- Spike Cave
|
||||||
priority_locations:
|
priority_locations:
|
||||||
|
@ -235,6 +239,9 @@ Timespinner:
|
||||||
* `local_items` forces the `Bombos`, `Ether`, and `Quake` medallions to all be placed within our own world, meaning we
|
* `local_items` forces the `Bombos`, `Ether`, and `Quake` medallions to all be placed within our own world, meaning we
|
||||||
have to find it ourselves.
|
have to find it ourselves.
|
||||||
* `non_local_items` forces the `Moon Pearl` to be placed in someone else's world, meaning we won't be able to find it.
|
* `non_local_items` forces the `Moon Pearl` to be placed in someone else's world, meaning we won't be able to find it.
|
||||||
|
* `early_items` forces the `Flute` to be placed in a location that is available from the beginning of the game ("Sphere
|
||||||
|
1"). Since it is not specified in `local_items` or `non_local_items`, it can be placed one of these locations in any
|
||||||
|
world.
|
||||||
* `start_location_hints` gives us a starting hint for the `Spike Cave` location available at the beginning of the
|
* `start_location_hints` gives us a starting hint for the `Spike Cave` location available at the beginning of the
|
||||||
multiworld that can be used for no cost.
|
multiworld that can be used for no cost.
|
||||||
* `priority_locations` forces a progression item to be placed on the `Link's House` location.
|
* `priority_locations` forces a progression item to be placed on the `Link's House` location.
|
||||||
|
|
|
@ -175,7 +175,7 @@ item_table: Dict[int, ItemDict] = {
|
||||||
'name': 'Thermal Plant Fragment',
|
'name': 'Thermal Plant Fragment',
|
||||||
'tech_type': 'ThermalPlantFragment'},
|
'tech_type': 'ThermalPlantFragment'},
|
||||||
35041: {'classification': ItemClassification.progression,
|
35041: {'classification': ItemClassification.progression,
|
||||||
'count': 2,
|
'count': 4,
|
||||||
'name': 'Seaglide Fragment',
|
'name': 'Seaglide Fragment',
|
||||||
'tech_type': 'SeaglideFragment'},
|
'tech_type': 'SeaglideFragment'},
|
||||||
35042: {'classification': ItemClassification.progression,
|
35042: {'classification': ItemClassification.progression,
|
||||||
|
|
|
@ -44,14 +44,12 @@ class SubnauticaWorld(World):
|
||||||
data_version = 7
|
data_version = 7
|
||||||
required_client_version = (0, 3, 5)
|
required_client_version = (0, 3, 5)
|
||||||
|
|
||||||
prefill_items: List[Item]
|
|
||||||
creatures_to_scan: List[str]
|
creatures_to_scan: List[str]
|
||||||
|
|
||||||
def generate_early(self) -> None:
|
def generate_early(self) -> None:
|
||||||
self.prefill_items = [
|
if "Seaglide Fragment" not in self.world.early_items[self.player]:
|
||||||
self.create_item("Seaglide Fragment"),
|
self.world.early_items[self.player].value["Seaglide Fragment"] = 2
|
||||||
self.create_item("Seaglide Fragment")
|
|
||||||
]
|
|
||||||
scan_option: Options.AggressiveScanLogic = self.world.creature_scan_logic[self.player]
|
scan_option: Options.AggressiveScanLogic = self.world.creature_scan_logic[self.player]
|
||||||
creature_pool = scan_option.get_pool()
|
creature_pool = scan_option.get_pool()
|
||||||
|
|
||||||
|
@ -149,17 +147,6 @@ class SubnauticaWorld(World):
|
||||||
ret.exits.append(Entrance(self.player, region_exit, ret))
|
ret.exits.append(Entrance(self.player, region_exit, ret))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_pre_fill_items(self) -> List[Item]:
|
|
||||||
return self.prefill_items
|
|
||||||
|
|
||||||
def pre_fill(self) -> None:
|
|
||||||
reachable = [location for location in self.world.get_reachable_locations(player=self.player)
|
|
||||||
if not location.item]
|
|
||||||
self.world.random.shuffle(reachable)
|
|
||||||
items = self.prefill_items.copy()
|
|
||||||
for item in items:
|
|
||||||
reachable.pop().place_locked_item(item)
|
|
||||||
|
|
||||||
|
|
||||||
class SubnauticaLocation(Location):
|
class SubnauticaLocation(Location):
|
||||||
game: str = "Subnautica"
|
game: str = "Subnautica"
|
||||||
|
|
Loading…
Reference in New Issue