Subnautica: revamp aggressive creature scans (#966)
* add forgotten aggressive creatures * fix logic requirements * added option to opt out of aggressive creature scans
This commit is contained in:
parent
4a2a184db1
commit
60d1a27079
|
@ -1,3 +1,4 @@
|
|||
import functools
|
||||
from typing import Dict, Set, List
|
||||
|
||||
# EN Locale Creature Name to rough depth in meters found at
|
||||
|
@ -58,13 +59,18 @@ all_creatures: Dict[str, int] = {
|
|||
aggressive: Set[str] = {
|
||||
"Cave Crawler", # is very easy without Stasis Rifle, but included for consistency
|
||||
"Crashfish",
|
||||
"Biter",
|
||||
"Bleeder",
|
||||
"Blighter",
|
||||
"Blood Crawler",
|
||||
"Mesmer",
|
||||
"Reaper Leviathan",
|
||||
"Crabsquid",
|
||||
"Warper",
|
||||
"Crabsnake",
|
||||
"Ampeel",
|
||||
"Stalker",
|
||||
"Sand Shark",
|
||||
"Boneshark",
|
||||
"Lava Lizard",
|
||||
"Sea Dragon Leviathan",
|
||||
|
@ -94,6 +100,25 @@ creature_locations: Dict[str, int] = {
|
|||
creature + suffix: creature_id for creature_id, creature in enumerate(all_creatures, start=34000)
|
||||
}
|
||||
|
||||
all_creatures_presorted: List[str] = sorted(all_creatures)
|
||||
all_creatures_presorted_without_containment = [name for name in all_creatures_presorted if name not in containment]
|
||||
|
||||
class Definitions:
|
||||
"""Only compute lists if needed and then cache them."""
|
||||
|
||||
@functools.cached_property
|
||||
def all_creatures_presorted(self) -> List[str]:
|
||||
return sorted(all_creatures)
|
||||
|
||||
@functools.cached_property
|
||||
def all_creatures_presorted_without_containment(self) -> List[str]:
|
||||
return [name for name in self.all_creatures_presorted if name not in containment]
|
||||
|
||||
@functools.cached_property
|
||||
def all_creatures_presorted_without_stasis(self) -> List[str]:
|
||||
return [name for name in self.all_creatures_presorted if name not in aggressive or name in hatchable]
|
||||
|
||||
@functools.cached_property
|
||||
def all_creatures_presorted_without_aggressive(self) -> List[str]:
|
||||
return [name for name in self.all_creatures_presorted if name not in aggressive]
|
||||
|
||||
# only singleton needed
|
||||
Definitions: Definitions = Definitions()
|
||||
|
|
|
@ -15,7 +15,12 @@ class LocationDict(TypedDict, total=False):
|
|||
need_propulsion_cannon: bool
|
||||
|
||||
|
||||
events: List[str] = ["Neptune Launch", "Disable Quarantine", "Full Infection", "Repair Aurora Drive"]
|
||||
events: List[str] = [
|
||||
"Neptune Launch",
|
||||
"Disable Quarantine",
|
||||
"Full Infection",
|
||||
"Repair Aurora Drive",
|
||||
]
|
||||
|
||||
location_table: Dict[int, LocationDict] = {
|
||||
33000: {'can_slip_through': False,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import typing
|
||||
|
||||
from Options import Choice, Range, DeathLink
|
||||
from .Creatures import all_creatures
|
||||
from .Creatures import all_creatures, Definitions
|
||||
|
||||
|
||||
class ItemPool(Choice):
|
||||
|
@ -46,14 +46,27 @@ class AggressiveScanLogic(Choice):
|
|||
Containment: Removes Stasis Rifle as expected solution and expects Alien Containment instead.
|
||||
Either: Creatures may be expected to be scanned via Stasis Rifle or Containment, whichever is found first.
|
||||
None: Aggressive Creatures are assumed to not need any tools to scan.
|
||||
Removed: No Creatures needing Stasis or Containment will be in the pool at all.
|
||||
|
||||
Note: Containment, Either and None adds Cuddlefish as an option for scans.
|
||||
Note: Stasis, Either and None adds unhatchable aggressive species, such as Warper.
|
||||
Note: This is purely a logic expectation, and does not affect gameplay, only placement."""
|
||||
display_name = "Aggressive Creature Scan Logic"
|
||||
option_stasis = 0
|
||||
option_containment = 1
|
||||
option_either = 2
|
||||
option_none = 3
|
||||
option_removed = 4
|
||||
|
||||
def get_pool(self) -> typing.List[str]:
|
||||
if self == self.option_removed:
|
||||
return Definitions.all_creatures_presorted_without_aggressive
|
||||
elif self == self.option_stasis:
|
||||
return Definitions.all_creatures_presorted_without_containment
|
||||
elif self == self.option_containment:
|
||||
return Definitions.all_creatures_presorted_without_stasis
|
||||
else:
|
||||
return Definitions.all_creatures_presorted
|
||||
|
||||
|
||||
class SubnauticaDeathLink(DeathLink):
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
from typing import TYPE_CHECKING, Dict, Callable
|
||||
from typing import TYPE_CHECKING, Dict, Callable, Optional
|
||||
|
||||
from worlds.generic.Rules import set_rule, add_rule
|
||||
from .Locations import location_table, LocationDict
|
||||
from .Creatures import all_creatures, aggressive, suffix
|
||||
from .Creatures import all_creatures, aggressive, suffix, hatchable, containment
|
||||
from .Options import AggressiveScanLogic
|
||||
import math
|
||||
|
||||
|
@ -258,6 +258,15 @@ def set_creature_rule(world, player: int, creature_name: str) -> "Location":
|
|||
return location
|
||||
|
||||
|
||||
def get_aggression_rule(option: AggressiveScanLogic, creature_name: str) -> \
|
||||
Optional[Callable[["CollectionState", int], bool]]:
|
||||
"""Get logic rule for a creature scan location."""
|
||||
if creature_name not in hatchable and option != option.option_none: # can only be done via stasis
|
||||
return has_stasis_rifle
|
||||
# otherwise allow option preference
|
||||
return aggression_rules.get(option.value, None)
|
||||
|
||||
|
||||
aggression_rules: Dict[int, Callable[["CollectionState", int], bool]] = {
|
||||
AggressiveScanLogic.option_stasis: has_stasis_rifle,
|
||||
AggressiveScanLogic.option_containment: has_containment,
|
||||
|
@ -274,14 +283,21 @@ def set_rules(subnautica_world: "SubnauticaWorld"):
|
|||
set_location_rule(world, player, loc)
|
||||
|
||||
if subnautica_world.creatures_to_scan:
|
||||
aggressive_rule = aggression_rules.get(world.creature_scan_logic[player], None)
|
||||
option = world.creature_scan_logic[player]
|
||||
|
||||
for creature_name in subnautica_world.creatures_to_scan:
|
||||
location = set_creature_rule(world, player, creature_name)
|
||||
if creature_name in aggressive and aggressive_rule:
|
||||
add_rule(location, lambda state: aggressive_rule(state, player))
|
||||
if creature_name in containment: # there is no other way, hard-required containment
|
||||
add_rule(location, lambda state: has_containment(state, player))
|
||||
elif creature_name in aggressive:
|
||||
rule = get_aggression_rule(option, creature_name)
|
||||
if rule:
|
||||
add_rule(location,
|
||||
lambda state, loc_rule=get_aggression_rule(option, creature_name): loc_rule(state, player))
|
||||
|
||||
# Victory locations
|
||||
set_rule(world.get_location("Neptune Launch", player), lambda state:
|
||||
set_rule(world.get_location("Neptune Launch", player),
|
||||
lambda state:
|
||||
get_max_depth(state, player) >= 1444 and
|
||||
has_mobile_vehicle_bay(state, player) and
|
||||
state.has("Neptune Launch Platform", player) and
|
||||
|
|
|
@ -52,14 +52,15 @@ class SubnauticaWorld(World):
|
|||
self.create_item("Seaglide Fragment"),
|
||||
self.create_item("Seaglide Fragment")
|
||||
]
|
||||
if self.world.creature_scan_logic[self.player] == Options.AggressiveScanLogic.option_stasis:
|
||||
valid_creatures = Creatures.all_creatures_presorted_without_containment
|
||||
self.world.creature_scans[self.player].value = min(len(
|
||||
Creatures.all_creatures_presorted_without_containment),
|
||||
self.world.creature_scans[self.player].value)
|
||||
else:
|
||||
valid_creatures = Creatures.all_creatures_presorted
|
||||
self.creatures_to_scan = self.world.random.sample(valid_creatures,
|
||||
scan_option: Options.AggressiveScanLogic = self.world.creature_scan_logic[self.player]
|
||||
creature_pool = scan_option.get_pool()
|
||||
|
||||
self.world.creature_scans[self.player].value = min(
|
||||
len(creature_pool),
|
||||
self.world.creature_scans[self.player].value
|
||||
)
|
||||
|
||||
self.creatures_to_scan = self.world.random.sample(creature_pool,
|
||||
self.world.creature_scans[self.player].value)
|
||||
|
||||
def create_regions(self):
|
||||
|
|
Loading…
Reference in New Issue