Subnautica: implement swim_rule option and skip goal prog balancing (#1258)
This commit is contained in:
parent
1288f15e45
commit
f81d2653e0
|
@ -186,7 +186,7 @@ item_table: Dict[int, ItemDict] = {
|
||||||
'count': 2,
|
'count': 2,
|
||||||
'name': 'Propulsion Cannon Fragment',
|
'name': 'Propulsion Cannon Fragment',
|
||||||
'tech_type': 'PropulsionCannonFragment'},
|
'tech_type': 'PropulsionCannonFragment'},
|
||||||
35044: {'classification': ItemClassification.progression,
|
35044: {'classification': ItemClassification.progression_skip_balancing,
|
||||||
'count': 1,
|
'count': 1,
|
||||||
'name': 'Neptune Launch Platform',
|
'name': 'Neptune Launch Platform',
|
||||||
'tech_type': 'RocketBase'},
|
'tech_type': 'RocketBase'},
|
||||||
|
@ -314,19 +314,19 @@ item_table: Dict[int, ItemDict] = {
|
||||||
'count': 1,
|
'count': 1,
|
||||||
'name': 'Ion Battery',
|
'name': 'Ion Battery',
|
||||||
'tech_type': 'PrecursorIonBattery'},
|
'tech_type': 'PrecursorIonBattery'},
|
||||||
35076: {'classification': ItemClassification.progression,
|
35076: {'classification': ItemClassification.progression_skip_balancing,
|
||||||
'count': 1,
|
'count': 1,
|
||||||
'name': 'Neptune Gantry',
|
'name': 'Neptune Gantry',
|
||||||
'tech_type': 'RocketBaseLadder'},
|
'tech_type': 'RocketBaseLadder'},
|
||||||
35077: {'classification': ItemClassification.progression,
|
35077: {'classification': ItemClassification.progression_skip_balancing,
|
||||||
'count': 1,
|
'count': 1,
|
||||||
'name': 'Neptune Boosters',
|
'name': 'Neptune Boosters',
|
||||||
'tech_type': 'RocketStage1'},
|
'tech_type': 'RocketStage1'},
|
||||||
35078: {'classification': ItemClassification.progression,
|
35078: {'classification': ItemClassification.progression_skip_balancing,
|
||||||
'count': 1,
|
'count': 1,
|
||||||
'name': 'Neptune Fuel Reserve',
|
'name': 'Neptune Fuel Reserve',
|
||||||
'tech_type': 'RocketStage2'},
|
'tech_type': 'RocketStage2'},
|
||||||
35079: {'classification': ItemClassification.progression,
|
35079: {'classification': ItemClassification.progression_skip_balancing,
|
||||||
'count': 1,
|
'count': 1,
|
||||||
'name': 'Neptune Cockpit',
|
'name': 'Neptune Cockpit',
|
||||||
'tech_type': 'RocketStage3'},
|
'tech_type': 'RocketStage3'},
|
||||||
|
|
|
@ -4,6 +4,32 @@ from Options import Choice, Range, DeathLink, DefaultOnToggle
|
||||||
from .Creatures import all_creatures, Definitions
|
from .Creatures import all_creatures, Definitions
|
||||||
|
|
||||||
|
|
||||||
|
class SwimRule(Choice):
|
||||||
|
"""What logic considers ok swimming distances.
|
||||||
|
Easy: +200 depth from any max vehicle depth.
|
||||||
|
Normal: +400 depth from any max vehicle depth.
|
||||||
|
Warning: Normal can expect you to death run to a location (No viable return trip).
|
||||||
|
Hard: +600 depth from any max vehicle depth.
|
||||||
|
Warning: Hard may require bases, deaths, glitches, multi-tank inventory or other depth extending means.
|
||||||
|
Items: Expected depth is extended by items like seaglide, ultra glide fins and capacity tanks.
|
||||||
|
"""
|
||||||
|
display_name = "Swim Rule"
|
||||||
|
option_easy = 0
|
||||||
|
option_normal = 1
|
||||||
|
option_hard = 2
|
||||||
|
option_items_easy = 3
|
||||||
|
option_items_normal = 4
|
||||||
|
option_items_hard = 5
|
||||||
|
|
||||||
|
@property
|
||||||
|
def base_depth(self) -> int:
|
||||||
|
return [200, 400, 600][self.value % 3]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def consider_items(self) -> bool:
|
||||||
|
return self.value > 2
|
||||||
|
|
||||||
|
|
||||||
class EarlySeaglide(DefaultOnToggle):
|
class EarlySeaglide(DefaultOnToggle):
|
||||||
"""Make sure 2 of the Seaglide Fragments are available in or near the Safe Shallows (Sphere 1 Locations)."""
|
"""Make sure 2 of the Seaglide Fragments are available in or near the Safe Shallows (Sphere 1 Locations)."""
|
||||||
|
|
||||||
|
@ -79,6 +105,7 @@ class SubnauticaDeathLink(DeathLink):
|
||||||
|
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
|
"swim_rule": SwimRule,
|
||||||
"early_seaglide": EarlySeaglide,
|
"early_seaglide": EarlySeaglide,
|
||||||
"item_pool": ItemPool,
|
"item_pool": ItemPool,
|
||||||
"goal": Goal,
|
"goal": Goal,
|
||||||
|
|
|
@ -3,7 +3,7 @@ from typing import TYPE_CHECKING, Dict, Callable, Optional
|
||||||
from worlds.generic.Rules import set_rule, add_rule
|
from worlds.generic.Rules import set_rule, add_rule
|
||||||
from .Locations import location_table, LocationDict
|
from .Locations import location_table, LocationDict
|
||||||
from .Creatures import all_creatures, aggressive, suffix, hatchable, containment
|
from .Creatures import all_creatures, aggressive, suffix, hatchable, containment
|
||||||
from .Options import AggressiveScanLogic
|
from .Options import AggressiveScanLogic, SwimRule
|
||||||
import math
|
import math
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -11,155 +11,162 @@ if TYPE_CHECKING:
|
||||||
from BaseClasses import CollectionState, Location
|
from BaseClasses import CollectionState, Location
|
||||||
|
|
||||||
|
|
||||||
def has_seaglide(state: "CollectionState", player: int):
|
def has_seaglide(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Seaglide Fragment", player, 2)
|
return state.has("Seaglide Fragment", player, 2)
|
||||||
|
|
||||||
|
|
||||||
def has_modification_station(state: "CollectionState", player: int):
|
def has_modification_station(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Modification Station Fragment", player, 3)
|
return state.has("Modification Station Fragment", player, 3)
|
||||||
|
|
||||||
|
|
||||||
def has_mobile_vehicle_bay(state: "CollectionState", player: int):
|
def has_mobile_vehicle_bay(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Mobile Vehicle Bay Fragment", player, 3)
|
return state.has("Mobile Vehicle Bay Fragment", player, 3)
|
||||||
|
|
||||||
|
|
||||||
def has_moonpool(state: "CollectionState", player: int):
|
def has_moonpool(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Moonpool Fragment", player, 2)
|
return state.has("Moonpool Fragment", player, 2)
|
||||||
|
|
||||||
|
|
||||||
def has_vehicle_upgrade_console(state: "CollectionState", player: int):
|
def has_vehicle_upgrade_console(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Vehicle Upgrade Console", player) and \
|
return state.has("Vehicle Upgrade Console", player) and \
|
||||||
has_moonpool(state, player)
|
has_moonpool(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_seamoth(state: "CollectionState", player: int):
|
def has_seamoth(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Seamoth Fragment", player, 3) and \
|
return state.has("Seamoth Fragment", player, 3) and \
|
||||||
has_mobile_vehicle_bay(state, player)
|
has_mobile_vehicle_bay(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_seamoth_depth_module_mk1(state: "CollectionState", player: int):
|
def has_seamoth_depth_module_mk1(state: "CollectionState", player: int) -> bool:
|
||||||
return has_vehicle_upgrade_console(state, player)
|
return has_vehicle_upgrade_console(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_seamoth_depth_module_mk2(state: "CollectionState", player: int):
|
def has_seamoth_depth_module_mk2(state: "CollectionState", player: int) -> bool:
|
||||||
return has_seamoth_depth_module_mk1(state, player) and \
|
return has_seamoth_depth_module_mk1(state, player) and \
|
||||||
has_modification_station(state, player)
|
has_modification_station(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_seamoth_depth_module_mk3(state: "CollectionState", player: int):
|
def has_seamoth_depth_module_mk3(state: "CollectionState", player: int) -> bool:
|
||||||
return has_seamoth_depth_module_mk2(state, player) and \
|
return has_seamoth_depth_module_mk2(state, player) and \
|
||||||
has_modification_station(state, player)
|
has_modification_station(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_cyclops_bridge(state: "CollectionState", player: int):
|
def has_cyclops_bridge(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Cyclops Bridge Fragment", player, 3)
|
return state.has("Cyclops Bridge Fragment", player, 3)
|
||||||
|
|
||||||
|
|
||||||
def has_cyclops_engine(state: "CollectionState", player: int):
|
def has_cyclops_engine(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Cyclops Engine Fragment", player, 3)
|
return state.has("Cyclops Engine Fragment", player, 3)
|
||||||
|
|
||||||
|
|
||||||
def has_cyclops_hull(state: "CollectionState", player: int):
|
def has_cyclops_hull(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Cyclops Hull Fragment", player, 3)
|
return state.has("Cyclops Hull Fragment", player, 3)
|
||||||
|
|
||||||
|
|
||||||
def has_cyclops(state: "CollectionState", player: int):
|
def has_cyclops(state: "CollectionState", player: int) -> bool:
|
||||||
return has_cyclops_bridge(state, player) and \
|
return has_cyclops_bridge(state, player) and \
|
||||||
has_cyclops_engine(state, player) and \
|
has_cyclops_engine(state, player) and \
|
||||||
has_cyclops_hull(state, player) and \
|
has_cyclops_hull(state, player) and \
|
||||||
has_mobile_vehicle_bay(state, player)
|
has_mobile_vehicle_bay(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_cyclops_depth_module_mk1(state: "CollectionState", player: int):
|
def has_cyclops_depth_module_mk1(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Cyclops Depth Module MK1", player) and \
|
return state.has("Cyclops Depth Module MK1", player) and \
|
||||||
has_modification_station(state, player)
|
has_modification_station(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_cyclops_depth_module_mk2(state: "CollectionState", player: int):
|
def has_cyclops_depth_module_mk2(state: "CollectionState", player: int) -> bool:
|
||||||
return has_cyclops_depth_module_mk1(state, player) and \
|
return has_cyclops_depth_module_mk1(state, player) and \
|
||||||
has_modification_station(state, player)
|
has_modification_station(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_cyclops_depth_module_mk3(state: "CollectionState", player: int):
|
def has_cyclops_depth_module_mk3(state: "CollectionState", player: int) -> bool:
|
||||||
return has_cyclops_depth_module_mk2(state, player) and \
|
return has_cyclops_depth_module_mk2(state, player) and \
|
||||||
has_modification_station(state, player)
|
has_modification_station(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_prawn(state: "CollectionState", player: int):
|
def has_prawn(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Prawn Suit Fragment", player, 4) and \
|
return state.has("Prawn Suit Fragment", player, 4) and \
|
||||||
has_mobile_vehicle_bay(state, player)
|
has_mobile_vehicle_bay(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_prawn_propulsion_arm(state: "CollectionState", player: int):
|
def has_prawn_propulsion_arm(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Prawn Suit Propulsion Cannon Fragment", player, 2) and \
|
return state.has("Prawn Suit Propulsion Cannon Fragment", player, 2) and \
|
||||||
has_vehicle_upgrade_console(state, player)
|
has_vehicle_upgrade_console(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_prawn_depth_module_mk1(state: "CollectionState", player: int):
|
def has_prawn_depth_module_mk1(state: "CollectionState", player: int) -> bool:
|
||||||
return has_vehicle_upgrade_console(state, player)
|
return has_vehicle_upgrade_console(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_prawn_depth_module_mk2(state: "CollectionState", player: int):
|
def has_prawn_depth_module_mk2(state: "CollectionState", player: int) -> bool:
|
||||||
return has_prawn_depth_module_mk1(state, player) and \
|
return has_prawn_depth_module_mk1(state, player) and \
|
||||||
has_modification_station(state, player)
|
has_modification_station(state, player)
|
||||||
|
|
||||||
|
|
||||||
def has_laser_cutter(state: "CollectionState", player: int):
|
def has_laser_cutter(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Laser Cutter Fragment", player, 3)
|
return state.has("Laser Cutter Fragment", player, 3)
|
||||||
|
|
||||||
|
|
||||||
def has_stasis_rifle(state: "CollectionState", player: int):
|
def has_stasis_rifle(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Stasis Rifle Fragment", player, 2)
|
return state.has("Stasis Rifle Fragment", player, 2)
|
||||||
|
|
||||||
|
|
||||||
def has_containment(state: "CollectionState", player: int):
|
def has_containment(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Alien Containment Fragment", player, 2) and state.has("Multipurpose Room", player)
|
return state.has("Alien Containment Fragment", player, 2) and state.has("Multipurpose Room", player)
|
||||||
|
|
||||||
|
|
||||||
# Either we have propulsion cannon, or prawn + propulsion cannon arm
|
# Either we have propulsion cannon, or prawn + propulsion cannon arm
|
||||||
def has_propulsion_cannon(state: "CollectionState", player: int):
|
def has_propulsion_cannon(state: "CollectionState", player: int) -> bool:
|
||||||
return state.has("Propulsion Cannon Fragment", player, 2) or \
|
return state.has("Propulsion Cannon Fragment", player, 2)
|
||||||
(has_prawn(state, player) and has_prawn_propulsion_arm(state, player))
|
|
||||||
|
|
||||||
|
|
||||||
def has_cyclops_shield(state: "CollectionState", player: int):
|
def has_cyclops_shield(state: "CollectionState", player: int) -> bool:
|
||||||
return has_cyclops(state, player) and \
|
return has_cyclops(state, player) and \
|
||||||
state.has("Cyclops Shield Generator", player)
|
state.has("Cyclops Shield Generator", player)
|
||||||
|
|
||||||
|
|
||||||
|
def has_ultra_high_capacity_tank(state: "CollectionState", player: int) -> bool:
|
||||||
|
return has_modification_station(state, player) and state.has("Ultra High Capacity Tank", player)
|
||||||
|
|
||||||
|
|
||||||
|
def has_lightweight_high_capacity_tank(state: "CollectionState", player: int) -> bool:
|
||||||
|
return has_modification_station(state, player) and state.has("Lightweight High Capacity Tank", player)
|
||||||
|
|
||||||
|
|
||||||
|
def has_ultra_glide_fins(state: "CollectionState", player: int) -> bool:
|
||||||
|
return has_modification_station(state, player) and state.has("Ultra Glide Fins", player)
|
||||||
|
|
||||||
# Swim depth rules:
|
# Swim depth rules:
|
||||||
# Rebreather, high capacity tank and fins are available from the start.
|
# Rebreather, high capacity tank and fins are available from the start.
|
||||||
# All tests for those were done without inventory for light weight.
|
# All tests for those were done without inventory for light weight.
|
||||||
# Fins and ultra Fins are better than charge fins, so we ignore charge fins.
|
# Fins and ultra Fins are better than charge fins, so we ignore charge fins.
|
||||||
# We're ignoring lightweight tank in the chart, because the difference is
|
|
||||||
# negligeable with from high capacity tank. 430m -> 460m
|
|
||||||
# Fins are not used when using seaglide
|
|
||||||
#
|
|
||||||
def get_max_swim_depth(state: "CollectionState", player: int):
|
|
||||||
# TODO, Make this a difficulty setting.
|
|
||||||
# Only go up to 200m without any submarines for now.
|
|
||||||
return 200
|
|
||||||
|
|
||||||
# Rules bellow, are what are technically possible
|
# swim speeds: https://subnautica.fandom.com/wiki/Swimming_Speed
|
||||||
|
|
||||||
# has_ultra_high_capacity_tank = state.has("Ultra High Capacity Tank", player)
|
|
||||||
# has_ultra_glide_fins = state.has("Ultra Glide Fins", player)
|
|
||||||
|
|
||||||
# max_depth = 400 # More like 430m. Give some room
|
def get_max_swim_depth(state: "CollectionState", player: int) -> int:
|
||||||
# if has_seaglide(state: "CollectionState", player: int):
|
swim_rule: SwimRule = state.multiworld.swim_rule[player]
|
||||||
# if has_ultra_high_capacity_tank:
|
depth: int = swim_rule.base_depth
|
||||||
# max_depth = 750 # It's about 50m more. Give some room
|
if swim_rule == swim_rule.consider_items:
|
||||||
# else:
|
if has_seaglide(state, player):
|
||||||
# max_depth = 600 # It's about 50m more. Give some room
|
if has_ultra_high_capacity_tank(state, player):
|
||||||
# elif has_ultra_high_capacity_tank:
|
depth += 350 # It's about 800m. Give some room
|
||||||
# if has_ultra_glide_fins:
|
else:
|
||||||
# pass
|
depth += 200 # It's about 650m. Give some room
|
||||||
# else:
|
# seaglide and fins cannot be used together
|
||||||
# pass
|
elif has_ultra_glide_fins(state, player):
|
||||||
# elif has_ultra_glide_fins:
|
if has_ultra_high_capacity_tank(state, player):
|
||||||
# max_depth = 500
|
depth += 150
|
||||||
|
elif has_lightweight_high_capacity_tank(state, player):
|
||||||
# return max_depth
|
depth += 75
|
||||||
|
else:
|
||||||
|
depth += 50
|
||||||
|
elif has_ultra_high_capacity_tank(state, player):
|
||||||
|
depth += 100
|
||||||
|
elif has_lightweight_high_capacity_tank(state, player):
|
||||||
|
depth += 25
|
||||||
|
return depth
|
||||||
|
|
||||||
|
|
||||||
def get_seamoth_max_depth(state: "CollectionState", player: int):
|
def get_seamoth_max_depth(state: "CollectionState", player: int):
|
||||||
|
@ -203,13 +210,11 @@ def get_prawn_max_depth(state: "CollectionState", player):
|
||||||
|
|
||||||
|
|
||||||
def get_max_depth(state: "CollectionState", player: int):
|
def get_max_depth(state: "CollectionState", player: int):
|
||||||
# TODO, Difficulty option, we can add vehicle depth + swim depth
|
return get_max_swim_depth(state, player) + max(
|
||||||
# But at this point, we have to consider traver distance in caves, not
|
get_seamoth_max_depth(state, player),
|
||||||
# just depth
|
get_cyclops_max_depth(state, player),
|
||||||
return max(get_max_swim_depth(state, player),
|
get_prawn_max_depth(state, player)
|
||||||
get_seamoth_max_depth(state, player),
|
)
|
||||||
get_cyclops_max_depth(state, player),
|
|
||||||
get_prawn_max_depth(state, player))
|
|
||||||
|
|
||||||
|
|
||||||
def can_access_location(state: "CollectionState", player: int, loc: LocationDict) -> bool:
|
def can_access_location(state: "CollectionState", player: int, loc: LocationDict) -> bool:
|
||||||
|
@ -298,22 +303,22 @@ def set_rules(subnautica_world: "SubnauticaWorld"):
|
||||||
# Victory locations
|
# Victory locations
|
||||||
set_rule(world.get_location("Neptune Launch", player),
|
set_rule(world.get_location("Neptune Launch", player),
|
||||||
lambda state:
|
lambda state:
|
||||||
get_max_depth(state, player) >= 1444 and
|
get_max_depth(state, player) >= 1444 and
|
||||||
has_mobile_vehicle_bay(state, player) and
|
has_mobile_vehicle_bay(state, player) and
|
||||||
state.has("Neptune Launch Platform", player) and
|
state.has("Neptune Launch Platform", player) and
|
||||||
state.has("Neptune Gantry", player) and
|
state.has("Neptune Gantry", player) and
|
||||||
state.has("Neptune Boosters", player) and
|
state.has("Neptune Boosters", player) and
|
||||||
state.has("Neptune Fuel Reserve", player) and
|
state.has("Neptune Fuel Reserve", player) and
|
||||||
state.has("Neptune Cockpit", player) and
|
state.has("Neptune Cockpit", player) and
|
||||||
state.has("Ion Power Cell", player) and
|
state.has("Ion Power Cell", player) and
|
||||||
state.has("Ion Battery", player) and
|
state.has("Ion Battery", player) and
|
||||||
has_cyclops_shield(state, player))
|
has_cyclops_shield(state, player))
|
||||||
|
|
||||||
set_rule(world.get_location("Disable Quarantine", player), lambda state:
|
set_rule(world.get_location("Disable Quarantine", player), lambda state:
|
||||||
get_max_depth(state, player) >= 1444)
|
get_max_depth(state, player) >= 1444)
|
||||||
|
|
||||||
set_rule(world.get_location("Full Infection", player), lambda state:
|
set_rule(world.get_location("Full Infection", player), lambda state:
|
||||||
get_max_depth(state, player) >= 900)
|
get_max_depth(state, player) >= 900)
|
||||||
|
|
||||||
room = world.get_location("Aurora Drive Room - Upgrade Console", player)
|
room = world.get_location("Aurora Drive Room - Upgrade Console", player)
|
||||||
set_rule(world.get_location("Repair Aurora Drive", player), lambda state: room.can_reach(state))
|
set_rule(world.get_location("Repair Aurora Drive", player), lambda state: room.can_reach(state))
|
||||||
|
|
Loading…
Reference in New Issue