Merge branch 'main' into main
This commit is contained in:
commit
ac915d00fc
|
@ -213,9 +213,8 @@ class MultiWorld():
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
raise KeyError('No such dungeon %s for player %d' % (dungeonname, player)) from e
|
raise KeyError('No such dungeon %s for player %d' % (dungeonname, player)) from e
|
||||||
|
|
||||||
def get_all_state(self, keys=False) -> CollectionState:
|
def get_all_state(self) -> CollectionState:
|
||||||
key = f"_all_state_{keys}"
|
cached = getattr(self, "_all_state", None)
|
||||||
cached = getattr(self, key, None)
|
|
||||||
if cached:
|
if cached:
|
||||||
return cached.copy()
|
return cached.copy()
|
||||||
|
|
||||||
|
@ -223,27 +222,12 @@ class MultiWorld():
|
||||||
|
|
||||||
for item in self.itempool:
|
for item in self.itempool:
|
||||||
self.worlds[item.player].collect(ret, item)
|
self.worlds[item.player].collect(ret, item)
|
||||||
|
from worlds.alttp.Dungeons import get_dungeon_item_pool
|
||||||
if keys:
|
for item in get_dungeon_item_pool(self):
|
||||||
for p in self.get_game_players("A Link to the Past"):
|
subworld = self.worlds[item.player]
|
||||||
world = self.worlds[p]
|
if item.name in subworld.dungeon_local_item_names:
|
||||||
from worlds.alttp.Items import ItemFactory
|
subworld.collect(ret, item)
|
||||||
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)
|
|
||||||
ret.sweep_for_events()
|
ret.sweep_for_events()
|
||||||
setattr(self, key, ret)
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_items(self) -> list:
|
def get_items(self) -> list:
|
||||||
|
|
12
Options.py
12
Options.py
|
@ -57,11 +57,12 @@ class Option(metaclass=AssembleOptions):
|
||||||
"""For display purposes."""
|
"""For display purposes."""
|
||||||
return self.get_option_name(self.value)
|
return self.get_option_name(self.value)
|
||||||
|
|
||||||
def get_option_name(self, value: typing.Any) -> str:
|
@classmethod
|
||||||
if self.autodisplayname:
|
def get_option_name(cls, value: typing.Any) -> str:
|
||||||
return self.name_lookup[self.value].replace("_", " ").title()
|
if cls.autodisplayname:
|
||||||
|
return cls.name_lookup[value].replace("_", " ").title()
|
||||||
else:
|
else:
|
||||||
return self.name_lookup[self.value]
|
return cls.name_lookup[value]
|
||||||
|
|
||||||
def __int__(self) -> int:
|
def __int__(self) -> int:
|
||||||
return self.value
|
return self.value
|
||||||
|
@ -114,7 +115,8 @@ class Toggle(Option):
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return int(self.value)
|
return int(self.value)
|
||||||
|
|
||||||
def get_option_name(self, value):
|
@classmethod
|
||||||
|
def get_option_name(cls, value):
|
||||||
return ["No", "Yes"][int(value)]
|
return ["No", "Yes"][int(value)]
|
||||||
|
|
||||||
class DefaultOnToggle(Toggle):
|
class DefaultOnToggle(Toggle):
|
||||||
|
|
|
@ -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 import Flask, request, redirect, url_for, render_template, Response, session, abort, send_from_directory
|
||||||
from flask_caching import Cache
|
from flask_caching import Cache
|
||||||
from flask_compress import Compress
|
from flask_compress import Compress
|
||||||
|
from worlds.AutoWorld import AutoWorldRegister
|
||||||
|
|
||||||
from .models import *
|
from .models import *
|
||||||
|
|
||||||
|
@ -81,54 +82,6 @@ def page_not_found(err):
|
||||||
return render_template('404.html'), 404
|
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
|
# Player settings pages
|
||||||
@app.route('/games/<string:game>/player-settings')
|
@app.route('/games/<string:game>/player-settings')
|
||||||
|
@ -151,7 +104,11 @@ def game_page(game):
|
||||||
# List of supported games
|
# List of supported games
|
||||||
@app.route('/games')
|
@app.route('/games')
|
||||||
def 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/<string:game>/<string:file>/<string:lang>')
|
@app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from flask import send_file, Response
|
from flask import send_file, Response, render_template
|
||||||
from pony.orm import select
|
from pony.orm import select
|
||||||
|
|
||||||
from Patch import update_patch_data
|
from Patch import update_patch_data
|
||||||
from WebHostLib import app, Slot, Room, Seed
|
from WebHostLib import app, Slot, Room, Seed, cache
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
@app.route("/dl_patch/<suuid:room_id>/<int:patch_id>")
|
@app.route("/dl_patch/<suuid:room_id>/<int:patch_id>")
|
||||||
|
@ -68,4 +68,14 @@ def download_slot_file(room_id, player_id: int):
|
||||||
fname = name.rsplit("/", 1)[0]+".zip"
|
fname = name.rsplit("/", 1)[0]+".zip"
|
||||||
else:
|
else:
|
||||||
return "Game download not supported."
|
return "Game download not supported."
|
||||||
return send_file(io.BytesIO(slot_data.data), as_attachment=True, attachment_filename=fname)
|
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)
|
|
@ -10,9 +10,18 @@ target_folder = os.path.join("WebHostLib", "static", "generated")
|
||||||
|
|
||||||
|
|
||||||
def create():
|
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():
|
for game_name, world in AutoWorldRegister.world_types.items():
|
||||||
res = Template(open(os.path.join("WebHostLib", "templates", "options.yaml")).read()).render(
|
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:
|
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():
|
for sub_option_name, sub_option_id in option.options.items():
|
||||||
this_option["options"].append({
|
this_option["options"].append({
|
||||||
"name": sub_option_name,
|
"name": option.get_option_name(sub_option_id),
|
||||||
"value": sub_option_name,
|
"value": sub_option_name,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -9,9 +9,9 @@
|
||||||
{% include 'header/grassHeader.html' %}
|
{% include 'header/grassHeader.html' %}
|
||||||
<div id="games">
|
<div id="games">
|
||||||
<h1>Currently Supported Games</h1>
|
<h1>Currently Supported Games</h1>
|
||||||
{% for game, (display_name, description) in games_list.items() %}
|
{% for game, description in worlds.items() %}
|
||||||
<h3><a href="{{ url_for("game_page", game=game) }}">{{ display_name}}</a></h3>
|
<h3><a href="{{ url_for("game_page", game=game) }}">{{ game }}</a></h3>
|
||||||
<p>{{ description}}</p>
|
<p>{{ description }}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -18,7 +18,8 @@
|
||||||
# http://www.yamllint.com/
|
# http://www.yamllint.com/
|
||||||
|
|
||||||
description: Default {{ game }} Template # Used to describe your yaml. Useful if you have multiple files
|
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.
|
||||||
#{PLAYER} will be replaced with the player's slot number if that slot number is greater than 1.
|
#{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.
|
#{number} will be replaced with the counter value of the name.
|
||||||
|
@ -51,16 +52,18 @@ progression_balancing:
|
||||||
# - "Progressive Weapons"
|
# - "Progressive Weapons"
|
||||||
# exclude_locations: # Force certain locations to never contain progression items, and always be filled with junk.
|
# exclude_locations: # Force certain locations to never contain progression items, and always be filled with junk.
|
||||||
# - "Master Sword Pedestal"
|
# - "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 }}:
|
{{ game }}:
|
||||||
{%- for option_key, option in options.items() %}
|
{%- for option_key, option in options.items() %}
|
||||||
{{ option_key }}:{% if option.__doc__ %} # {{ option.__doc__ | replace('\n', '\n#') | indent(4, first=False) }}{% endif %}
|
{{ option_key }}:{% if option.__doc__ %} # {{ option.__doc__ | replace('\n', '\n#') | indent(4, first=False) }}{% endif %}
|
||||||
{%- if option.range_start is defined %}
|
{%- if option.range_start is defined %}
|
||||||
# you can add additional values between minimum and maximum
|
{{- range_option(option) -}}
|
||||||
{{ option.range_start }}: 0 # minimum value
|
|
||||||
{{ option.range_end }}: 0 # maximum value
|
|
||||||
random: 50
|
|
||||||
random-low: 0
|
|
||||||
random-high: 0
|
|
||||||
{%- elif option.options -%}
|
{%- elif option.options -%}
|
||||||
{%- for suboption_option_id, sub_option_name in option.name_lookup.items() %}
|
{%- 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 %}
|
{{ sub_option_name }}: {% if suboption_option_id == option.default %}50{% else %}0{% endif %}
|
||||||
|
@ -68,4 +71,5 @@ progression_balancing:
|
||||||
{%- else %}
|
{%- else %}
|
||||||
{{ yaml_dump(option.default) | indent(4, first=False) }}
|
{{ yaml_dump(option.default) | indent(4, first=False) }}
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
{%- endfor -%}
|
{%- endfor %}
|
||||||
|
{% if not options %}{}{% endif %}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
{% extends 'pageWrapper.html' %}
|
||||||
|
|
||||||
|
{% block head %}
|
||||||
|
{% include 'header/grassHeader.html' %}
|
||||||
|
<title>Option Templates (YAML)</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/markdown.css") }}" />
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.1/showdown.min.js"
|
||||||
|
integrity="sha512-L03kznCrNOfVxOUovR6ESfCz9Gfny7gihUX/huVbQB9zjODtYpxaVtIaAkpetoiyV2eqWbvxMH9fiSv5enX7bw=="
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div>
|
||||||
|
<h1>Option Templates (YAML)</h1>
|
||||||
|
<ul>
|
||||||
|
{% for file in files %}
|
||||||
|
<li><a href="{{ url_for('static', filename="generated/"+file+".yaml") }}">{{ file }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -157,6 +157,8 @@ def fill_dungeons_restrictive(autoworld, world):
|
||||||
in_dungeon_items.sort(
|
in_dungeon_items.sort(
|
||||||
key=lambda item: sort_order.get(item.type, 1) +
|
key=lambda item: sort_order.get(item.type, 1) +
|
||||||
(5 if (item.player, item.name) in dungeon_specific else 0))
|
(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)
|
fill_restrictive(world, all_state_base, locations, in_dungeon_items, True, True)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave.
|
# ToDo: With shuffle_ganon option, prevent gtower from linking to an exit only location through a 2 entrance cave.
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from worlds.alttp.OverworldGlitchRules import overworld_glitch_connections
|
||||||
from worlds.alttp.UnderworldGlitchRules import underworld_glitch_connections
|
from worlds.alttp.UnderworldGlitchRules import underworld_glitch_connections
|
||||||
|
|
||||||
def link_entrances(world, player):
|
def link_entrances(world, player):
|
||||||
|
@ -1066,9 +1067,11 @@ def link_entrances(world, player):
|
||||||
raise NotImplementedError(
|
raise NotImplementedError(
|
||||||
f'{world.shuffle[player]} Shuffling not supported yet. Player {world.get_player_name(player)}')
|
f'{world.shuffle[player]} Shuffling not supported yet. Player {world.get_player_name(player)}')
|
||||||
|
|
||||||
# mandatory hybrid major glitches connections
|
if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']:
|
||||||
if world.logic[player] in ['hybridglitches', 'nologic']:
|
overworld_glitch_connections(world, player)
|
||||||
underworld_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
|
# 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)':
|
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:
|
else:
|
||||||
raise NotImplementedError('Shuffling not supported yet')
|
raise NotImplementedError('Shuffling not supported yet')
|
||||||
|
|
||||||
# mandatory hybrid major glitches connections
|
if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']:
|
||||||
if world.logic[player] in ['hybridglitches', 'nologic']:
|
overworld_glitch_connections(world, player)
|
||||||
underworld_glitch_connections(world, player)
|
# mandatory hybrid major glitches connections
|
||||||
|
if world.logic[player] in ['hybridglitches', 'nologic']:
|
||||||
|
underworld_glitch_connections(world, player)
|
||||||
|
|
||||||
# patch swamp drain
|
# 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)':
|
if world.get_entrance('Dam', player).connected_region.name != 'Dam' or world.get_entrance('Swamp Palace', player).connected_region.name != 'Swamp Palace (Entrance)':
|
||||||
|
|
|
@ -235,24 +235,41 @@ def no_logic_rules(world, player):
|
||||||
create_no_logic_connections(player, world, get_mirror_offset_spots_lw(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):
|
def overworld_glitches_rules(world, player):
|
||||||
|
|
||||||
# Boots-accessible locations.
|
# 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))
|
set_owg_connection_rules(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_dw(world.mode[player] == 'inverted', player), lambda state: state.can_boots_clip_dw(player))
|
||||||
|
|
||||||
# Glitched speed drops.
|
# 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.
|
# Dark Death Mountain Ledge Clip Spot also accessible with mirror.
|
||||||
if world.mode[player] != 'inverted':
|
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))
|
add_alternate_rule(world.get_entrance('Dark Death Mountain Ledge Clip Spot', player), lambda state: state.has('Magic Mirror', player))
|
||||||
|
|
||||||
# Mirror clip spots.
|
# Mirror clip spots.
|
||||||
if world.mode[player] != 'inverted':
|
if world.mode[player] != 'inverted':
|
||||||
create_owg_connections(player, world, get_mirror_clip_spots_dw(), lambda state: state.has('Magic Mirror', player))
|
set_owg_connection_rules(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_offset_spots_dw(), lambda state: state.has('Magic Mirror', player) and state.can_boots_clip_lw(player))
|
||||||
else:
|
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.
|
# Regions that require the boots and some other stuff.
|
||||||
if world.mode[player] != 'inverted':
|
if world.mode[player] != 'inverted':
|
||||||
|
@ -282,12 +299,16 @@ def create_no_logic_connections(player, world, connections):
|
||||||
parent.exits.append(connection)
|
parent.exits.append(connection)
|
||||||
connection.connect(target)
|
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:
|
for entrance, parent_region, target_region, *rule_override in connections:
|
||||||
parent = world.get_region(parent_region, player)
|
parent = world.get_region(parent_region, player)
|
||||||
target = world.get_region(target_region, player)
|
target = world.get_region(target_region, player)
|
||||||
connection = Entrance(player, entrance, parent)
|
connection = Entrance(player, entrance, parent)
|
||||||
parent.exits.append(connection)
|
parent.exits.append(connection)
|
||||||
connection.connect(target)
|
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
|
rule = rule_override[0] if len(rule_override) > 0 else default_rule
|
||||||
connection.access_rule = rule
|
connection.access_rule = rule
|
||||||
|
|
|
@ -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']:
|
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)
|
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.
|
# 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]
|
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]
|
||||||
|
|
|
@ -24,6 +24,12 @@ lttp_logger = logging.getLogger("A Link to the Past")
|
||||||
|
|
||||||
|
|
||||||
class ALTTPWorld(World):
|
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"
|
game: str = "A Link to the Past"
|
||||||
options = alttp_options
|
options = alttp_options
|
||||||
topology_present = True
|
topology_present = True
|
||||||
|
@ -192,8 +198,8 @@ class ALTTPWorld(World):
|
||||||
elif 'Bow' in item_name:
|
elif 'Bow' in item_name:
|
||||||
if state.has('Silver Bow', item.player):
|
if state.has('Silver Bow', item.player):
|
||||||
return
|
return
|
||||||
elif state.has('Bow', item.player) and (self.world.difficulty_requirements[item.player].progressive_bow_limit >= 2
|
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.logic[item.player] == 'noglitches'
|
||||||
or self.world.swordless[item.player]): # modes where silver bow is always required for ganon
|
or self.world.swordless[item.player]): # modes where silver bow is always required for ganon
|
||||||
return 'Silver Bow'
|
return 'Silver Bow'
|
||||||
elif self.world.difficulty_requirements[item.player].progressive_bow_limit >= 1:
|
elif self.world.difficulty_requirements[item.player].progressive_bow_limit >= 1:
|
||||||
|
@ -206,7 +212,7 @@ class ALTTPWorld(World):
|
||||||
attempts = 5
|
attempts = 5
|
||||||
world = self.world
|
world = self.world
|
||||||
player = self.player
|
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']]
|
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),
|
crystal_locations = [world.get_location('Turtle Rock - Prize', player),
|
||||||
world.get_location('Eastern Palace - Prize', player),
|
world.get_location('Eastern Palace - Prize', player),
|
||||||
|
@ -401,4 +407,4 @@ class ALttPLogic(LogicMixin):
|
||||||
return True
|
return True
|
||||||
if self.world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
|
if self.world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
|
||||||
return self.can_buy_unlimited('Small Key (Universal)', player)
|
return self.can_buy_unlimited('Small Key (Universal)', player)
|
||||||
return self.prog_items[item, player] >= count
|
return self.prog_items[item, player] >= count
|
||||||
|
|
|
@ -24,6 +24,11 @@ all_items["Evolution Trap"] = factorio_base_id - 2
|
||||||
|
|
||||||
|
|
||||||
class Factorio(World):
|
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"
|
game: str = "Factorio"
|
||||||
static_nodes = {"automation", "logistics", "rocket-silo"}
|
static_nodes = {"automation", "logistics", "rocket-silo"}
|
||||||
custom_recipes = {}
|
custom_recipes = {}
|
||||||
|
|
|
@ -16,6 +16,12 @@ from ..AutoWorld import World
|
||||||
client_version = 6
|
client_version = 6
|
||||||
|
|
||||||
class MinecraftWorld(World):
|
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"
|
game: str = "Minecraft"
|
||||||
options = minecraft_options
|
options = minecraft_options
|
||||||
topology_present = True
|
topology_present = True
|
||||||
|
@ -47,7 +53,7 @@ class MinecraftWorld(World):
|
||||||
itempool = []
|
itempool = []
|
||||||
junk_pool = junk_weights.copy()
|
junk_pool = junk_weights.copy()
|
||||||
# Add all required progression items
|
# Add all required progression items
|
||||||
for (name, num) in required_items.items():
|
for (name, num) in required_items.items():
|
||||||
itempool += [name] * num
|
itempool += [name] * num
|
||||||
# Add structure compasses if desired
|
# Add structure compasses if desired
|
||||||
if self.world.structure_compasses[self.player]:
|
if self.world.structure_compasses[self.player]:
|
||||||
|
@ -85,9 +91,9 @@ class MinecraftWorld(World):
|
||||||
def MCRegion(region_name: str, exits=[]):
|
def MCRegion(region_name: str, exits=[]):
|
||||||
ret = Region(region_name, None, region_name, self.player, self.world)
|
ret = Region(region_name, None, region_name, self.player, self.world)
|
||||||
ret.locations = [MinecraftAdvancement(self.player, loc_name, loc_data.id, ret)
|
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]
|
if loc_data.region == region_name]
|
||||||
for exit in exits:
|
for exit in exits:
|
||||||
ret.exits.append(Entrance(self.player, exit, ret))
|
ret.exits.append(Entrance(self.player, exit, ret))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
@ -100,7 +106,7 @@ class MinecraftWorld(World):
|
||||||
with open(os.path.join(output_directory, filename), 'wb') as f:
|
with open(os.path.join(output_directory, filename), 'wb') as f:
|
||||||
f.write(b64encode(bytes(json.dumps(data), 'utf-8')))
|
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()
|
slot_data = self._get_mc_data()
|
||||||
for option_name in minecraft_options:
|
for option_name in minecraft_options:
|
||||||
option = getattr(self.world, option_name)[self.player]
|
option = getattr(self.world, option_name)[self.player]
|
||||||
|
@ -115,7 +121,7 @@ class MinecraftWorld(World):
|
||||||
item.never_exclude = True
|
item.never_exclude = True
|
||||||
return item
|
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 = json.loads(b64decode(raw_data))
|
||||||
data['server'] = server
|
data['server'] = server
|
||||||
data['port'] = port
|
data['port'] = port
|
||||||
|
|
|
@ -15,6 +15,11 @@ from ..AutoWorld import World
|
||||||
|
|
||||||
|
|
||||||
class SubnauticaWorld(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"
|
game: str = "Subnautica"
|
||||||
|
|
||||||
item_name_to_id = items_lookup_name_to_id
|
item_name_to_id = items_lookup_name_to_id
|
||||||
|
@ -53,7 +58,7 @@ class SubnauticaWorld(World):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def fill_slot_data(self):
|
def fill_slot_data(self):
|
||||||
slot_data = {}
|
slot_data = {}
|
||||||
return slot_data
|
return slot_data
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue