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:
Alchav 2022-10-27 03:00:24 -04:00 committed by GitHub
parent 700fe8b75e
commit 4b18920819
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 58 additions and 18 deletions

View File

@ -48,6 +48,7 @@ class MultiWorld():
state: CollectionState
accessibility: Dict[int, Options.Accessibility]
early_items: Dict[int, Options.EarlyItems]
local_items: Dict[int, Options.LocalItems]
non_local_items: Dict[int, Options.NonLocalItems]
progression_balancing: Dict[int, Options.ProgressionBalancing]

39
Fill.py
View File

@ -258,6 +258,45 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
usefulitempool: 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:
if item.advancement:
progitempool.append(item)

View File

@ -883,6 +883,11 @@ class NonLocalItems(ItemSet):
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):
"""Start with these items."""
verify_item_name = True
@ -981,6 +986,7 @@ per_game_common_options = {
**common_options, # can be overwritten per-game
"local_items": LocalItems,
"non_local_items": NonLocalItems,
"early_items": EarlyItems,
"start_inventory": StartInventory,
"start_hints": StartHints,
"start_location_hints": StartLocationHints,

View File

@ -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.
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.
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
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.
* `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
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.
@ -172,6 +174,8 @@ A Link to the Past:
- Quake
non_local_items:
- Moon Pearl
early_items:
Flute: 1
start_location_hints:
- Spike Cave
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
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.
* `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
multiworld that can be used for no cost.
* `priority_locations` forces a progression item to be placed on the `Link's House` location.

View File

@ -175,7 +175,7 @@ item_table: Dict[int, ItemDict] = {
'name': 'Thermal Plant Fragment',
'tech_type': 'ThermalPlantFragment'},
35041: {'classification': ItemClassification.progression,
'count': 2,
'count': 4,
'name': 'Seaglide Fragment',
'tech_type': 'SeaglideFragment'},
35042: {'classification': ItemClassification.progression,

View File

@ -44,14 +44,12 @@ class SubnauticaWorld(World):
data_version = 7
required_client_version = (0, 3, 5)
prefill_items: List[Item]
creatures_to_scan: List[str]
def generate_early(self) -> None:
self.prefill_items = [
self.create_item("Seaglide Fragment"),
self.create_item("Seaglide Fragment")
]
if "Seaglide Fragment" not in self.world.early_items[self.player]:
self.world.early_items[self.player].value["Seaglide Fragment"] = 2
scan_option: Options.AggressiveScanLogic = self.world.creature_scan_logic[self.player]
creature_pool = scan_option.get_pool()
@ -149,17 +147,6 @@ class SubnauticaWorld(World):
ret.exits.append(Entrance(self.player, region_exit, 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):
game: str = "Subnautica"