diff --git a/BaseClasses.py b/BaseClasses.py index e101642a..e799af5e 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -213,9 +213,8 @@ class MultiWorld(): except KeyError as e: raise KeyError('No such dungeon %s for player %d' % (dungeonname, player)) from e - def get_all_state(self, keys=False) -> CollectionState: - key = f"_all_state_{keys}" - cached = getattr(self, key, None) + def get_all_state(self) -> CollectionState: + cached = getattr(self, "_all_state", None) if cached: return cached.copy() @@ -223,27 +222,12 @@ class MultiWorld(): for item in self.itempool: self.worlds[item.player].collect(ret, item) - - if keys: - for p in self.get_game_players("A Link to the Past"): - world = self.worlds[p] - from worlds.alttp.Items import ItemFactory - for item in ItemFactory( - ['Small Key (Hyrule Castle)', 'Big Key (Eastern Palace)', 'Big Key (Desert Palace)', - 'Small Key (Desert Palace)', 'Big Key (Tower of Hera)', 'Small Key (Tower of Hera)', - 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', - 'Big Key (Palace of Darkness)'] + ['Small Key (Palace of Darkness)'] * 6 + [ - 'Big Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Skull Woods)'] + [ - 'Small Key (Skull Woods)'] * 3 + ['Big Key (Swamp Palace)', - 'Small Key (Swamp Palace)', 'Big Key (Ice Palace)'] + [ - 'Small Key (Ice Palace)'] * 2 + ['Big Key (Misery Mire)', 'Big Key (Turtle Rock)', - 'Big Key (Ganons Tower)'] + [ - 'Small Key (Misery Mire)'] * 3 + ['Small Key (Turtle Rock)'] * 4 + [ - 'Small Key (Ganons Tower)'] * 4, - p): - world.collect(ret, item) + from worlds.alttp.Dungeons import get_dungeon_item_pool + for item in get_dungeon_item_pool(self): + subworld = self.worlds[item.player] + if item.name in subworld.dungeon_local_item_names: + subworld.collect(ret, item) ret.sweep_for_events() - setattr(self, key, ret) return ret def get_items(self) -> list: diff --git a/Options.py b/Options.py index f31c7e6f..fbb19d88 100644 --- a/Options.py +++ b/Options.py @@ -57,11 +57,12 @@ class Option(metaclass=AssembleOptions): """For display purposes.""" return self.get_option_name(self.value) - def get_option_name(self, value: typing.Any) -> str: - if self.autodisplayname: - return self.name_lookup[self.value].replace("_", " ").title() + @classmethod + def get_option_name(cls, value: typing.Any) -> str: + if cls.autodisplayname: + return cls.name_lookup[value].replace("_", " ").title() else: - return self.name_lookup[self.value] + return cls.name_lookup[value] def __int__(self) -> int: return self.value @@ -114,7 +115,8 @@ class Toggle(Option): def __int__(self): return int(self.value) - def get_option_name(self, value): + @classmethod + def get_option_name(cls, value): return ["No", "Yes"][int(value)] class DefaultOnToggle(Toggle): diff --git a/WebHostLib/__init__.py b/WebHostLib/__init__.py index 529bfbe1..38e8afd4 100644 --- a/WebHostLib/__init__.py +++ b/WebHostLib/__init__.py @@ -8,6 +8,7 @@ from pony.flask import Pony from flask import Flask, request, redirect, url_for, render_template, Response, session, abort, send_from_directory from flask_caching import Cache from flask_compress import Compress +from worlds.AutoWorld import AutoWorldRegister from .models import * @@ -81,54 +82,6 @@ def page_not_found(err): return render_template('404.html'), 404 -games_list = { - "A Link to the Past": ("The Legend of Zelda: A Link to the Past", - """ - The Legend of Zelda: A Link to the Past is an action/adventure game. Take on the role of - Link, a boy who is destined to save the land of Hyrule. Delve through three palaces and nine - dungeons on your quest to rescue the descendents of the seven wise men and defeat the evil - Ganon!"""), - "Factorio": ("Factorio", - """ - Factorio is a game about automation. You play as an engineer who has crash landed on the planet - Nauvis, an inhospitable world filled with dangerous creatures called biters. Build a factory, - research new technologies, and become more efficient in your quest to build a rocket and return home. - """), - "Minecraft": ("Minecraft", - """ - Minecraft is a game about creativity. In a world made entirely of cubes, you explore, discover, mine, - craft, and try not to explode. Delve deep into the earth and discover abandoned mines, ancient - structures, and materials to create a portal to another world. Defeat the Ender Dragon, and claim - victory!"""), - "Subnautica": ("Subnautica", - """ - Subnautica is an undersea exploration game. Stranded on an alien world, you become infected by - an unknown bacteria. The planet's automatic quarantine will shoot you down if you try to leave. - You must find a cure for yourself, build an escape rocket, and leave the planet. - """), - "Ocarina of Time": ("The Legend of Zelda: Ocarina of Time", - """ - The Legend of Zelda: Ocarina of Time was the first three dimensional Zelda game. Journey as - Link as he quests to fulfil his destiny. Journey across Hyrule and defeat the evil masters of - corrupted temples or seek out the pieces of the Triforce. Defeat the evil Ganondorf to become - the Hero of Time and save Hyrule! - """), - "Super Metroid": ("Super Metroid", - """ - Samus is back in her first 16 bit adventure! Space pirates have attacked Ceres station and stolen - the last living Metroid. Go to planet Zebes and search out the abilities you will need to power - up your suit and defeat the villainous leader of the space pirates, Mother Brain. - """), - "Risk of Rain 2": ("Risk of Rain 2", - """ - Escape a chaotic alien planet by fighting through hordes of frenzied monsters – with your friends - , or on your own. Combine loot in surprising ways and master each character until you become the - havoc you feared upon your first crash landing. - """) - # "Ori and the Blind Forest": ("Ori and the Blind Forest", "Coming Soon™"), - # "Hollow Knight": ("Hollow Knight", "Coming Soon™"), -} - # Player settings pages @app.route('/games//player-settings') @@ -151,7 +104,11 @@ def game_page(game): # List of supported games @app.route('/games') def games(): - return render_template("games/games.html", games_list=games_list) + worlds = {} + for game, world in AutoWorldRegister.world_types.items(): + if not world.hidden: + worlds[game] = world.__doc__ if world.__doc__ else "No description provided." + return render_template("games/games.html", worlds=worlds) @app.route('/tutorial///') diff --git a/WebHostLib/downloads.py b/WebHostLib/downloads.py index 2d944117..77dc179e 100644 --- a/WebHostLib/downloads.py +++ b/WebHostLib/downloads.py @@ -1,8 +1,8 @@ -from flask import send_file, Response +from flask import send_file, Response, render_template from pony.orm import select from Patch import update_patch_data -from WebHostLib import app, Slot, Room, Seed +from WebHostLib import app, Slot, Room, Seed, cache import zipfile @app.route("/dl_patch//") @@ -68,4 +68,14 @@ def download_slot_file(room_id, player_id: int): fname = name.rsplit("/", 1)[0]+".zip" else: return "Game download not supported." - return send_file(io.BytesIO(slot_data.data), as_attachment=True, attachment_filename=fname) \ No newline at end of file + return send_file(io.BytesIO(slot_data.data), as_attachment=True, attachment_filename=fname) + +@app.route("/templates") +@cache.cached() +def list_yaml_templates(): + files = [] + from worlds.AutoWorld import AutoWorldRegister + for world_name, world in AutoWorldRegister.world_types.items(): + if not world.hidden: + files.append(world_name) + return render_template("templates.html", files=files) \ No newline at end of file diff --git a/WebHostLib/options.py b/WebHostLib/options.py index 56d01737..6a052fa2 100644 --- a/WebHostLib/options.py +++ b/WebHostLib/options.py @@ -10,9 +10,18 @@ target_folder = os.path.join("WebHostLib", "static", "generated") def create(): + def dictify_range(option): + data = {option.range_start: 0, option.range_end: 0, "random": 0, "random-low": 0, "random-high": 0, + option.default: 50} + notes = { + option.range_start: "minimum value", + option.range_end: "maximum value" + } + return data, notes for game_name, world in AutoWorldRegister.world_types.items(): res = Template(open(os.path.join("WebHostLib", "templates", "options.yaml")).read()).render( - options=world.options, __version__=__version__, game=game_name, yaml_dump=yaml.dump + options=world.options, __version__=__version__, game=game_name, yaml_dump=yaml.dump, + dictify_range=dictify_range ) with open(os.path.join(target_folder, game_name + ".yaml"), "w") as f: @@ -39,7 +48,7 @@ def create(): for sub_option_name, sub_option_id in option.options.items(): this_option["options"].append({ - "name": sub_option_name, + "name": option.get_option_name(sub_option_id), "value": sub_option_name, }) diff --git a/WebHostLib/templates/games/games.html b/WebHostLib/templates/games/games.html index 9b5bec8d..3aa1eb49 100644 --- a/WebHostLib/templates/games/games.html +++ b/WebHostLib/templates/games/games.html @@ -9,9 +9,9 @@ {% include 'header/grassHeader.html' %}

Currently Supported Games

- {% for game, (display_name, description) in games_list.items() %} -

{{ display_name}}

-

{{ description}}

+ {% for game, description in worlds.items() %} +

{{ game }}

+

{{ description }}

{% endfor %}
{% endblock %} diff --git a/WebHostLib/templates/options.yaml b/WebHostLib/templates/options.yaml index 105cd895..6fc06c46 100644 --- a/WebHostLib/templates/options.yaml +++ b/WebHostLib/templates/options.yaml @@ -18,7 +18,8 @@ # http://www.yamllint.com/ description: Default {{ game }} Template # Used to describe your yaml. Useful if you have multiple files -name: YourName{number} # Your name in-game. Spaces will be replaced with underscores and there is a 16 character limit +# Your name in-game. Spaces will be replaced with underscores and there is a 16 character limit +name: YourName{number} #{player} will be replaced with the player's slot number. #{PLAYER} will be replaced with the player's slot number if that slot number is greater than 1. #{number} will be replaced with the counter value of the name. @@ -51,16 +52,18 @@ progression_balancing: # - "Progressive Weapons" # exclude_locations: # Force certain locations to never contain progression items, and always be filled with junk. # - "Master Sword Pedestal" +{%- macro range_option(option) %} + # you can add additional values between minimum and maximum + {%- set data, notes = dictify_range(option) %} + {%- for entry, default in data.items() %} + {{ entry }}: {{ default }}{% if notes[entry] %} # {{ notes[entry] }}{% endif %} + {%- endfor -%} +{% endmacro %} {{ game }}: {%- for option_key, option in options.items() %} {{ option_key }}:{% if option.__doc__ %} # {{ option.__doc__ | replace('\n', '\n#') | indent(4, first=False) }}{% endif %} {%- if option.range_start is defined %} - # you can add additional values between minimum and maximum - {{ option.range_start }}: 0 # minimum value - {{ option.range_end }}: 0 # maximum value - random: 50 - random-low: 0 - random-high: 0 + {{- range_option(option) -}} {%- elif option.options -%} {%- for suboption_option_id, sub_option_name in option.name_lookup.items() %} {{ sub_option_name }}: {% if suboption_option_id == option.default %}50{% else %}0{% endif %} @@ -68,4 +71,5 @@ progression_balancing: {%- else %} {{ yaml_dump(option.default) | indent(4, first=False) }} {%- endif -%} - {%- endfor -%} \ No newline at end of file + {%- endfor %} + {% if not options %}{}{% endif %} diff --git a/WebHostLib/templates/templates.html b/WebHostLib/templates/templates.html new file mode 100644 index 00000000..5c5fe5f1 --- /dev/null +++ b/WebHostLib/templates/templates.html @@ -0,0 +1,21 @@ +{% extends 'pageWrapper.html' %} + +{% block head %} + {% include 'header/grassHeader.html' %} + Option Templates (YAML) + + +{% endblock %} + +{% block body %} +
+

Option Templates (YAML)

+
    + {% for file in files %} +
  • {{ file }}
  • + {% endfor %} +
+
+{% endblock %} diff --git a/worlds/alttp/Dungeons.py b/worlds/alttp/Dungeons.py index 76d4953f..000af504 100644 --- a/worlds/alttp/Dungeons.py +++ b/worlds/alttp/Dungeons.py @@ -157,6 +157,8 @@ def fill_dungeons_restrictive(autoworld, world): in_dungeon_items.sort( key=lambda item: sort_order.get(item.type, 1) + (5 if (item.player, item.name) in dungeon_specific else 0)) + for item in in_dungeon_items: + all_state_base.remove(item) fill_restrictive(world, all_state_base, locations, in_dungeon_items, True, True) diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py index 2a4514bc..2d555930 100644 --- a/worlds/alttp/EntranceShuffle.py +++ b/worlds/alttp/EntranceShuffle.py @@ -1,5 +1,6 @@ # ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave. from collections import defaultdict +from worlds.alttp.OverworldGlitchRules import overworld_glitch_connections from worlds.alttp.UnderworldGlitchRules import underworld_glitch_connections def link_entrances(world, player): @@ -1066,9 +1067,11 @@ def link_entrances(world, player): raise NotImplementedError( f'{world.shuffle[player]} Shuffling not supported yet. Player {world.get_player_name(player)}') - # mandatory hybrid major glitches connections - if world.logic[player] in ['hybridglitches', 'nologic']: - underworld_glitch_connections(world, player) + if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: + overworld_glitch_connections(world, player) + # mandatory hybrid major glitches connections + if world.logic[player] in ['hybridglitches', 'nologic']: + underworld_glitch_connections(world, player) # check for swamp palace fix if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)': @@ -1771,9 +1774,11 @@ def link_inverted_entrances(world, player): else: raise NotImplementedError('Shuffling not supported yet') - # mandatory hybrid major glitches connections - if world.logic[player] in ['hybridglitches', 'nologic']: - underworld_glitch_connections(world, player) + if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']: + overworld_glitch_connections(world, player) + # mandatory hybrid major glitches connections + if world.logic[player] in ['hybridglitches', 'nologic']: + underworld_glitch_connections(world, player) # patch swamp drain if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)': diff --git a/worlds/alttp/OverworldGlitchRules.py b/worlds/alttp/OverworldGlitchRules.py index 593a5407..705db7e7 100644 --- a/worlds/alttp/OverworldGlitchRules.py +++ b/worlds/alttp/OverworldGlitchRules.py @@ -235,24 +235,41 @@ def no_logic_rules(world, player): create_no_logic_connections(player, world, get_mirror_offset_spots_lw(player)) +def overworld_glitch_connections(world, player): + + # Boots-accessible locations. + create_owg_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted')) + create_owg_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player)) + + # Glitched speed drops. + create_owg_connections(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted')) + + # Mirror clip spots. + if world.mode[player] != 'inverted': + create_owg_connections(player, world, get_mirror_clip_spots_dw()) + create_owg_connections(player, world, get_mirror_offset_spots_dw()) + else: + create_owg_connections(player, world, get_mirror_offset_spots_lw(player)) + + def overworld_glitches_rules(world, player): # Boots-accessible locations. - create_owg_connections(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted'), lambda state: state.can_boots_clip_lw(player)) - create_owg_connections(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player), lambda state: state.can_boots_clip_dw(player)) + set_owg_connection_rules(player, world, get_boots_clip_exits_lw(world.mode[player] == 'inverted'), lambda state: state.can_boots_clip_lw(player)) + set_owg_connection_rules(player, world, get_boots_clip_exits_dw(world.mode[player] == 'inverted', player), lambda state: state.can_boots_clip_dw(player)) # Glitched speed drops. - create_owg_connections(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted'), lambda state: state.can_get_glitched_speed_dw(player)) + set_owg_connection_rules(player, world, get_glitched_speed_drops_dw(world.mode[player] == 'inverted'), lambda state: state.can_get_glitched_speed_dw(player)) # Dark Death Mountain Ledge Clip Spot also accessible with mirror. if world.mode[player] != 'inverted': add_alternate_rule(world.get_entrance('Dark Death Mountain Ledge Clip Spot', player), lambda state: state.has('Magic Mirror', player)) # Mirror clip spots. if world.mode[player] != 'inverted': - create_owg_connections(player, world, get_mirror_clip_spots_dw(), lambda state: state.has('Magic Mirror', player)) - create_owg_connections(player, world, get_mirror_offset_spots_dw(), lambda state: state.has('Magic Mirror', player) and state.can_boots_clip_lw(player)) + set_owg_connection_rules(player, world, get_mirror_clip_spots_dw(), lambda state: state.has('Magic Mirror', player)) + set_owg_connection_rules(player, world, get_mirror_offset_spots_dw(), lambda state: state.has('Magic Mirror', player) and state.can_boots_clip_lw(player)) else: - create_owg_connections(player, world, get_mirror_offset_spots_lw(player), lambda state: state.has('Magic Mirror', player) and state.can_boots_clip_dw(player)) + set_owg_connection_rules(player, world, get_mirror_offset_spots_lw(player), lambda state: state.has('Magic Mirror', player) and state.can_boots_clip_dw(player)) # Regions that require the boots and some other stuff. if world.mode[player] != 'inverted': @@ -282,12 +299,16 @@ def create_no_logic_connections(player, world, connections): parent.exits.append(connection) connection.connect(target) -def create_owg_connections(player, world, connections, default_rule): +def create_owg_connections(player, world, connections): for entrance, parent_region, target_region, *rule_override in connections: parent = world.get_region(parent_region, player) target = world.get_region(target_region, player) connection = Entrance(player, entrance, parent) parent.exits.append(connection) connection.connect(target) + +def set_owg_connection_rules(player, world, connections, default_rule): + for entrance, _, _, *rule_override in connections: + connection = world.get_entrance(entrance, player) rule = rule_override[0] if len(rule_override) > 0 else default_rule connection.access_rule = rule diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py index c9837e78..4e986c0d 100644 --- a/worlds/alttp/Rules.py +++ b/worlds/alttp/Rules.py @@ -853,7 +853,9 @@ def set_trock_key_rules(world, player): for entrance in ['Turtle Rock Dark Room Staircase', 'Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock Pokey Room', 'Turtle Rock Big Key Door']: set_rule(world.get_entrance(entrance, player), lambda state: False) - all_state = world.get_all_state(True) + all_state = world.get_all_state() + all_state.reachable_regions[player] = set() # wipe reachable regions so that the locked doors actually work + all_state.stale[player] = True # Check if each of the four main regions of the dungoen can be reached. The previous code section prevents key-costing moves within the dungeon. can_reach_back = all_state.can_reach(world.get_region('Turtle Rock (Eye Bridge)', player)) if world.can_access_trock_eyebridge[player] is None else world.can_access_trock_eyebridge[player] diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py index 10006daf..b7bdb348 100644 --- a/worlds/alttp/__init__.py +++ b/worlds/alttp/__init__.py @@ -24,6 +24,12 @@ lttp_logger = logging.getLogger("A Link to the Past") class ALTTPWorld(World): + """ + The Legend of Zelda: A Link to the Past is an action/adventure game. Take on the role of + Link, a boy who is destined to save the land of Hyrule. Delve through three palaces and nine + dungeons on your quest to rescue the descendents of the seven wise men and defeat the evil + Ganon! + """ game: str = "A Link to the Past" options = alttp_options topology_present = True @@ -192,8 +198,8 @@ class ALTTPWorld(World): elif 'Bow' in item_name: if state.has('Silver Bow', item.player): return - elif state.has('Bow', item.player) and (self.world.difficulty_requirements[item.player].progressive_bow_limit >= 2 - or self.world.logic[item.player] == 'noglitches' + elif state.has('Bow', item.player) and (self.world.difficulty_requirements[item.player].progressive_bow_limit >= 2 + or self.world.logic[item.player] == 'noglitches' or self.world.swordless[item.player]): # modes where silver bow is always required for ganon return 'Silver Bow' elif self.world.difficulty_requirements[item.player].progressive_bow_limit >= 1: @@ -206,7 +212,7 @@ class ALTTPWorld(World): attempts = 5 world = self.world player = self.player - all_state = world.get_all_state(keys=True) + all_state = world.get_all_state() crystals = [self.create_item(name) for name in ['Red Pendant', 'Blue Pendant', 'Green Pendant', 'Crystal 1', 'Crystal 2', 'Crystal 3', 'Crystal 4', 'Crystal 7', 'Crystal 5', 'Crystal 6']] crystal_locations = [world.get_location('Turtle Rock - Prize', player), world.get_location('Eastern Palace - Prize', player), @@ -401,4 +407,4 @@ class ALttPLogic(LogicMixin): return True if self.world.smallkey_shuffle[player] == smallkey_shuffle.option_universal: return self.can_buy_unlimited('Small Key (Universal)', player) - return self.prog_items[item, player] >= count \ No newline at end of file + return self.prog_items[item, player] >= count diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py index 6c4bce45..6028ae61 100644 --- a/worlds/factorio/__init__.py +++ b/worlds/factorio/__init__.py @@ -24,6 +24,11 @@ all_items["Evolution Trap"] = factorio_base_id - 2 class Factorio(World): + """ + Factorio is a game about automation. You play as an engineer who has crash landed on the planet + Nauvis, an inhospitable world filled with dangerous creatures called biters. Build a factory, + research new technologies, and become more efficient in your quest to build a rocket and return home. + """ game: str = "Factorio" static_nodes = {"automation", "logistics", "rocket-silo"} custom_recipes = {} diff --git a/worlds/minecraft/__init__.py b/worlds/minecraft/__init__.py index 78ab60b9..fd972740 100644 --- a/worlds/minecraft/__init__.py +++ b/worlds/minecraft/__init__.py @@ -16,6 +16,12 @@ from ..AutoWorld import World client_version = 6 class MinecraftWorld(World): + """ + Minecraft is a game about creativity. In a world made entirely of cubes, you explore, discover, mine, + craft, and try not to explode. Delve deep into the earth and discover abandoned mines, ancient + structures, and materials to create a portal to another world. Defeat the Ender Dragon, and claim + victory! + """ game: str = "Minecraft" options = minecraft_options topology_present = True @@ -47,7 +53,7 @@ class MinecraftWorld(World): itempool = [] junk_pool = junk_weights.copy() # Add all required progression items - for (name, num) in required_items.items(): + for (name, num) in required_items.items(): itempool += [name] * num # Add structure compasses if desired if self.world.structure_compasses[self.player]: @@ -85,9 +91,9 @@ class MinecraftWorld(World): def MCRegion(region_name: str, exits=[]): ret = Region(region_name, None, region_name, self.player, self.world) ret.locations = [MinecraftAdvancement(self.player, loc_name, loc_data.id, ret) - for loc_name, loc_data in advancement_table.items() + for loc_name, loc_data in advancement_table.items() if loc_data.region == region_name] - for exit in exits: + for exit in exits: ret.exits.append(Entrance(self.player, exit, ret)) return ret @@ -100,7 +106,7 @@ class MinecraftWorld(World): with open(os.path.join(output_directory, filename), 'wb') as f: f.write(b64encode(bytes(json.dumps(data), 'utf-8'))) - def fill_slot_data(self): + def fill_slot_data(self): slot_data = self._get_mc_data() for option_name in minecraft_options: option = getattr(self.world, option_name)[self.player] @@ -115,7 +121,7 @@ class MinecraftWorld(World): item.never_exclude = True return item -def mc_update_output(raw_data, server, port): +def mc_update_output(raw_data, server, port): data = json.loads(b64decode(raw_data)) data['server'] = server data['port'] = port diff --git a/worlds/subnautica/__init__.py b/worlds/subnautica/__init__.py index 527cf8ee..50376a37 100644 --- a/worlds/subnautica/__init__.py +++ b/worlds/subnautica/__init__.py @@ -15,6 +15,11 @@ from ..AutoWorld import World class SubnauticaWorld(World): + """ + Subnautica is an undersea exploration game. Stranded on an alien world, you become infected by + an unknown bacteria. The planet's automatic quarantine will shoot you down if you try to leave. + You must find a cure for yourself, build an escape rocket, and leave the planet. + """ game: str = "Subnautica" item_name_to_id = items_lookup_name_to_id @@ -53,7 +58,7 @@ class SubnauticaWorld(World): pass - def fill_slot_data(self): + def fill_slot_data(self): slot_data = {} return slot_data