mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-09-21 07:04:34 -07:00
Merge branch 'main' into add_dark_souls_III
This commit is contained in:
@@ -154,8 +154,10 @@ def autogen(config: dict):
|
||||
while 1:
|
||||
time.sleep(0.1)
|
||||
with db_session:
|
||||
# for update locks the database row(s) during transaction, preventing writes from elsewhere
|
||||
to_start = select(
|
||||
generation for generation in Generation if generation.state == STATE_QUEUED)
|
||||
generation for generation in Generation
|
||||
if generation.state == STATE_QUEUED).for_update()
|
||||
for generation in to_start:
|
||||
launch_generator(generator_pool, generation)
|
||||
except AlreadyRunningException:
|
||||
|
||||
+18
-20
@@ -4,7 +4,7 @@ import random
|
||||
import json
|
||||
import zipfile
|
||||
from collections import Counter
|
||||
from typing import Dict, Optional as TypeOptional
|
||||
from typing import Dict, Optional, Any
|
||||
from Utils import __version__
|
||||
|
||||
from flask import request, flash, redirect, url_for, session, render_template
|
||||
@@ -15,7 +15,7 @@ from BaseClasses import seeddigits, get_seed
|
||||
from Generate import handle_name, PlandoSettings
|
||||
import pickle
|
||||
|
||||
from .models import *
|
||||
from .models import Generation, STATE_ERROR, STATE_QUEUED, commit, db_session, Seed, UUID
|
||||
from WebHostLib import app
|
||||
from .check import get_yaml_data, roll_options
|
||||
from .upload import upload_zip_to_db
|
||||
@@ -30,16 +30,15 @@ def get_meta(options_source: dict) -> dict:
|
||||
}
|
||||
plando_options -= {""}
|
||||
|
||||
meta = {
|
||||
server_options = {
|
||||
"hint_cost": int(options_source.get("hint_cost", 10)),
|
||||
"forfeit_mode": options_source.get("forfeit_mode", "goal"),
|
||||
"remaining_mode": options_source.get("remaining_mode", "disabled"),
|
||||
"collect_mode": options_source.get("collect_mode", "disabled"),
|
||||
"item_cheat": bool(int(options_source.get("item_cheat", 1))),
|
||||
"server_password": options_source.get("server_password", None),
|
||||
"plando_options": list(plando_options)
|
||||
}
|
||||
return meta
|
||||
return {"server_options": server_options, "plando_options": list(plando_options)}
|
||||
|
||||
|
||||
@app.route('/generate', methods=['GET', 'POST'])
|
||||
@@ -60,13 +59,13 @@ def generate(race=False):
|
||||
results, gen_options = roll_options(options, meta["plando_options"])
|
||||
|
||||
if race:
|
||||
meta["item_cheat"] = False
|
||||
meta["remaining_mode"] = "disabled"
|
||||
meta["server_options"]["item_cheat"] = False
|
||||
meta["server_options"]["remaining_mode"] = "disabled"
|
||||
|
||||
if any(type(result) == str for result in results.values()):
|
||||
return render_template("checkResult.html", results=results)
|
||||
elif len(gen_options) > app.config["MAX_ROLL"]:
|
||||
flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players for now. "
|
||||
flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players. "
|
||||
f"If you have a larger group, please generate it yourself and upload it.")
|
||||
elif len(gen_options) >= app.config["JOB_THRESHOLD"]:
|
||||
gen = Generation(
|
||||
@@ -92,23 +91,22 @@ def generate(race=False):
|
||||
return render_template("generate.html", race=race, version=__version__)
|
||||
|
||||
|
||||
def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=None, sid=None):
|
||||
def gen_game(gen_options, meta: Optional[Dict[str, Any]] = None, owner=None, sid=None):
|
||||
if not meta:
|
||||
meta: Dict[str, object] = {}
|
||||
meta: Dict[str, Any] = {}
|
||||
|
||||
meta.setdefault("server_options", {}).setdefault("hint_cost", 10)
|
||||
race = meta.setdefault("race", False)
|
||||
|
||||
meta.setdefault("hint_cost", 10)
|
||||
race = meta.get("race", False)
|
||||
del (meta["race"])
|
||||
plando_options = meta.get("plando", {"bosses", "items", "connections", "texts"})
|
||||
del (meta["plando_options"])
|
||||
try:
|
||||
target = tempfile.TemporaryDirectory()
|
||||
playercount = len(gen_options)
|
||||
seed = get_seed()
|
||||
random.seed(seed)
|
||||
|
||||
if race:
|
||||
random.seed() # reset to time-based random source
|
||||
random.seed() # use time-based random source
|
||||
else:
|
||||
random.seed(seed)
|
||||
|
||||
seedname = "W" + (f"{random.randint(0, pow(10, seeddigits) - 1)}".zfill(seeddigits))
|
||||
|
||||
@@ -120,7 +118,8 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No
|
||||
erargs.outputname = seedname
|
||||
erargs.outputpath = target.name
|
||||
erargs.teams = 1
|
||||
erargs.plando_options = PlandoSettings.from_set(plando_options)
|
||||
erargs.plando_options = PlandoSettings.from_set(meta.setdefault("plando_options",
|
||||
{"bosses", "items", "connections", "texts"}))
|
||||
|
||||
name_counter = Counter()
|
||||
for player, (playerfile, settings) in enumerate(gen_options.items(), 1):
|
||||
@@ -136,7 +135,7 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No
|
||||
erargs.name[player] = handle_name(erargs.name[player], player, name_counter)
|
||||
if len(set(erargs.name.values())) != len(erargs.name):
|
||||
raise Exception(f"Names have to be unique. Names: {Counter(erargs.name.values())}")
|
||||
ERmain(erargs, seed, baked_server_options=meta)
|
||||
ERmain(erargs, seed, baked_server_options=meta["server_options"])
|
||||
|
||||
return upload_to_db(target.name, sid, owner, race)
|
||||
except BaseException as e:
|
||||
@@ -148,7 +147,6 @@ def gen_game(gen_options, meta: TypeOptional[Dict[str, object]] = None, owner=No
|
||||
meta = json.loads(gen.meta)
|
||||
meta["error"] = (e.__class__.__name__ + ": " + str(e))
|
||||
gen.meta = json.dumps(meta)
|
||||
|
||||
commit()
|
||||
raise
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@ Type: dirifempty; Name: "{app}"
|
||||
|
||||
[InstallDelete]
|
||||
Type: files; Name: "{app}\ArchipelagoLttPClient.exe"
|
||||
Type: filesandordirs; Name: "{app}\lib\worlds\rogue-legacy*"
|
||||
|
||||
[Registry]
|
||||
|
||||
|
||||
+7
-3
@@ -175,12 +175,15 @@ A Link to the Past:
|
||||
retro_caves:
|
||||
on: 0 # Zelda-1 like mode. There are randomly placed take-any caves that contain one Sword and choices of Heart Container/Blue Potion.
|
||||
off: 50
|
||||
hints: # Vendors: King Zora and Bottle Merchant say what they're selling.
|
||||
# On/Full: Put item and entrance placement hints on telepathic tiles and some NPCs, Full removes joke hints.
|
||||
hints: # On/Full: Put item and entrance placement hints on telepathic tiles and some NPCs, Full removes joke hints.
|
||||
'on': 50
|
||||
vendors: 0
|
||||
'off': 0
|
||||
full: 0
|
||||
scams: # If on, these Merchants will no longer tell you what they're selling.
|
||||
'off': 50
|
||||
'king_zora': 0
|
||||
'bottle_merchant': 0
|
||||
'all': 0
|
||||
swordless:
|
||||
on: 0 # Your swords are replaced by rupees. Gameplay changes have been made to accommodate this change
|
||||
off: 1
|
||||
@@ -273,6 +276,7 @@ A Link to the Past:
|
||||
p: 0 # Randomize the prices of the items in shop inventories
|
||||
u: 0 # Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld)
|
||||
w: 0 # Consider witch's hut like any other shop and shuffle/randomize it too
|
||||
P: 0 # Prices of the items in shop inventories cost hearts, arrow, or bombs instead of rupees
|
||||
ip: 0 # Shuffle inventories and randomize prices
|
||||
fpu: 0 # Generate new inventories, randomize prices and shuffle capacity upgrades into item pool
|
||||
uip: 0 # Shuffle inventories, randomize prices and shuffle capacity upgrades into the item pool
|
||||
|
||||
@@ -1088,10 +1088,10 @@ def get_pool_core(world):
|
||||
placed_items['Hideout Jail Guard (4 Torches)'] = 'Recovery Heart'
|
||||
skip_in_spoiler_locations.extend(['Hideout Jail Guard (2 Torches)', 'Hideout Jail Guard (3 Torches)', 'Hideout Jail Guard (4 Torches)'])
|
||||
else:
|
||||
placed_items['Hideout Jail Guard (1 Torch)'] = 'Small Key (Gerudo Fortress)'
|
||||
placed_items['Hideout Jail Guard (2 Torches)'] = 'Small Key (Gerudo Fortress)'
|
||||
placed_items['Hideout Jail Guard (3 Torches)'] = 'Small Key (Gerudo Fortress)'
|
||||
placed_items['Hideout Jail Guard (4 Torches)'] = 'Small Key (Gerudo Fortress)'
|
||||
placed_items['Hideout Jail Guard (1 Torch)'] = 'Small Key (Thieves Hideout)'
|
||||
placed_items['Hideout Jail Guard (2 Torches)'] = 'Small Key (Thieves Hideout)'
|
||||
placed_items['Hideout Jail Guard (3 Torches)'] = 'Small Key (Thieves Hideout)'
|
||||
placed_items['Hideout Jail Guard (4 Torches)'] = 'Small Key (Thieves Hideout)'
|
||||
|
||||
if world.shuffle_gerudo_card and world.gerudo_fortress != 'open':
|
||||
pool.append('Gerudo Membership Card')
|
||||
|
||||
@@ -619,6 +619,7 @@ class Patch:
|
||||
if (self.myWorld.Config.Keysanity):
|
||||
self.patches.append((Snes(0x40003B), [ 1 ])) #// MapMode #$00 = Always On (default) - #$01 = Require Map Item
|
||||
self.patches.append((Snes(0x400045), [ 0x0f ])) #// display ----dcba a: Small Keys, b: Big Key, c: Map, d: Compass
|
||||
self.patches.append((Snes(0x40016A), [ 0x01 ])) #// enable local item dialog boxes for dungeon and keycard items
|
||||
|
||||
def WriteSMKeyCardDoors(self):
|
||||
if (not self.myWorld.Config.Keysanity):
|
||||
|
||||
+17
-7
@@ -27,14 +27,18 @@ class SMZ3CollectionState(metaclass=AutoLogicRegister):
|
||||
# for unit tests where MultiWorld is instantiated before worlds
|
||||
if hasattr(parent, "state"):
|
||||
self.smz3state = {player: TotalSMZ3Item.Progression([]) for player in parent.get_game_players("SMZ3")}
|
||||
for player, group in parent.groups.items():
|
||||
if (group["game"] == "SMZ3"):
|
||||
self.smz3state[player] = TotalSMZ3Item.Progression([])
|
||||
if player not in parent.state.smz3state:
|
||||
parent.state.smz3state[player] = TotalSMZ3Item.Progression([])
|
||||
else:
|
||||
self.smz3state = {}
|
||||
|
||||
def copy_mixin(self, ret) -> CollectionState:
|
||||
ret.smz3state = {player: copy.deepcopy(self.smz3state[player]) for player in self.world.get_game_players("SMZ3")}
|
||||
ret.smz3state = {player: copy.deepcopy(self.smz3state[player]) for player in self.smz3state}
|
||||
return ret
|
||||
|
||||
|
||||
class SMZ3Web(WebWorld):
|
||||
tutorials = [Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
@@ -106,6 +110,7 @@ class SMZ3World(World):
|
||||
niceItems = TotalSMZ3Item.Item.CreateNicePool(self.smz3World)
|
||||
junkItems = TotalSMZ3Item.Item.CreateJunkPool(self.smz3World)
|
||||
allJunkItems = niceItems + junkItems
|
||||
self.junkItemsNames = [item.Type.name for item in junkItems]
|
||||
|
||||
if (self.smz3World.Config.Keysanity):
|
||||
progressionItems = self.progression + self.dungeon + self.keyCardsItems
|
||||
@@ -256,11 +261,11 @@ class SMZ3World(World):
|
||||
base_combined_rom = basepatch.apply(base_combined_rom)
|
||||
|
||||
patcher = TotalSMZ3Patch(self.smz3World,
|
||||
[world.smz3World for key, world in self.world.worlds.items() if isinstance(world, SMZ3World)],
|
||||
[world.smz3World for key, world in self.world.worlds.items() if isinstance(world, SMZ3World) and hasattr(world, "smz3World")],
|
||||
self.world.seed_name,
|
||||
self.world.seed,
|
||||
self.local_random,
|
||||
self.world.world_name_lookup,
|
||||
{v: k for k, v in self.world.player_name.items()},
|
||||
next(iter(loc.player for loc in self.world.get_locations() if (loc.item.name == "SilverArrows" and loc.item.player == self.player))))
|
||||
patches = patcher.Create(self.smz3World.Config)
|
||||
patches.update(self.apply_sm_custom_sprite())
|
||||
@@ -312,7 +317,7 @@ class SMZ3World(World):
|
||||
return slot_data
|
||||
|
||||
def collect(self, state: CollectionState, item: Item) -> bool:
|
||||
state.smz3state[item.player].Add([TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World)])
|
||||
state.smz3state[self.player].Add([TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World if hasattr(self, "smz3World") else None)])
|
||||
if item.advancement:
|
||||
state.prog_items[item.name, item.player] += 1
|
||||
return True # indicate that a logical state change has occured
|
||||
@@ -321,7 +326,7 @@ class SMZ3World(World):
|
||||
def remove(self, state: CollectionState, item: Item) -> bool:
|
||||
name = self.collect_item(state, item, True)
|
||||
if name:
|
||||
state.smz3state[item.player].Remove([TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World)])
|
||||
state.smz3state[item.player].Remove([TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[item.name], self.smz3World if hasattr(self, "smz3World") else None)])
|
||||
state.prog_items[name, item.player] -= 1
|
||||
if state.prog_items[name, item.player] < 1:
|
||||
del (state.prog_items[name, item.player])
|
||||
@@ -330,7 +335,9 @@ class SMZ3World(World):
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
return SMZ3Item(name, ItemClassification.progression,
|
||||
TotalSMZ3Item.ItemType[name], self.item_name_to_id[name], player = self.player)
|
||||
TotalSMZ3Item.ItemType[name], self.item_name_to_id[name],
|
||||
self.player,
|
||||
TotalSMZ3Item.Item(TotalSMZ3Item.ItemType[name], self))
|
||||
|
||||
def pre_fill(self):
|
||||
from Fill import fill_restrictive
|
||||
@@ -364,6 +371,9 @@ class SMZ3World(World):
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.world.random.choice(self.junkItemsNames)
|
||||
|
||||
def write_spoiler(self, spoiler_handle: TextIO):
|
||||
self.world.spoiler.unreachables.update(self.unreachable)
|
||||
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,82 @@
|
||||
from typing import Dict, Set, List
|
||||
|
||||
# EN Locale Creature Name to rough depth in meters found at
|
||||
all_creatures: Dict[str, int] = {
|
||||
"Gasopod": 0,
|
||||
"Bladderfish": 0,
|
||||
"Ancient Floater": 0,
|
||||
"Skyray": 0,
|
||||
"Garryfish": 0,
|
||||
"Peeper": 0,
|
||||
"Shuttlebug": 0,
|
||||
"Rabbit Ray": 0,
|
||||
"Stalker": 0,
|
||||
"Floater": 0,
|
||||
"Holefish": 0,
|
||||
"Cave Crawler": 0,
|
||||
"Hoopfish": 0,
|
||||
"Crashfish": 0,
|
||||
"Hoverfish": 0,
|
||||
"Spadefish": 0,
|
||||
"Reefback Leviathan": 0,
|
||||
"Reaper Leviathan": 0,
|
||||
"Warper": 0,
|
||||
"Boomerang": 0,
|
||||
"Biter": 200,
|
||||
"Sand Shark": 200,
|
||||
"Bleeder": 200,
|
||||
"Crabsnake": 300,
|
||||
"Jellyray": 300,
|
||||
"Oculus": 300,
|
||||
"Mesmer": 300,
|
||||
"Eyeye": 300,
|
||||
"Reginald": 400,
|
||||
"Sea Treader Leviathan": 400,
|
||||
"Crabsquid": 400,
|
||||
"Ampeel": 400,
|
||||
"Boneshark": 400,
|
||||
"Rockgrub": 400,
|
||||
"Ghost Leviathan": 500,
|
||||
"Ghost Leviathan Juvenile": 500,
|
||||
"Spinefish": 600,
|
||||
"Blighter": 600,
|
||||
"Blood Crawler": 600,
|
||||
"Ghostray": 1000,
|
||||
"Amoeboid": 1000,
|
||||
"River Prowler": 1000,
|
||||
"Red Eyeye": 1300,
|
||||
"Magmarang": 1300,
|
||||
"Crimson Ray": 1300,
|
||||
"Lava Larva": 1300,
|
||||
"Lava Lizard": 1300,
|
||||
"Sea Dragon Leviathan": 1300,
|
||||
"Sea Emperor Leviathan": 1700,
|
||||
"Sea Emperor Juvenile": 1700,
|
||||
|
||||
# "Cuddlefish": 300, # maybe at some point, needs hatching in containment chamber (20 real-life minutes)
|
||||
}
|
||||
|
||||
# be nice and make these require Stasis Rifle
|
||||
aggressive: Set[str] = {
|
||||
"Cave Crawler", # is very easy without Stasis Rifle, but included for consistency
|
||||
"Crashfish",
|
||||
"Bleeder",
|
||||
"Mesmer",
|
||||
"Reaper Leviathan",
|
||||
"Crabsquid",
|
||||
"Warper",
|
||||
"Crabsnake",
|
||||
"Ampeel",
|
||||
"Boneshark",
|
||||
"Lava Lizard",
|
||||
"Sea Dragon Leviathan",
|
||||
"River Prowler",
|
||||
}
|
||||
|
||||
suffix: str = " Scan"
|
||||
|
||||
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)
|
||||
@@ -166,7 +166,7 @@ item_table: Dict[int, ItemDict] = {
|
||||
'count': 5,
|
||||
'name': 'Seamoth Fragment',
|
||||
'tech_type': 'SeamothFragment'},
|
||||
35039: {'classification': ItemClassification.useful,
|
||||
35039: {'classification': ItemClassification.progression,
|
||||
'count': 2,
|
||||
'name': 'Stasis Rifle Fragment',
|
||||
'tech_type': 'StasisRifleFragment'},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from Options import Choice
|
||||
from Options import Choice, Range
|
||||
from .Creatures import all_creatures
|
||||
|
||||
|
||||
class ItemPool(Choice):
|
||||
@@ -31,7 +32,15 @@ class Goal(Choice):
|
||||
}[self.value]
|
||||
|
||||
|
||||
class CreatureScans(Range):
|
||||
"""Place items on specific creature scans.
|
||||
Warning: Includes aggressive Leviathans."""
|
||||
display_name = "Creature Scans"
|
||||
range_end = len(all_creatures)
|
||||
|
||||
|
||||
options = {
|
||||
"item_pool": ItemPool,
|
||||
"goal": Goal,
|
||||
"creature_scans": CreatureScans
|
||||
}
|
||||
|
||||
+56
-30
@@ -1,112 +1,122 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from worlds.generic.Rules import set_rule
|
||||
from .Locations import location_table, LocationDict
|
||||
from .Creatures import all_creatures, aggressive, suffix
|
||||
import math
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import SubnauticaWorld
|
||||
|
||||
def has_seaglide(state, player):
|
||||
|
||||
def has_seaglide(state, player: int):
|
||||
return state.has("Seaglide Fragment", player, 2)
|
||||
|
||||
|
||||
def has_modification_station(state, player):
|
||||
def has_modification_station(state, player: int):
|
||||
return state.has("Modification Station Fragment", player, 3)
|
||||
|
||||
|
||||
def has_mobile_vehicle_bay(state, player):
|
||||
def has_mobile_vehicle_bay(state, player: int):
|
||||
return state.has("Mobile Vehicle Bay Fragment", player, 3)
|
||||
|
||||
|
||||
def has_moonpool(state, player):
|
||||
def has_moonpool(state, player: int):
|
||||
return state.has("Moonpool Fragment", player, 2)
|
||||
|
||||
|
||||
def has_vehicle_upgrade_console(state, player):
|
||||
def has_vehicle_upgrade_console(state, player: int):
|
||||
return state.has("Vehicle Upgrade Console", player) and \
|
||||
has_moonpool(state, player)
|
||||
|
||||
|
||||
def has_seamoth(state, player):
|
||||
def has_seamoth(state, player: int):
|
||||
return state.has("Seamoth Fragment", player, 3) and \
|
||||
has_mobile_vehicle_bay(state, player)
|
||||
|
||||
|
||||
def has_seamoth_depth_module_mk1(state, player):
|
||||
def has_seamoth_depth_module_mk1(state, player: int):
|
||||
return has_vehicle_upgrade_console(state, player)
|
||||
|
||||
|
||||
def has_seamoth_depth_module_mk2(state, player):
|
||||
def has_seamoth_depth_module_mk2(state, player: int):
|
||||
return has_seamoth_depth_module_mk1(state, player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_seamoth_depth_module_mk3(state, player):
|
||||
def has_seamoth_depth_module_mk3(state, player: int):
|
||||
return has_seamoth_depth_module_mk2(state, player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_cyclops_bridge(state, player):
|
||||
def has_cyclops_bridge(state, player: int):
|
||||
return state.has("Cyclops Bridge Fragment", player, 3)
|
||||
|
||||
|
||||
def has_cyclops_engine(state, player):
|
||||
def has_cyclops_engine(state, player: int):
|
||||
return state.has("Cyclops Engine Fragment", player, 3)
|
||||
|
||||
|
||||
def has_cyclops_hull(state, player):
|
||||
def has_cyclops_hull(state, player: int):
|
||||
return state.has("Cyclops Hull Fragment", player, 3)
|
||||
|
||||
|
||||
def has_cyclops(state, player):
|
||||
def has_cyclops(state, player: int):
|
||||
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):
|
||||
def has_cyclops_depth_module_mk1(state, player: int):
|
||||
return state.has("Cyclops Depth Module MK1", player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_cyclops_depth_module_mk2(state, player):
|
||||
def has_cyclops_depth_module_mk2(state, player: int):
|
||||
return has_cyclops_depth_module_mk1(state, player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_cyclops_depth_module_mk3(state, player):
|
||||
def has_cyclops_depth_module_mk3(state, player: int):
|
||||
return has_cyclops_depth_module_mk2(state, player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_prawn(state, player):
|
||||
def has_prawn(state, player: int):
|
||||
return state.has("Prawn Suit Fragment", player, 4) and \
|
||||
has_mobile_vehicle_bay(state, player)
|
||||
|
||||
|
||||
def has_praw_propulsion_arm(state, player):
|
||||
def has_praw_propulsion_arm(state, player: int):
|
||||
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):
|
||||
def has_prawn_depth_module_mk1(state, player: int):
|
||||
return has_vehicle_upgrade_console(state, player)
|
||||
|
||||
|
||||
def has_prawn_depth_module_mk2(state, player):
|
||||
def has_prawn_depth_module_mk2(state, player: int):
|
||||
return has_prawn_depth_module_mk1(state, player) and \
|
||||
has_modification_station(state, player)
|
||||
|
||||
|
||||
def has_laser_cutter(state, player):
|
||||
def has_laser_cutter(state, player: int):
|
||||
return state.has("Laser Cutter Fragment", player, 3)
|
||||
|
||||
|
||||
def has_stasis_rile(state, player: int):
|
||||
return state.has("Stasis Rifle Fragment", player, 2)
|
||||
|
||||
|
||||
# Either we have propulsion cannon, or prawn + propulsion cannon arm
|
||||
def has_propulsion_cannon(state, player):
|
||||
def has_propulsion_cannon(state, player: int):
|
||||
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):
|
||||
def has_cyclops_shield(state, player: int):
|
||||
return has_cyclops(state, player) and \
|
||||
state.has("Cyclops Shield Generator", player)
|
||||
|
||||
@@ -119,7 +129,7 @@ def has_cyclops_shield(state, player):
|
||||
# negligeable with from high capacity tank. 430m -> 460m
|
||||
# Fins are not used when using seaglide
|
||||
#
|
||||
def get_max_swim_depth(state, player):
|
||||
def get_max_swim_depth(state, player: int):
|
||||
# TODO, Make this a difficulty setting.
|
||||
# Only go up to 200m without any submarines for now.
|
||||
return 200
|
||||
@@ -130,7 +140,7 @@ def get_max_swim_depth(state, 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_seaglide(state, player: int):
|
||||
# if has_ultra_high_capacity_tank:
|
||||
# max_depth = 750 # It's about 50m more. Give some room
|
||||
# else:
|
||||
@@ -146,7 +156,7 @@ def get_max_swim_depth(state, player):
|
||||
# return max_depth
|
||||
|
||||
|
||||
def get_seamoth_max_depth(state, player):
|
||||
def get_seamoth_max_depth(state, player: int):
|
||||
if has_seamoth(state, player):
|
||||
if has_seamoth_depth_module_mk3(state, player):
|
||||
return 900
|
||||
@@ -186,7 +196,7 @@ def get_prawn_max_depth(state, player):
|
||||
return 0
|
||||
|
||||
|
||||
def get_max_depth(state, player):
|
||||
def get_max_depth(state, player: int):
|
||||
# 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
|
||||
@@ -196,7 +206,7 @@ def get_max_depth(state, player):
|
||||
get_prawn_max_depth(state, player))
|
||||
|
||||
|
||||
def can_access_location(state, player: int, loc: LocationDict):
|
||||
def can_access_location(state, player: int, loc: LocationDict) -> bool:
|
||||
need_laser_cutter = loc.get("need_laser_cutter", False)
|
||||
if need_laser_cutter and not has_laser_cutter(state, player):
|
||||
return False
|
||||
@@ -225,17 +235,33 @@ def can_access_location(state, player: int, loc: LocationDict):
|
||||
return get_max_depth(state, player) >= depth
|
||||
|
||||
|
||||
def set_location_rule(world, player, loc):
|
||||
def set_location_rule(world, player: int, loc: LocationDict):
|
||||
set_rule(world.get_location(loc["name"], player), lambda state: can_access_location(state, player, loc))
|
||||
|
||||
|
||||
def set_rules(subnautica_world):
|
||||
def can_scan_creature(state, player: int, creature: str) -> bool:
|
||||
if not has_seaglide(state, player):
|
||||
return False
|
||||
if creature in aggressive and not has_stasis_rile(state, player):
|
||||
return False
|
||||
return get_max_depth(state, player) >= all_creatures[creature]
|
||||
|
||||
|
||||
def set_creature_rule(world, player, creature_name: str):
|
||||
set_rule(world.get_location(creature_name + suffix, player),
|
||||
lambda state: can_scan_creature(state, player, creature_name))
|
||||
|
||||
|
||||
def set_rules(subnautica_world: "SubnauticaWorld"):
|
||||
player = subnautica_world.player
|
||||
world = subnautica_world.world
|
||||
|
||||
for loc in location_table.values():
|
||||
set_location_rule(world, player, loc)
|
||||
|
||||
for creature_name in subnautica_world.creatures_to_scan:
|
||||
set_creature_rule(world, player, creature_name)
|
||||
|
||||
# Victory locations
|
||||
set_rule(world.get_location("Neptune Launch", player), lambda state:
|
||||
get_max_depth(state, player) >= 1444 and
|
||||
|
||||
@@ -5,6 +5,7 @@ from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassifi
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from . import Items
|
||||
from . import Locations
|
||||
from . import Creatures
|
||||
from . import Options
|
||||
from .Items import item_table
|
||||
from .Rules import set_rules
|
||||
@@ -23,6 +24,10 @@ class SubnaticaWeb(WebWorld):
|
||||
)]
|
||||
|
||||
|
||||
all_locations = {data["name"]: loc_id for loc_id, data in Locations.location_table.items()}
|
||||
all_locations.update(Creatures.creature_locations)
|
||||
|
||||
|
||||
class SubnauticaWorld(World):
|
||||
"""
|
||||
Subnautica is an undersea exploration game. Stranded on an alien world, you become infected by
|
||||
@@ -33,25 +38,30 @@ class SubnauticaWorld(World):
|
||||
web = SubnaticaWeb()
|
||||
|
||||
item_name_to_id = {data["name"]: item_id for item_id, data in Items.item_table.items()}
|
||||
location_name_to_id = {data["name"]: loc_id for loc_id, data in Locations.location_table.items()}
|
||||
location_name_to_id = all_locations
|
||||
options = Options.options
|
||||
|
||||
data_version = 2
|
||||
data_version = 3
|
||||
required_client_version = (0, 3, 3)
|
||||
|
||||
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")
|
||||
]
|
||||
self.creatures_to_scan = self.world.random.sample(Creatures.all_creatures_presorted,
|
||||
self.world.creature_scans[self.player].value)
|
||||
|
||||
def create_regions(self):
|
||||
self.world.regions += [
|
||||
self.create_region("Menu", None, ["Lifepod 5"]),
|
||||
self.create_region("Planet 4546B",
|
||||
Locations.events + [location["name"] for location in Locations.location_table.values()])
|
||||
Locations.events +
|
||||
[location["name"] for location in Locations.location_table.values()] +
|
||||
[creature+Creatures.suffix for creature in self.creatures_to_scan])
|
||||
]
|
||||
|
||||
# refer to Rules.py
|
||||
@@ -64,7 +74,7 @@ class SubnauticaWorld(World):
|
||||
# Generate item pool
|
||||
pool = []
|
||||
neptune_launch_platform = None
|
||||
extras = 0
|
||||
extras = self.world.creature_scans[self.player].value
|
||||
valuable = self.world.item_pool[self.player] == Options.ItemPool.option_valuable
|
||||
for item in item_table.values():
|
||||
for i in range(item["count"]):
|
||||
@@ -105,6 +115,7 @@ class SubnauticaWorld(World):
|
||||
slot_data: Dict[str, Any] = {
|
||||
"goal": goal.current_key,
|
||||
"vanilla_tech": vanilla_tech,
|
||||
"creatures_to_scan": self.creatures_to_scan
|
||||
}
|
||||
|
||||
return slot_data
|
||||
|
||||
Reference in New Issue
Block a user