Skip to content

Commit

Permalink
Merge remote-tracking branch 'Daivuk/subnautica_clean' into subnautic…
Browse files Browse the repository at this point in the history
…a_test
  • Loading branch information
Berserker66 committed Jul 23, 2021
2 parents 997a3e1 + d406e4c commit c99a689
Show file tree
Hide file tree
Showing 8 changed files with 976 additions and 0 deletions.
1 change: 1 addition & 0 deletions playerSettings.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ game:
A Link to the Past: 1
Factorio: 1
Minecraft: 1
Subnautica: 1
requires:
version: 0.1.3 # Version of Archipelago required for this yaml to work as expected.
# Shared Options supported by all games:
Expand Down
14 changes: 14 additions & 0 deletions worlds/subnautica/Items.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import json

with open('worlds/subnautica/items.json', 'r') as file:
item_table = json.loads(file.read())

lookup_id_to_name = {}
lookup_name_to_item = {}
for item in item_table:
lookup_id_to_name[item["id"]] = item["name"]
lookup_name_to_item[item["name"]] = item

lookup_id_to_name[None] = "Victory"

lookup_name_to_id = {name: id for id, name in lookup_id_to_name.items()}
11 changes: 11 additions & 0 deletions worlds/subnautica/Locations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import json

with open('worlds/subnautica/locations.json', 'r') as file:
location_table = json.loads(file.read())

lookup_id_to_name = {}
for item in location_table:
lookup_id_to_name[item["id"]] = item["name"]

lookup_id_to_name[None] = "Neptune Launch"
lookup_name_to_id = {name: id for id, name in lookup_id_to_name.items()}
8 changes: 8 additions & 0 deletions worlds/subnautica/Regions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
def create_regions(world, player: int):
from . import create_region
from .Locations import lookup_name_to_id as location_lookup_name_to_id

world.regions += [
create_region(world, player, 'Menu', None, ['Lifepod 5']),
create_region(world, player, 'Planet 4546B', [location for location in location_lookup_name_to_id])
]
254 changes: 254 additions & 0 deletions worlds/subnautica/Rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
from ..generic.Rules import set_rule
from .Locations import location_table
import logging
import math


def has_seaglide(state, player):
return state.has("Seaglide Fragment", player, 2)


def has_modification_station(state, player):
return state.has("Modification Station Fragment", player, 3)


def has_mobile_vehicle_bay(state, player):
return state.has("Mobile Vehicle Bay Fragment", player, 3)


def has_moonpool(state, player):
return state.has("Moonpool Fragment", player, 2)


def has_vehicle_upgrade_console(state, player):
return state.has("Vehicle Upgrade Console", player) and \
has_moonpool(state, player)


def has_seamoth(state, player):
return state.has("Seamoth Fragment", player, 3) and \
has_mobile_vehicle_bay(state, player)


def has_seamoth_depth_module_mk1(state, player):
return has_vehicle_upgrade_console(state, player)


def has_seamoth_depth_module_mk2(state, player):
return has_seamoth_depth_module_mk1(state, player) and \
has_modification_station(state, player)


def has_seamoth_depth_module_mk3(state, player):
return has_seamoth_depth_module_mk2(state, player) and \
has_modification_station(state, player)


def has_cyclops_bridge(state, player):
return state.has("Cyclops Bridge Fragment", player, 3)


def has_cyclops_engine(state, player):
return state.has("Cyclops Engine Fragment", player, 3)


def has_cyclops_hull(state, player):
return state.has("Cyclops Hull Fragment", player, 3)


def has_cyclops(state, player):
return has_cyclops_bridge(state, player) and \
has_cyclops_engine(state, player) and \
has_cyclops_hull(state, player) and \
has_mobile_vehicle_bay(state, player)


def has_cyclops_depth_module_mk1(state, player):
return state.has("Cyclops Depth Module MK1", player) and \
has_modification_station(state, player)


def has_cyclops_depth_module_mk2(state, player):
return has_cyclops_depth_module_mk1(state, player) and \
has_modification_station(state, player)


def has_cyclops_depth_module_mk3(state, player):
return has_cyclops_depth_module_mk2(state, player) and \
has_modification_station(state, player)


def has_prawn(state, player):
return state.has("Prawn Suit Fragment", player, 4) and \
has_mobile_vehicle_bay(state, player)


def has_praw_propulsion_arm(state, player):
return state.has("Prawn Suit Propulsion Cannon Fragment", player, 2) and \
has_vehicle_upgrade_console(state, player)


def has_prawn_depth_module_mk1(state, player):
return has_vehicle_upgrade_console(state, player)


def has_prawn_depth_module_mk2(state, player):
return has_prawn_depth_module_mk1(state, player) and \
has_modification_station(state, player)


def has_laser_cutter(state, player):
return state.has("Laser Cutter Fragment", player, 3)


# Either we have propulsion cannon, or prawn + propulsion cannon arm
def has_propulsion_cannon(state, player):
return state.has("Propulsion Cannon Fragment", player, 2) or \
(has_prawn(state, player) and has_praw_propulsion_arm(state, player))


def has_cyclops_shield(state, player):
return has_cyclops(state, player) and \
state.has("Cyclops Shield Generator", player)


# Swim depth rules:
# Rebreather, high capacity tank and fins are available from the start.
# 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.
# 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, player):
#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

# 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
# if has_seaglide(state, player):
# if has_ultra_high_capacity_tank:
# max_depth = 750 # It's about 50m more. Give some room
# else:
# max_depth = 600 # It's about 50m more. Give some room
# elif has_ultra_high_capacity_tank:
# if has_ultra_glide_fins:
# pass
# else:
# pass
# elif has_ultra_glide_fins:
# max_depth = 500

# return max_depth


def get_seamoth_max_depth(state, player):
if has_seamoth(state, player):
if has_seamoth_depth_module_mk3(state, player):
return 900
elif has_seamoth_depth_module_mk2(state, player): # Will never be the case, 3 is craftable
return 500
elif has_seamoth_depth_module_mk1(state, player):
return 300
else:
return 200
else:
return 0


def get_cyclops_max_depth(state, player):
if has_cyclops(state, player):
if has_cyclops_depth_module_mk3(state, player):
return 1700
elif has_cyclops_depth_module_mk2(state, player): # Will never be the case, 3 is craftable
return 1300
elif has_cyclops_depth_module_mk1(state, player):
return 900
else:
return 500
else:
return 0


def get_prawn_max_depth(state, player):
if has_prawn(state, player):
if has_prawn_depth_module_mk2(state, player):
return 1700
elif has_prawn_depth_module_mk1(state, player):
return 1300
else:
return 900
else:
return 0


def get_max_depth(state, player):
#TODO, Difficulty option, we can add vehicle depth + swim depth
# But at this point, we have to consider traver distance in caves, not
# just depth
return max(get_max_swim_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, player, loc):
# Extract location from game id.
# Game id is in format: "(x, y, z)"
pos_raw = loc.get("game_id")[1:-1].split(', ')
pos_x = float(pos_raw[0])
pos_y = float(pos_raw[1])
pos_z = float(pos_raw[2])
depth = -pos_y # y-up
map_center_dist = math.sqrt(pos_x**2 + pos_z**2)
aurora_dist = math.sqrt((pos_x - 1040)**2 + (pos_z - -160)**2)

need_radiation_suit = aurora_dist < 940
need_laser_cutter = loc.get("need_laser_cutter", False)
need_propulsion_cannon = loc.get("need_propulsion_cannon", False)

if need_laser_cutter and not has_laser_cutter(state, player):
return False

if need_radiation_suit and not state.has("Radiation Suit", player):
return False

if need_propulsion_cannon and not has_propulsion_cannon(state, player):
return False

# Seaglide doesn't unlock anything specific, but just allows for faster movement.
# Otherwise the game is painfully slow.
if (map_center_dist > 800 or pos_y < -200) and not has_seaglide(state, player):
return False

return get_max_depth(state, player) >= depth


def set_location_rule(world, player, loc):
set_rule(world.get_location(loc["name"], player), lambda state: can_access_location(state, player, loc))


def set_rules(world, player):
logging.warning(type(location_table))
for loc in location_table:
set_location_rule(world, player, loc)

# Victory location
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 \
state.has('Neptune Gantry', player) and \
state.has('Neptune Boosters', player) and \
state.has('Neptune Fuel Reserve', player) and \
state.has('Neptune Cockpit', player) and \
state.has('Ion Power Cell', player) and \
state.has('Ion Battery', player) and \
has_cyclops_shield(state, player))

world.completion_condition[player] = lambda state: state.has('Victory', player)
89 changes: 89 additions & 0 deletions worlds/subnautica/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import logging
from typing import Set

logger = logging.getLogger("Subnautica")

from .Locations import lookup_name_to_id as locations_lookup_name_to_id
from .Items import item_table
from .Items import lookup_name_to_id as items_lookup_name_to_id

from .Regions import create_regions
from .Rules import set_rules

from BaseClasses import Region, Entrance, Location, MultiWorld, Item
from ..AutoWorld import World


class SubnauticaWorld(World):
game: str = "Subnautica"
item_names: Set[str] = frozenset(items_lookup_name_to_id)
location_names: Set[str] = frozenset(locations_lookup_name_to_id)

item_name_to_id = items_lookup_name_to_id
location_name_to_id = locations_lookup_name_to_id

def generate_basic(self):
# Link regions
self.world.get_entrance('Lifepod 5', self.player).connect(self.world.get_region('Planet 4546B', self.player))

# Generate item pool
pool = []
neptune_launch_platform = None
for item in item_table:
for i in range(item["count"]):
subnautica_item = SubnauticaItem(item["name"], item["progression"], item["id"], player = self.player)
if item["name"] == "Neptune Launch Platform":
neptune_launch_platform = subnautica_item
else:
pool.append(subnautica_item)
self.world.itempool += pool

# Victory item
self.world.get_location("Aurora - Captain Data Terminal", self.player).place_locked_item(neptune_launch_platform)
self.world.get_location("Neptune Launch", self.player).place_locked_item(SubnauticaItem("Victory", True, None, player = self.player))


def set_rules(self):
set_rules(self.world, self.player)


def create_regions(self):
create_regions(self.world, self.player)


def generate_output(self, output_directory: str):
pass


def fill_slot_data(self):
slot_data = {}
return slot_data


def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
ret = Region(name, None, name, player)
ret.world = world
if locations:
for location in locations:
loc_id = locations_lookup_name_to_id.get(location, 0)
location = SubnauticaLocation(player, location, loc_id, ret)
ret.locations.append(location)
if exits:
for exit in exits:
ret.exits.append(Entrance(player, exit, ret))

return ret


class SubnauticaLocation(Location):
game: str = "Subnautica"

def __init__(self, player: int, name: str, address=None, parent=None):
super(SubnauticaLocation, self).__init__(player, name, address, parent)


class SubnauticaItem(Item):
game = "Subnautica"

def __init__(self, name, advancement, code, player: int = None):
super(SubnauticaItem, self).__init__(name, advancement, code, player)
Loading

0 comments on commit c99a689

Please sign in to comment.