-
Notifications
You must be signed in to change notification settings - Fork 693
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'Daivuk/subnautica_clean' into subnautic…
…a_test
- Loading branch information
Showing
8 changed files
with
976 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.