Compare commits

..

1 Commits

Author SHA1 Message Date
NewSoupVi
83d8bd584b Revert "DS3: Make your own region cache (#4040)"
This reverts commit 2751ccdaab.
2024-10-11 03:03:32 +02:00
82 changed files with 1386 additions and 1391 deletions

View File

@@ -355,8 +355,6 @@ class CommonContext:
self.item_names = self.NameLookupDict(self, "item") self.item_names = self.NameLookupDict(self, "item")
self.location_names = self.NameLookupDict(self, "location") self.location_names = self.NameLookupDict(self, "location")
self.versions = {}
self.checksums = {}
self.jsontotextparser = JSONtoTextParser(self) self.jsontotextparser = JSONtoTextParser(self)
self.rawjsontotextparser = RawJSONtoTextParser(self) self.rawjsontotextparser = RawJSONtoTextParser(self)
@@ -573,34 +571,26 @@ class CommonContext:
needed_updates.add(game) needed_updates.add(game)
continue continue
cached_version: int = self.versions.get(game, 0) local_version: int = network_data_package["games"].get(game, {}).get("version", 0)
cached_checksum: typing.Optional[str] = self.checksums.get(game) local_checksum: typing.Optional[str] = network_data_package["games"].get(game, {}).get("checksum")
# no action required if cached version is new enough # no action required if local version is new enough
if (not remote_checksum and (remote_version > cached_version or remote_version == 0)) \ if (not remote_checksum and (remote_version > local_version or remote_version == 0)) \
or remote_checksum != cached_checksum: or remote_checksum != local_checksum:
local_version: int = network_data_package["games"].get(game, {}).get("version", 0) cached_game = Utils.load_data_package_for_checksum(game, remote_checksum)
local_checksum: typing.Optional[str] = network_data_package["games"].get(game, {}).get("checksum") cache_version: int = cached_game.get("version", 0)
if ((remote_checksum or remote_version <= local_version and remote_version != 0) cache_checksum: typing.Optional[str] = cached_game.get("checksum")
and remote_checksum == local_checksum): # download remote version if cache is not new enough
self.update_game(network_data_package["games"][game], game) if (not remote_checksum and (remote_version > cache_version or remote_version == 0)) \
or remote_checksum != cache_checksum:
needed_updates.add(game)
else: else:
cached_game = Utils.load_data_package_for_checksum(game, remote_checksum) self.update_game(cached_game, game)
cache_version: int = cached_game.get("version", 0)
cache_checksum: typing.Optional[str] = cached_game.get("checksum")
# download remote version if cache is not new enough
if (not remote_checksum and (remote_version > cache_version or remote_version == 0)) \
or remote_checksum != cache_checksum:
needed_updates.add(game)
else:
self.update_game(cached_game, game)
if needed_updates: if needed_updates:
await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates]) await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates])
def update_game(self, game_package: dict, game: str): def update_game(self, game_package: dict, game: str):
self.item_names.update_game(game, game_package["item_name_to_id"]) self.item_names.update_game(game, game_package["item_name_to_id"])
self.location_names.update_game(game, game_package["location_name_to_id"]) self.location_names.update_game(game, game_package["location_name_to_id"])
self.versions[game] = game_package.get("version", 0)
self.checksums[game] = game_package.get("checksum")
def update_data_package(self, data_package: dict): def update_data_package(self, data_package: dict):
for game, game_data in data_package["games"].items(): for game, game_data in data_package["games"].items():

View File

@@ -35,9 +35,7 @@ from Utils import is_frozen, user_path, local_path, init_logging, open_filename,
def open_host_yaml(): def open_host_yaml():
s = settings.get_settings() file = settings.get_settings().filename
file = s.filename
s.save()
assert file, "host.yaml missing" assert file, "host.yaml missing"
if is_linux: if is_linux:
exe = which('sensible-editor') or which('gedit') or \ exe = which('sensible-editor') or which('gedit') or \

View File

@@ -185,9 +185,11 @@ class Context:
slot_info: typing.Dict[int, NetworkSlot] slot_info: typing.Dict[int, NetworkSlot]
generator_version = Version(0, 0, 0) generator_version = Version(0, 0, 0)
checksums: typing.Dict[str, str] checksums: typing.Dict[str, str]
item_names: typing.Dict[str, typing.Dict[int, str]] item_names: typing.Dict[str, typing.Dict[int, str]] = (
collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')))
item_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]] item_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
location_names: typing.Dict[str, typing.Dict[int, str]] location_names: typing.Dict[str, typing.Dict[int, str]] = (
collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')))
location_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]] location_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
all_item_and_group_names: typing.Dict[str, typing.Set[str]] all_item_and_group_names: typing.Dict[str, typing.Set[str]]
all_location_and_group_names: typing.Dict[str, typing.Set[str]] all_location_and_group_names: typing.Dict[str, typing.Set[str]]
@@ -196,6 +198,7 @@ class Context:
""" each sphere is { player: { location_id, ... } } """ """ each sphere is { player: { location_id, ... } } """
logger: logging.Logger logger: logging.Logger
def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int, def __init__(self, host: str, port: int, server_password: str, password: str, location_check_points: int,
hint_cost: int, item_cheat: bool, release_mode: str = "disabled", collect_mode="disabled", hint_cost: int, item_cheat: bool, release_mode: str = "disabled", collect_mode="disabled",
remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2, remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2,
@@ -266,10 +269,6 @@ class Context:
self.location_name_groups = {} self.location_name_groups = {}
self.all_item_and_group_names = {} self.all_item_and_group_names = {}
self.all_location_and_group_names = {} self.all_location_and_group_names = {}
self.item_names = collections.defaultdict(
lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})'))
self.location_names = collections.defaultdict(
lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})'))
self.non_hintable_names = collections.defaultdict(frozenset) self.non_hintable_names = collections.defaultdict(frozenset)
self._load_game_data() self._load_game_data()
@@ -1154,10 +1153,7 @@ class CommandProcessor(metaclass=CommandMeta):
if not raw: if not raw:
return return
try: try:
try: command = shlex.split(raw, comments=False)
command = shlex.split(raw, comments=False)
except ValueError: # most likely: "ValueError: No closing quotation"
command = raw.split()
basecommand = command[0] basecommand = command[0]
if basecommand[0] == self.marker: if basecommand[0] == self.marker:
method = self.commands.get(basecommand[1:].lower(), None) method = self.commands.get(basecommand[1:].lower(), None)

View File

@@ -423,7 +423,7 @@ class RestrictedUnpickler(pickle.Unpickler):
if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint", "SlotType", "NetworkSlot"}: if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint", "SlotType", "NetworkSlot"}:
return getattr(self.net_utils_module, name) return getattr(self.net_utils_module, name)
# Options and Plando are unpickled by WebHost -> Generate # Options and Plando are unpickled by WebHost -> Generate
if module == "worlds.generic" and name == "PlandoItem": if module == "worlds.generic" and name in {"PlandoItem", "PlandoConnection"}:
if not self.generic_properties_module: if not self.generic_properties_module:
self.generic_properties_module = importlib.import_module("worlds.generic") self.generic_properties_module = importlib.import_module("worlds.generic")
return getattr(self.generic_properties_module, name) return getattr(self.generic_properties_module, name)
@@ -434,7 +434,7 @@ class RestrictedUnpickler(pickle.Unpickler):
else: else:
mod = importlib.import_module(module) mod = importlib.import_module(module)
obj = getattr(mod, name) obj = getattr(mod, name)
if issubclass(obj, (self.options_module.Option, self.options_module.PlandoConnection)): if issubclass(obj, self.options_module.Option):
return obj return obj
# Forbid everything else. # Forbid everything else.
raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden") raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden")

View File

@@ -5,7 +5,6 @@ from typing import Any, IO, Dict, Iterator, List, Tuple, Union
import jinja2.exceptions import jinja2.exceptions
from flask import request, redirect, url_for, render_template, Response, session, abort, send_from_directory from flask import request, redirect, url_for, render_template, Response, session, abort, send_from_directory
from pony.orm import count, commit, db_session from pony.orm import count, commit, db_session
from werkzeug.utils import secure_filename
from worlds.AutoWorld import AutoWorldRegister from worlds.AutoWorld import AutoWorldRegister
from . import app, cache from . import app, cache
@@ -70,28 +69,14 @@ def tutorial_landing():
@app.route('/faq/<string:lang>/') @app.route('/faq/<string:lang>/')
@cache.cached() @cache.cached()
def faq(lang: str): def faq(lang):
import markdown return render_template("faq.html", lang=lang)
with open(os.path.join(app.static_folder, "assets", "faq", secure_filename(lang)+".md")) as f:
document = f.read()
return render_template(
"markdown_document.html",
title="Frequently Asked Questions",
html_from_markdown=markdown.markdown(document, extensions=["mdx_breakless_lists"]),
)
@app.route('/glossary/<string:lang>/') @app.route('/glossary/<string:lang>/')
@cache.cached() @cache.cached()
def glossary(lang: str): def terms(lang):
import markdown return render_template("glossary.html", lang=lang)
with open(os.path.join(app.static_folder, "assets", "glossary", secure_filename(lang)+".md")) as f:
document = f.read()
return render_template(
"markdown_document.html",
title="Glossary",
html_from_markdown=markdown.markdown(document, extensions=["mdx_breakless_lists"]),
)
@app.route('/seed/<suuid:seed>') @app.route('/seed/<suuid:seed>')

View File

@@ -9,5 +9,3 @@ bokeh>=3.1.1; python_version <= '3.8'
bokeh>=3.4.3; python_version == '3.9' bokeh>=3.4.3; python_version == '3.9'
bokeh>=3.5.2; python_version >= '3.10' bokeh>=3.5.2; python_version >= '3.10'
markupsafe>=2.1.5 markupsafe>=2.1.5
Markdown>=3.7
mdx-breakless-lists>=1.0.1

View File

@@ -0,0 +1,51 @@
window.addEventListener('load', () => {
const tutorialWrapper = document.getElementById('faq-wrapper');
new Promise((resolve, reject) => {
const ajax = new XMLHttpRequest();
ajax.onreadystatechange = () => {
if (ajax.readyState !== 4) { return; }
if (ajax.status === 404) {
reject("Sorry, the tutorial is not available in that language yet.");
return;
}
if (ajax.status !== 200) {
reject("Something went wrong while loading the tutorial.");
return;
}
resolve(ajax.responseText);
};
ajax.open('GET', `${window.location.origin}/static/assets/faq/` +
`faq_${tutorialWrapper.getAttribute('data-lang')}.md`, true);
ajax.send();
}).then((results) => {
// Populate page with HTML generated from markdown
showdown.setOption('tables', true);
showdown.setOption('strikethrough', true);
showdown.setOption('literalMidWordUnderscores', true);
tutorialWrapper.innerHTML += (new showdown.Converter()).makeHtml(results);
adjustHeaderWidth();
// Reset the id of all header divs to something nicer
for (const header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
const headerId = header.innerText.replace(/\s+/g, '-').toLowerCase();
header.setAttribute('id', headerId);
header.addEventListener('click', () => {
window.location.hash = `#${headerId}`;
header.scrollIntoView();
});
}
// Manually scroll the user to the appropriate header if anchor navigation is used
document.fonts.ready.finally(() => {
if (window.location.hash) {
const scrollTarget = document.getElementById(window.location.hash.substring(1));
scrollTarget?.scrollIntoView();
}
});
}).catch((error) => {
console.error(error);
tutorialWrapper.innerHTML =
`<h2>This page is out of logic!</h2>
<h3>Click <a href="${window.location.origin}">here</a> to return to safety.</h3>`;
});
});

View File

@@ -0,0 +1,51 @@
window.addEventListener('load', () => {
const tutorialWrapper = document.getElementById('glossary-wrapper');
new Promise((resolve, reject) => {
const ajax = new XMLHttpRequest();
ajax.onreadystatechange = () => {
if (ajax.readyState !== 4) { return; }
if (ajax.status === 404) {
reject("Sorry, the glossary page is not available in that language yet.");
return;
}
if (ajax.status !== 200) {
reject("Something went wrong while loading the glossary.");
return;
}
resolve(ajax.responseText);
};
ajax.open('GET', `${window.location.origin}/static/assets/faq/` +
`glossary_${tutorialWrapper.getAttribute('data-lang')}.md`, true);
ajax.send();
}).then((results) => {
// Populate page with HTML generated from markdown
showdown.setOption('tables', true);
showdown.setOption('strikethrough', true);
showdown.setOption('literalMidWordUnderscores', true);
tutorialWrapper.innerHTML += (new showdown.Converter()).makeHtml(results);
adjustHeaderWidth();
// Reset the id of all header divs to something nicer
for (const header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
const headerId = header.innerText.replace(/\s+/g, '-').toLowerCase();
header.setAttribute('id', headerId);
header.addEventListener('click', () => {
window.location.hash = `#${headerId}`;
header.scrollIntoView();
});
}
// Manually scroll the user to the appropriate header if anchor navigation is used
document.fonts.ready.finally(() => {
if (window.location.hash) {
const scrollTarget = document.getElementById(window.location.hash.substring(1));
scrollTarget?.scrollIntoView();
}
});
}).catch((error) => {
console.error(error);
tutorialWrapper.innerHTML =
`<h2>This page is out of logic!</h2>
<h3>Click <a href="${window.location.origin}">here</a> to return to safety.</h3>`;
});
});

View File

@@ -288,11 +288,6 @@ const applyPresets = (presetName) => {
} }
}); });
namedRangeSelect.value = trueValue; namedRangeSelect.value = trueValue;
// It is also possible for a preset to use an unnamed value. If this happens, set the dropdown to "Custom"
if (namedRangeSelect.selectedIndex == -1)
{
namedRangeSelect.value = "custom";
}
} }
// Handle options whose presets are "random" // Handle options whose presets are "random"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 229 KiB

View File

@@ -1,66 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 240 38" style="enable-background:new 0 0 240 38;" xml:space="preserve">
<style type="text/css">
.st0{fill:#316B84;}
</style>
<g>
<g>
<path class="st0" d="M59.72,27.96L53.03,4.21L42.25,4.04l1.42,4.37l1.41-0.26l-7.9,24.22h8.44l-0.56-2.27l-0.81-3.27l8.9-5.7
l1.78,11.24h7.97v-4.73L59.72,27.96z M45.62,20.21l3.13-10.84h1.5l2.02,7.44L45.62,20.21z"/>
<path class="st0" d="M78.67,27.96V20.4l-4.11-2.5l3.29-3.78l-0.47-7.46l-2.82-2.45H56.65v5.27l3.81-1.11l2.31,13.36l-2.79,0.73
L61,26.34l5.06-0.52l0.36-6.15l4.32,0.13l3.16,3.62v8.94l12.89,1.49v-5.34L78.67,27.96z M73.27,13.33l-2.18,1.45h-4.64l-0.42-6.57
h5.68l1.55,1.37V13.33z"/>
<polygon class="st0" points="84.65,4.21 93.01,4.21 95.75,6.46 96.26,10.9 92.23,12.43 91.77,9.74 88.97,8.28 85.86,9.82
83.88,15.02 85.51,20.94 88.49,22.38 91.99,20.59 91.99,18.59 96.26,16.96 95.85,22.85 91.81,26.87 84.17,26.55 80.79,23.58
78.87,14.87 80.79,6.94 "/>
<polygon class="st0" points="97.62,4.21 103.33,4.21 102.96,21.08 108.7,20.14 108.34,6.42 113.85,3.28 113.9,19.9 115.75,19.71
115.27,25.86 113.88,25.86 114.27,32.36 108.7,32.36 108.7,26.39 102.96,26.39 102.96,32.36 91.77,33.85 92.2,28.85 97.88,27.96
"/>
<polygon class="st0" points="147.43,28.86 147.43,32.36 162.85,32.36 162.48,25.36 159.5,26 158.89,27.68 154.1,27.24
154.1,21.51 160.81,20.85 160.81,16.48 153.86,16.54 153.86,9.18 158.62,8.43 159.22,9.77 161.85,10.06 162.59,4 147.43,4
147.43,6.54 148.68,7.46 148.68,28.4 "/>
<polygon class="st0" points="163.89,9.24 163.89,4 172.31,4 170.35,26.87 179.55,24.74 179.55,32.36 164.51,32.34 164.65,28.71
165.73,27.84 165.73,9.59 "/>
<path class="st0" d="M193.69,32.36l-0.63-2.51l-2.84-1.89l-4.29-20.14L185.9,4h-11.27l-0.03,3.2l1.87-0.34l-2.79,14.07l-1.37,0.57
v2.85l6.29-1.33l0.4-2.7l4.65-0.89l1.69,12.93H193.69z M179.39,15.11l1.65-6.52l0.89,0.25l0.92,5.45L179.39,15.11z"/>
<polygon class="st0" points="208.47,21.68 210.62,21.12 210.04,18.15 200.51,17.46 198.87,21.13 203.56,21.9 203.32,23.91
200.58,25.19 196.44,23.77 194.48,17.19 196.2,10.02 200.08,8.52 203.31,9.62 202.85,11.75 207.79,13.6 208.83,9.69 204.71,4.21
195.57,4.21 191.24,7.36 189.29,16.87 192.06,27.54 199.03,30.53 203.2,29.3 203.09,32.36 209.01,32.36 209.4,29.95 207.38,28.99
"/>
<path class="st0" d="M230.45,6.26L226.39,4l-8.59-0.01l-4.07,2.86l-2.58,8.9l1.52,11.82l5.61,4.73l7.65,0.01l5.72-4.59l2.47-12.46
L230.45,6.26z M228.23,21.75l-3.95,5.45l-2.16,0.43l-4.6-3.46L216,15.72l2.4-7.02l5.14-0.48l2.97,1.79l1.74,5.83L228.23,21.75z"/>
<path class="st0" d="M116.13,27.48l-0.24,4.88l12.26,0.09l-0.83-5.01l-2.86-0.48l0.14-17.62l2.45-0.42l-0.14-4.85l-10.92,0.36
l0.1,4.6l3.2,0.63l-0.42,17.67L116.13,27.48z"/>
<path class="st0" d="M141.34,4.21l-12.88-0.39v4.26l1.95,0.62v25.15l-1.8,1.41l-0.02,2.63h8.23L136,27.96h6.09l4.57-4.46V7.27
L141.34,4.21z M141.38,20.51l-2.54,1.89l-3.23,0.16L135.4,9.32h3.88l2.1,1.68V20.51z"/>
</g>
<g>
<path class="st0" d="M14.14,11.28c0,0.35-0.02,0.71-0.07,1.05c0.38,0.07,0.76,0.11,1.16,0.11s0.79-0.04,1.16-0.11
c-0.05-0.34-0.07-0.7-0.07-1.05c0-3.23,1.94-6.02,4.72-7.25C20.17,1.68,17.9,0,15.24,0S10.3,1.68,9.42,4.03
C12.2,5.26,14.14,8.04,14.14,11.28z"/>
<path class="st0" d="M18.04,11.28c0,0.16,0.01,0.32,0.02,0.48c0.02,0.3,0.06,0.6,0.13,0.88c0.06,0.28,0.15,0.56,0.25,0.83
c0.11,0.3,0.24,0.58,0.39,0.85c1.42-1.33,3.33-2.15,5.42-2.15s4.01,0.82,5.42,2.15c0.51-0.9,0.79-1.94,0.79-3.04
c0-3.42-2.79-6.22-6.22-6.22c-0.4,0-0.79,0.04-1.16,0.11c-0.28,0.06-0.56,0.13-0.83,0.22c-0.28,0.09-0.56,0.21-0.83,0.35
C19.42,6.77,18.04,8.87,18.04,11.28z"/>
<path class="st0" d="M6.22,12.16c2.1,0,4.01,0.82,5.42,2.15c0.15-0.27,0.28-0.55,0.39-0.85c0.1-0.27,0.19-0.54,0.25-0.83
c0.06-0.28,0.11-0.58,0.13-0.88c0.02-0.15,0.02-0.32,0.02-0.48c0-2.41-1.38-4.51-3.39-5.54C8.77,5.6,8.5,5.49,8.21,5.39
c-0.27-0.1-0.55-0.17-0.83-0.22C7,5.1,6.61,5.06,6.22,5.06C2.79,5.06,0,7.85,0,11.28c0,1.1,0.28,2.14,0.79,3.04
C2.21,12.98,4.12,12.16,6.22,12.16z"/>
<path class="st0" d="M29.21,16.33c-0.18-0.23-0.36-0.44-0.57-0.65c-1.12-1.12-2.67-1.81-4.38-1.81c-1.71,0-3.25,0.69-4.38,1.81
c-0.2,0.2-0.39,0.42-0.56,0.64c-0.18,0.23-0.34,0.47-0.47,0.72c-0.2,0.34-0.36,0.71-0.48,1.09c2.83,1.21,4.81,4.02,4.81,7.28
c0,0.26-0.01,0.52-0.04,0.78c0.37,0.07,0.75,0.1,1.13,0.1c3.43,0,6.22-2.79,6.22-6.22c0-1.11-0.29-2.14-0.8-3.04
C29.54,16.8,29.38,16.56,29.21,16.33z"/>
<path class="st0" d="M12.12,18.14c-0.13-0.38-0.28-0.75-0.48-1.09c-0.14-0.26-0.3-0.5-0.47-0.72c-0.17-0.23-0.36-0.44-0.56-0.64
c-1.12-1.12-2.67-1.81-4.38-1.81s-3.26,0.69-4.38,1.81c-0.21,0.2-0.39,0.42-0.56,0.64c-0.18,0.23-0.34,0.47-0.47,0.72
C0.29,17.94,0,18.98,0,20.08c0,3.43,2.79,6.22,6.22,6.22c0.39,0,0.76-0.03,1.13-0.1c-0.03-0.26-0.04-0.52-0.04-0.78
C7.31,22.15,9.29,19.34,12.12,18.14z"/>
<path class="st0" d="M18.04,19.87c-0.27-0.14-0.55-0.26-0.84-0.35c-0.27-0.09-0.55-0.17-0.84-0.22c-0.37-0.07-0.75-0.1-1.13-0.1
s-0.76,0.03-1.13,0.1c-0.28,0.05-0.57,0.13-0.84,0.22c-0.29,0.1-0.57,0.22-0.84,0.35C10.4,20.9,9.02,23,9.02,25.42
c0,0.07,0,0.14,0.01,0.21c0.01,0.31,0.04,0.61,0.1,0.9c0.05,0.28,0.12,0.57,0.21,0.84c0.82,2.48,3.16,4.27,5.9,4.27
s5.08-1.79,5.9-4.27c0.09-0.27,0.17-0.55,0.21-0.84c0.06-0.3,0.09-0.6,0.1-0.91c0.01-0.07,0.01-0.14,0.01-0.21
C21.45,23,20.07,20.9,18.04,19.87z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@@ -1 +1,66 @@
<?xml version="1.0" encoding="utf-8"?><svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 240 38" style="enable-background:new 0 0 240 38" xml:space="preserve"><style>.st0{fill:#316b84}</style><path class="st0" d="M59.72 27.96 53.03 4.21l-10.78-.17 1.42 4.37 1.41-.26-7.9 24.22h8.44l-.56-2.27-.81-3.27 8.9-5.7 1.78 11.24h7.97v-4.73l-3.18.32zm-14.1-7.75 3.13-10.84h1.5l2.02 7.44-6.65 3.4z"/><path class="st0" d="M78.67 27.96V20.4l-4.11-2.5 3.29-3.78-.47-7.46-2.82-2.45H56.65v5.27l3.81-1.11 2.31 13.36-2.79.73L61 26.34l5.06-.52.36-6.15 4.32.13 3.16 3.62v8.94l12.89 1.49v-5.34l-8.12-.55zm-5.4-14.63-2.18 1.45h-4.64l-.42-6.57h5.68l1.55 1.37v3.75z"/><path class="st0" d="M84.65 4.21h8.36l2.74 2.25.51 4.44-4.03 1.53-.46-2.69-2.8-1.46-3.11 1.54-1.98 5.2 1.63 5.92 2.98 1.44 3.5-1.79v-2l4.27-1.63-.41 5.89-4.04 4.02-7.64-.32-3.38-2.97-1.92-8.71 1.92-7.93z"/><path class="st0" d="M97.62 4.21h5.71l-.37 16.87 5.74-.94-.36-13.72 5.51-3.14.05 16.62 1.85-.19-.48 6.15h-1.39l.39 6.5h-5.57v-5.97h-5.74v5.97l-11.19 1.49.43-5 5.68-.89zm49.81 24.65v3.5h15.42l-.37-7-2.98.64-.61 1.68-4.79-.44v-5.73l6.71-.66v-4.37l-6.95.06V9.18l4.76-.75.6 1.34 2.63.29.74-6.06h-15.16v2.54l1.25.92V28.4zm16.46-19.62V4h8.42l-1.96 22.87 9.2-2.13v7.62l-15.04-.02.14-3.63 1.08-.87V9.59z"/><path class="st0" d="m193.69 32.36-.63-2.51-2.84-1.89-4.29-20.14L185.9 4h-11.27l-.03 3.2 1.87-.34-2.79 14.07-1.37.57v2.85l6.29-1.33.4-2.7 4.65-.89 1.69 12.93h8.35zm-14.3-17.25 1.65-6.52.89.25.92 5.45-3.46.82z"/><path class="st0" d="m208.47 21.68 2.15-.56-.58-2.97-9.53-.69-1.64 3.67 4.69.77-.24 2.01-2.74 1.28-4.14-1.42-1.96-6.58 1.72-7.17 3.88-1.5 3.23 1.1-.46 2.13 4.94 1.85 1.04-3.91-4.12-5.48h-9.14l-4.33 3.15-1.95 9.51 2.77 10.67 6.97 2.99 4.17-1.23-.11 3.06h5.92l.39-2.41-2.02-.96zm21.98-15.42L226.39 4l-8.59-.01-4.07 2.86-2.58 8.9 1.52 11.82 5.61 4.73 7.65.01 5.72-4.59 2.47-12.46-3.67-9zm-2.22 15.49-3.95 5.45-2.16.43-4.6-3.46-1.52-8.45 2.4-7.02 5.14-.48 2.97 1.79 1.74 5.83-.02 5.91zm-112.1 5.73-.24 4.88 12.26.09-.83-5.01-2.86-.48.14-17.62 2.45-.42-.14-4.85-10.92.36.1 4.6 3.2.63-.42 17.67-2.74.15zm25.21-23.27-12.88-.39v4.26l1.95.62v25.15l-1.8 1.41-.02 2.63h8.23l-.82-9.93h6.09l4.57-4.46V7.27l-5.32-3.06zm.04 16.3-2.54 1.89-3.23.16-.21-13.24h3.88l2.1 1.68v9.51zM14.14 11.28c0 .35-.02.71-.07 1.05.38.07.76.11 1.16.11s.79-.04 1.16-.11a7.933 7.933 0 0 1 4.65-8.3C20.17 1.68 17.9 0 15.24 0S10.3 1.68 9.42 4.03a7.922 7.922 0 0 1 4.72 7.25z"/><path class="st0" d="M18.04 11.28c0 .16.01.32.02.48.02.3.06.6.13.88.06.28.15.56.25.83.11.3.24.58.39.85 1.42-1.33 3.33-2.15 5.42-2.15s4.01.82 5.42 2.15c.51-.9.79-1.94.79-3.04 0-3.42-2.79-6.22-6.22-6.22-.4 0-.79.04-1.16.11-.28.06-.56.13-.83.22-.28.09-.56.21-.83.35a6.24 6.24 0 0 0-3.38 5.54zm-11.82.88c2.1 0 4.01.82 5.42 2.15.15-.27.28-.55.39-.85.1-.27.19-.54.25-.83.06-.28.11-.58.13-.88.02-.15.02-.32.02-.48a6.23 6.23 0 0 0-3.39-5.54c-.27-.13-.54-.24-.83-.34-.27-.1-.55-.17-.83-.22a6.42 6.42 0 0 0-1.16-.11 6.227 6.227 0 0 0-5.43 9.26 7.885 7.885 0 0 1 5.43-2.16z"/><path class="st0" d="M29.21 16.33c-.18-.23-.36-.44-.57-.65a6.174 6.174 0 0 0-4.38-1.81 6.192 6.192 0 0 0-4.94 2.45c-.18.23-.34.47-.47.72-.2.34-.36.71-.48 1.09a7.923 7.923 0 0 1 4.77 8.06c.37.07.75.1 1.13.1 3.43 0 6.22-2.79 6.22-6.22 0-1.11-.29-2.14-.8-3.04-.15-.23-.31-.47-.48-.7zm-17.09 1.81c-.13-.38-.28-.75-.48-1.09-.14-.26-.3-.5-.47-.72-.17-.23-.36-.44-.56-.64-1.12-1.12-2.67-1.81-4.38-1.81s-3.26.69-4.38 1.81c-.21.2-.39.42-.56.64-.18.23-.34.47-.47.72-.53.89-.82 1.93-.82 3.03 0 3.43 2.79 6.22 6.22 6.22.39 0 .76-.03 1.13-.1a7.902 7.902 0 0 1 4.77-8.06z"/><path class="st0" d="M18.04 19.87c-.27-.14-.55-.26-.84-.35-.27-.09-.55-.17-.84-.22-.37-.07-.75-.1-1.13-.1s-.76.03-1.13.1c-.28.05-.57.13-.84.22-.29.1-.57.22-.84.35a6.225 6.225 0 0 0-3.4 5.55c0 .07 0 .14.01.21.01.31.04.61.1.9.05.28.12.57.21.84.82 2.48 3.16 4.27 5.9 4.27s5.08-1.79 5.9-4.27c.09-.27.17-.55.21-.84.06-.3.09-.6.1-.91.01-.07.01-.14.01-.21a6.24 6.24 0 0 0-3.42-5.54z"/></svg> <?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 240 38" style="enable-background:new 0 0 240 38;" xml:space="preserve">
<style type="text/css">
.st0{fill:#316B84;}
</style>
<g>
<g>
<path class="st0" d="M59.72,27.96L53.03,4.21L42.25,4.04l1.42,4.37l1.41-0.26l-7.9,24.22h8.44l-0.56-2.27l-0.81-3.27l8.9-5.7
l1.78,11.24h7.97v-4.73L59.72,27.96z M45.62,20.21l3.13-10.84h1.5l2.02,7.44L45.62,20.21z"/>
<path class="st0" d="M78.67,27.96V20.4l-4.11-2.5l3.29-3.78l-0.47-7.46l-2.82-2.45H56.65v5.27l3.81-1.11l2.31,13.36l-2.79,0.73
L61,26.34l5.06-0.52l0.36-6.15l4.32,0.13l3.16,3.62v8.94l12.89,1.49v-5.34L78.67,27.96z M73.27,13.33l-2.18,1.45h-4.64l-0.42-6.57
h5.68l1.55,1.37V13.33z"/>
<polygon class="st0" points="84.65,4.21 93.01,4.21 95.75,6.46 96.26,10.9 92.23,12.43 91.77,9.74 88.97,8.28 85.86,9.82
83.88,15.02 85.51,20.94 88.49,22.38 91.99,20.59 91.99,18.59 96.26,16.96 95.85,22.85 91.81,26.87 84.17,26.55 80.79,23.58
78.87,14.87 80.79,6.94 "/>
<polygon class="st0" points="97.62,4.21 103.33,4.21 102.96,21.08 108.7,20.14 108.34,6.42 113.85,3.28 113.9,19.9 115.75,19.71
115.27,25.86 113.88,25.86 114.27,32.36 108.7,32.36 108.7,26.39 102.96,26.39 102.96,32.36 91.77,33.85 92.2,28.85 97.88,27.96
"/>
<polygon class="st0" points="147.43,28.86 147.43,32.36 162.85,32.36 162.48,25.36 159.5,26 158.89,27.68 154.1,27.24
154.1,21.51 160.81,20.85 160.81,16.48 153.86,16.54 153.86,9.18 158.62,8.43 159.22,9.77 161.85,10.06 162.59,4 147.43,4
147.43,6.54 148.68,7.46 148.68,28.4 "/>
<polygon class="st0" points="163.89,9.24 163.89,4 172.31,4 170.35,26.87 179.55,24.74 179.55,32.36 164.51,32.34 164.65,28.71
165.73,27.84 165.73,9.59 "/>
<path class="st0" d="M193.69,32.36l-0.63-2.51l-2.84-1.89l-4.29-20.14L185.9,4h-11.27l-0.03,3.2l1.87-0.34l-2.79,14.07l-1.37,0.57
v2.85l6.29-1.33l0.4-2.7l4.65-0.89l1.69,12.93H193.69z M179.39,15.11l1.65-6.52l0.89,0.25l0.92,5.45L179.39,15.11z"/>
<polygon class="st0" points="208.47,21.68 210.62,21.12 210.04,18.15 200.51,17.46 198.87,21.13 203.56,21.9 203.32,23.91
200.58,25.19 196.44,23.77 194.48,17.19 196.2,10.02 200.08,8.52 203.31,9.62 202.85,11.75 207.79,13.6 208.83,9.69 204.71,4.21
195.57,4.21 191.24,7.36 189.29,16.87 192.06,27.54 199.03,30.53 203.2,29.3 203.09,32.36 209.01,32.36 209.4,29.95 207.38,28.99
"/>
<path class="st0" d="M230.45,6.26L226.39,4l-8.59-0.01l-4.07,2.86l-2.58,8.9l1.52,11.82l5.61,4.73l7.65,0.01l5.72-4.59l2.47-12.46
L230.45,6.26z M228.23,21.75l-3.95,5.45l-2.16,0.43l-4.6-3.46L216,15.72l2.4-7.02l5.14-0.48l2.97,1.79l1.74,5.83L228.23,21.75z"/>
<path class="st0" d="M116.13,27.48l-0.24,4.88l12.26,0.09l-0.83-5.01l-2.86-0.48l0.14-17.62l2.45-0.42l-0.14-4.85l-10.92,0.36
l0.1,4.6l3.2,0.63l-0.42,17.67L116.13,27.48z"/>
<path class="st0" d="M141.34,4.21l-12.88-0.39v4.26l1.95,0.62v25.15l-1.8,1.41l-0.02,2.63h8.23L136,27.96h6.09l4.57-4.46V7.27
L141.34,4.21z M141.38,20.51l-2.54,1.89l-3.23,0.16L135.4,9.32h3.88l2.1,1.68V20.51z"/>
</g>
<g>
<path class="st0" d="M14.14,11.28c0,0.35-0.02,0.71-0.07,1.05c0.38,0.07,0.76,0.11,1.16,0.11s0.79-0.04,1.16-0.11
c-0.05-0.34-0.07-0.7-0.07-1.05c0-3.23,1.94-6.02,4.72-7.25C20.17,1.68,17.9,0,15.24,0S10.3,1.68,9.42,4.03
C12.2,5.26,14.14,8.04,14.14,11.28z"/>
<path class="st0" d="M18.04,11.28c0,0.16,0.01,0.32,0.02,0.48c0.02,0.3,0.06,0.6,0.13,0.88c0.06,0.28,0.15,0.56,0.25,0.83
c0.11,0.3,0.24,0.58,0.39,0.85c1.42-1.33,3.33-2.15,5.42-2.15s4.01,0.82,5.42,2.15c0.51-0.9,0.79-1.94,0.79-3.04
c0-3.42-2.79-6.22-6.22-6.22c-0.4,0-0.79,0.04-1.16,0.11c-0.28,0.06-0.56,0.13-0.83,0.22c-0.28,0.09-0.56,0.21-0.83,0.35
C19.42,6.77,18.04,8.87,18.04,11.28z"/>
<path class="st0" d="M6.22,12.16c2.1,0,4.01,0.82,5.42,2.15c0.15-0.27,0.28-0.55,0.39-0.85c0.1-0.27,0.19-0.54,0.25-0.83
c0.06-0.28,0.11-0.58,0.13-0.88c0.02-0.15,0.02-0.32,0.02-0.48c0-2.41-1.38-4.51-3.39-5.54C8.77,5.6,8.5,5.49,8.21,5.39
c-0.27-0.1-0.55-0.17-0.83-0.22C7,5.1,6.61,5.06,6.22,5.06C2.79,5.06,0,7.85,0,11.28c0,1.1,0.28,2.14,0.79,3.04
C2.21,12.98,4.12,12.16,6.22,12.16z"/>
<path class="st0" d="M29.21,16.33c-0.18-0.23-0.36-0.44-0.57-0.65c-1.12-1.12-2.67-1.81-4.38-1.81c-1.71,0-3.25,0.69-4.38,1.81
c-0.2,0.2-0.39,0.42-0.56,0.64c-0.18,0.23-0.34,0.47-0.47,0.72c-0.2,0.34-0.36,0.71-0.48,1.09c2.83,1.21,4.81,4.02,4.81,7.28
c0,0.26-0.01,0.52-0.04,0.78c0.37,0.07,0.75,0.1,1.13,0.1c3.43,0,6.22-2.79,6.22-6.22c0-1.11-0.29-2.14-0.8-3.04
C29.54,16.8,29.38,16.56,29.21,16.33z"/>
<path class="st0" d="M12.12,18.14c-0.13-0.38-0.28-0.75-0.48-1.09c-0.14-0.26-0.3-0.5-0.47-0.72c-0.17-0.23-0.36-0.44-0.56-0.64
c-1.12-1.12-2.67-1.81-4.38-1.81s-3.26,0.69-4.38,1.81c-0.21,0.2-0.39,0.42-0.56,0.64c-0.18,0.23-0.34,0.47-0.47,0.72
C0.29,17.94,0,18.98,0,20.08c0,3.43,2.79,6.22,6.22,6.22c0.39,0,0.76-0.03,1.13-0.1c-0.03-0.26-0.04-0.52-0.04-0.78
C7.31,22.15,9.29,19.34,12.12,18.14z"/>
<path class="st0" d="M18.04,19.87c-0.27-0.14-0.55-0.26-0.84-0.35c-0.27-0.09-0.55-0.17-0.84-0.22c-0.37-0.07-0.75-0.1-1.13-0.1
s-0.76,0.03-1.13,0.1c-0.28,0.05-0.57,0.13-0.84,0.22c-0.29,0.1-0.57,0.22-0.84,0.35C10.4,20.9,9.02,23,9.02,25.42
c0,0.07,0,0.14,0.01,0.21c0.01,0.31,0.04,0.61,0.1,0.9c0.05,0.28,0.12,0.57,0.21,0.84c0.82,2.48,3.16,4.27,5.9,4.27
s5.08-1.79,5.9-4.27c0.09-0.27,0.17-0.55,0.21-0.84c0.06-0.3,0.09-0.6,0.1-0.91c0.01-0.07,0.01-0.14,0.01-0.21
C21.45,23,20.07,20.9,18.04,19.87z"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 204 KiB

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 249 KiB

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 125 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 161 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 B

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,17 @@
{% extends 'pageWrapper.html' %}
{% block head %}
{% include 'header/grassHeader.html' %}
<title>Frequently Asked Questions</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>
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/faq.js") }}"></script>
{% endblock %}
{% block body %}
<div id="faq-wrapper" data-lang="{{ lang }}" class="markdown">
<!-- Content generated by JavaScript -->
</div>
{% endblock %}

View File

@@ -0,0 +1,17 @@
{% extends 'pageWrapper.html' %}
{% block head %}
{% include 'header/grassHeader.html' %}
<title>Glossary</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>
<script type="application/ecmascript" src="{{ url_for('static', filename="assets/glossary.js") }}"></script>
{% endblock %}
{% block body %}
<div id="glossary-wrapper" data-lang="{{ lang }}" class="markdown">
<!-- Content generated by JavaScript -->
</div>
{% endblock %}

View File

@@ -1,13 +0,0 @@
{% extends 'pageWrapper.html' %}
{% block head %}
{% include 'header/grassHeader.html' %}
<title>{{ title }}</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename="styles/markdown.css") }}" />
{% endblock %}
{% block body %}
<div class="markdown">
{{ html_from_markdown | safe}}
</div>
{% endblock %}

View File

@@ -268,7 +268,6 @@ Additional arguments added to the [Set](#Set) package that triggered this [SetRe
These packets are sent purely from client to server. They are not accepted by clients. These packets are sent purely from client to server. They are not accepted by clients.
* [Connect](#Connect) * [Connect](#Connect)
* [ConnectUpdate](#ConnectUpdate)
* [Sync](#Sync) * [Sync](#Sync)
* [LocationChecks](#LocationChecks) * [LocationChecks](#LocationChecks)
* [LocationScouts](#LocationScouts) * [LocationScouts](#LocationScouts)

View File

@@ -5,6 +5,7 @@ import platform
import shutil import shutil
import sys import sys
import sysconfig import sysconfig
import typing
import warnings import warnings
import zipfile import zipfile
import urllib.request import urllib.request
@@ -13,14 +14,14 @@ import json
import threading import threading
import subprocess import subprocess
from collections.abc import Iterable
from hashlib import sha3_512 from hashlib import sha3_512
from pathlib import Path from pathlib import Path
from typing import Dict, Iterable, List, Optional, Sequence, Set, Tuple, Union
# This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it # This is a bit jank. We need cx-Freeze to be able to run anything from this script, so install it
requirement = 'cx-Freeze==7.2.0'
try: try:
requirement = 'cx-Freeze==7.2.0'
import pkg_resources import pkg_resources
try: try:
pkg_resources.require(requirement) pkg_resources.require(requirement)
@@ -29,7 +30,7 @@ try:
install_cx_freeze = True install_cx_freeze = True
except ImportError: except ImportError:
install_cx_freeze = True install_cx_freeze = True
pkg_resources = None # type: ignore[assignment] pkg_resources = None # type: ignore [assignment]
if install_cx_freeze: if install_cx_freeze:
# check if pip is available # check if pip is available
@@ -60,7 +61,7 @@ from Cython.Build import cythonize
# On Python < 3.10 LogicMixin is not currently supported. # On Python < 3.10 LogicMixin is not currently supported.
non_apworlds: Set[str] = { non_apworlds: set = {
"A Link to the Past", "A Link to the Past",
"Adventure", "Adventure",
"ArchipIDLE", "ArchipIDLE",
@@ -83,7 +84,7 @@ non_apworlds: Set[str] = {
if sys.version_info < (3,10): if sys.version_info < (3,10):
non_apworlds.add("Hollow Knight") non_apworlds.add("Hollow Knight")
def download_SNI() -> None: def download_SNI():
print("Updating SNI") print("Updating SNI")
machine_to_go = { machine_to_go = {
"x86_64": "amd64", "x86_64": "amd64",
@@ -113,8 +114,8 @@ def download_SNI() -> None:
if source_url and source_url.endswith(".zip"): if source_url and source_url.endswith(".zip"):
with urllib.request.urlopen(source_url) as download: with urllib.request.urlopen(source_url) as download:
with zipfile.ZipFile(io.BytesIO(download.read()), "r") as zf: with zipfile.ZipFile(io.BytesIO(download.read()), "r") as zf:
for zf_member in zf.infolist(): for member in zf.infolist():
zf.extract(zf_member, path="SNI") zf.extract(member, path="SNI")
print(f"Downloaded SNI from {source_url}") print(f"Downloaded SNI from {source_url}")
elif source_url and (source_url.endswith(".tar.xz") or source_url.endswith(".tar.gz")): elif source_url and (source_url.endswith(".tar.xz") or source_url.endswith(".tar.gz")):
@@ -128,13 +129,11 @@ def download_SNI() -> None:
raise ValueError(f"Unexpected file '{member.name}' in {source_url}") raise ValueError(f"Unexpected file '{member.name}' in {source_url}")
elif member.isdir() and not sni_dir: elif member.isdir() and not sni_dir:
sni_dir = member.name sni_dir = member.name
elif member.isfile() and not sni_dir or sni_dir and not member.name.startswith(sni_dir): elif member.isfile() and not sni_dir or not member.name.startswith(sni_dir):
raise ValueError(f"Expected folder before '{member.name}' in {source_url}") raise ValueError(f"Expected folder before '{member.name}' in {source_url}")
elif member.isfile() and sni_dir: elif member.isfile() and sni_dir:
tf.extract(member) tf.extract(member)
# sadly SNI is in its own folder on non-windows, so we need to rename # sadly SNI is in its own folder on non-windows, so we need to rename
if not sni_dir:
raise ValueError("Did not find SNI in archive")
shutil.rmtree("SNI", True) shutil.rmtree("SNI", True)
os.rename(sni_dir, "SNI") os.rename(sni_dir, "SNI")
print(f"Downloaded SNI from {source_url}") print(f"Downloaded SNI from {source_url}")
@@ -146,7 +145,7 @@ def download_SNI() -> None:
print(f"No SNI found for system spec {platform_name} {machine_name}") print(f"No SNI found for system spec {platform_name} {machine_name}")
signtool: Optional[str] signtool: typing.Optional[str]
if os.path.exists("X:/pw.txt"): if os.path.exists("X:/pw.txt"):
print("Using signtool") print("Using signtool")
with open("X:/pw.txt", encoding="utf-8-sig") as f: with open("X:/pw.txt", encoding="utf-8-sig") as f:
@@ -198,13 +197,13 @@ extra_data = ["LICENSE", "data", "EnemizerCLI", "SNI"]
extra_libs = ["libssl.so", "libcrypto.so"] if is_linux else [] extra_libs = ["libssl.so", "libcrypto.so"] if is_linux else []
def remove_sprites_from_folder(folder: Path) -> None: def remove_sprites_from_folder(folder):
for file in os.listdir(folder): for file in os.listdir(folder):
if file != ".gitignore": if file != ".gitignore":
os.remove(folder / file) os.remove(folder / file)
def _threaded_hash(filepath: Union[str, Path]) -> str: def _threaded_hash(filepath):
hasher = sha3_512() hasher = sha3_512()
hasher.update(open(filepath, "rb").read()) hasher.update(open(filepath, "rb").read())
return base64.b85encode(hasher.digest()).decode() return base64.b85encode(hasher.digest()).decode()
@@ -218,11 +217,11 @@ class BuildCommand(setuptools.command.build.build):
yes: bool yes: bool
last_yes: bool = False # used by sub commands of build last_yes: bool = False # used by sub commands of build
def initialize_options(self) -> None: def initialize_options(self):
super().initialize_options() super().initialize_options()
type(self).last_yes = self.yes = False type(self).last_yes = self.yes = False
def finalize_options(self) -> None: def finalize_options(self):
super().finalize_options() super().finalize_options()
type(self).last_yes = self.yes type(self).last_yes = self.yes
@@ -234,27 +233,27 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
('extra-data=', None, 'Additional files to add.'), ('extra-data=', None, 'Additional files to add.'),
] ]
yes: bool yes: bool
extra_data: Iterable[str] extra_data: Iterable # [any] not available in 3.8
extra_libs: Iterable[str] # work around broken include_files extra_libs: Iterable # work around broken include_files
buildfolder: Path buildfolder: Path
libfolder: Path libfolder: Path
library: Path library: Path
buildtime: datetime.datetime buildtime: datetime.datetime
def initialize_options(self) -> None: def initialize_options(self):
super().initialize_options() super().initialize_options()
self.yes = BuildCommand.last_yes self.yes = BuildCommand.last_yes
self.extra_data = [] self.extra_data = []
self.extra_libs = [] self.extra_libs = []
def finalize_options(self) -> None: def finalize_options(self):
super().finalize_options() super().finalize_options()
self.buildfolder = self.build_exe self.buildfolder = self.build_exe
self.libfolder = Path(self.buildfolder, "lib") self.libfolder = Path(self.buildfolder, "lib")
self.library = Path(self.libfolder, "library.zip") self.library = Path(self.libfolder, "library.zip")
def installfile(self, path: Path, subpath: Optional[Union[str, Path]] = None, keep_content: bool = False) -> None: def installfile(self, path, subpath=None, keep_content: bool = False):
folder = self.buildfolder folder = self.buildfolder
if subpath: if subpath:
folder /= subpath folder /= subpath
@@ -269,7 +268,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
else: else:
print('Warning,', path, 'not found') print('Warning,', path, 'not found')
def create_manifest(self, create_hashes: bool = False) -> None: def create_manifest(self, create_hashes=False):
# Since the setup is now split into components and the manifest is not, # Since the setup is now split into components and the manifest is not,
# it makes most sense to just remove the hashes for now. Not aware of anyone using them. # it makes most sense to just remove the hashes for now. Not aware of anyone using them.
hashes = {} hashes = {}
@@ -291,7 +290,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
json.dump(manifest, open(manifestpath, "wt"), indent=4) json.dump(manifest, open(manifestpath, "wt"), indent=4)
print("Created Manifest") print("Created Manifest")
def run(self) -> None: def run(self):
# start downloading sni asap # start downloading sni asap
sni_thread = threading.Thread(target=download_SNI, name="SNI Downloader") sni_thread = threading.Thread(target=download_SNI, name="SNI Downloader")
sni_thread.start() sni_thread.start()
@@ -342,7 +341,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
# post build steps # post build steps
if is_windows: # kivy_deps is win32 only, linux picks them up automatically if is_windows: # kivy_deps is win32 only, linux picks them up automatically
from kivy_deps import sdl2, glew # type: ignore from kivy_deps import sdl2, glew
for folder in sdl2.dep_bins + glew.dep_bins: for folder in sdl2.dep_bins + glew.dep_bins:
shutil.copytree(folder, self.libfolder, dirs_exist_ok=True) shutil.copytree(folder, self.libfolder, dirs_exist_ok=True)
print(f"copying {folder} -> {self.libfolder}") print(f"copying {folder} -> {self.libfolder}")
@@ -363,7 +362,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
self.installfile(Path(data)) self.installfile(Path(data))
# kivi data files # kivi data files
import kivy # type: ignore[import-untyped] import kivy
shutil.copytree(os.path.join(os.path.dirname(kivy.__file__), "data"), shutil.copytree(os.path.join(os.path.dirname(kivy.__file__), "data"),
self.buildfolder / "data", self.buildfolder / "data",
dirs_exist_ok=True) dirs_exist_ok=True)
@@ -373,7 +372,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
from worlds.AutoWorld import AutoWorldRegister from worlds.AutoWorld import AutoWorldRegister
assert not non_apworlds - set(AutoWorldRegister.world_types), \ assert not non_apworlds - set(AutoWorldRegister.world_types), \
f"Unknown world {non_apworlds - set(AutoWorldRegister.world_types)} designated for .apworld" f"Unknown world {non_apworlds - set(AutoWorldRegister.world_types)} designated for .apworld"
folders_to_remove: List[str] = [] folders_to_remove: typing.List[str] = []
disabled_worlds_folder = "worlds_disabled" disabled_worlds_folder = "worlds_disabled"
for entry in os.listdir(disabled_worlds_folder): for entry in os.listdir(disabled_worlds_folder):
if os.path.isdir(os.path.join(disabled_worlds_folder, entry)): if os.path.isdir(os.path.join(disabled_worlds_folder, entry)):
@@ -394,7 +393,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
shutil.rmtree(world_directory) shutil.rmtree(world_directory)
shutil.copyfile("meta.yaml", self.buildfolder / "Players" / "Templates" / "meta.yaml") shutil.copyfile("meta.yaml", self.buildfolder / "Players" / "Templates" / "meta.yaml")
try: try:
from maseya import z3pr # type: ignore[import-untyped] from maseya import z3pr
except ImportError: except ImportError:
print("Maseya Palette Shuffle not found, skipping data files.") print("Maseya Palette Shuffle not found, skipping data files.")
else: else:
@@ -445,16 +444,16 @@ class AppImageCommand(setuptools.Command):
("app-exec=", None, "The application to run inside the image."), ("app-exec=", None, "The application to run inside the image."),
("yes", "y", 'Answer "yes" to all questions.'), ("yes", "y", 'Answer "yes" to all questions.'),
] ]
build_folder: Optional[Path] build_folder: typing.Optional[Path]
dist_file: Optional[Path] dist_file: typing.Optional[Path]
app_dir: Optional[Path] app_dir: typing.Optional[Path]
app_name: str app_name: str
app_exec: Optional[Path] app_exec: typing.Optional[Path]
app_icon: Optional[Path] # source file app_icon: typing.Optional[Path] # source file
app_id: str # lower case name, used for icon and .desktop app_id: str # lower case name, used for icon and .desktop
yes: bool yes: bool
def write_desktop(self) -> None: def write_desktop(self):
assert self.app_dir, "Invalid app_dir" assert self.app_dir, "Invalid app_dir"
desktop_filename = self.app_dir / f"{self.app_id}.desktop" desktop_filename = self.app_dir / f"{self.app_id}.desktop"
with open(desktop_filename, 'w', encoding="utf-8") as f: with open(desktop_filename, 'w', encoding="utf-8") as f:
@@ -469,7 +468,7 @@ class AppImageCommand(setuptools.Command):
))) )))
desktop_filename.chmod(0o755) desktop_filename.chmod(0o755)
def write_launcher(self, default_exe: Path) -> None: def write_launcher(self, default_exe: Path):
assert self.app_dir, "Invalid app_dir" assert self.app_dir, "Invalid app_dir"
launcher_filename = self.app_dir / "AppRun" launcher_filename = self.app_dir / "AppRun"
with open(launcher_filename, 'w', encoding="utf-8") as f: with open(launcher_filename, 'w', encoding="utf-8") as f:
@@ -492,7 +491,7 @@ $APPDIR/$exe "$@"
""") """)
launcher_filename.chmod(0o755) launcher_filename.chmod(0o755)
def install_icon(self, src: Path, name: Optional[str] = None, symlink: Optional[Path] = None) -> None: def install_icon(self, src: Path, name: typing.Optional[str] = None, symlink: typing.Optional[Path] = None):
assert self.app_dir, "Invalid app_dir" assert self.app_dir, "Invalid app_dir"
try: try:
from PIL import Image from PIL import Image
@@ -514,8 +513,7 @@ $APPDIR/$exe "$@"
if symlink: if symlink:
symlink.symlink_to(dest_file.relative_to(symlink.parent)) symlink.symlink_to(dest_file.relative_to(symlink.parent))
def initialize_options(self) -> None: def initialize_options(self):
assert self.distribution.metadata.name
self.build_folder = None self.build_folder = None
self.app_dir = None self.app_dir = None
self.app_name = self.distribution.metadata.name self.app_name = self.distribution.metadata.name
@@ -529,22 +527,17 @@ $APPDIR/$exe "$@"
)) ))
self.yes = False self.yes = False
def finalize_options(self) -> None: def finalize_options(self):
assert self.build_folder
if not self.app_dir: if not self.app_dir:
self.app_dir = self.build_folder.parent / "AppDir" self.app_dir = self.build_folder.parent / "AppDir"
self.app_id = self.app_name.lower() self.app_id = self.app_name.lower()
def run(self) -> None: def run(self):
assert self.build_folder and self.dist_file, "Command not properly set up"
assert (
self.app_icon and self.app_id and self.app_dir and self.app_exec and self.app_name
), "AppImageCommand not properly set up"
self.dist_file.parent.mkdir(parents=True, exist_ok=True) self.dist_file.parent.mkdir(parents=True, exist_ok=True)
if self.app_dir.is_dir(): if self.app_dir.is_dir():
shutil.rmtree(self.app_dir) shutil.rmtree(self.app_dir)
self.app_dir.mkdir(parents=True) self.app_dir.mkdir(parents=True)
opt_dir = self.app_dir / "opt" / self.app_name opt_dir = self.app_dir / "opt" / self.distribution.metadata.name
shutil.copytree(self.build_folder, opt_dir) shutil.copytree(self.build_folder, opt_dir)
root_icon = self.app_dir / f'{self.app_id}{self.app_icon.suffix}' root_icon = self.app_dir / f'{self.app_id}{self.app_icon.suffix}'
self.install_icon(self.app_icon, self.app_id, symlink=root_icon) self.install_icon(self.app_icon, self.app_id, symlink=root_icon)
@@ -555,7 +548,7 @@ $APPDIR/$exe "$@"
subprocess.call(f'ARCH={build_arch} ./appimagetool -n "{self.app_dir}" "{self.dist_file}"', shell=True) subprocess.call(f'ARCH={build_arch} ./appimagetool -n "{self.app_dir}" "{self.dist_file}"', shell=True)
def find_libs(*args: str) -> Sequence[Tuple[str, str]]: def find_libs(*args: str) -> typing.Sequence[typing.Tuple[str, str]]:
"""Try to find system libraries to be included.""" """Try to find system libraries to be included."""
if not args: if not args:
return [] return []
@@ -563,7 +556,7 @@ def find_libs(*args: str) -> Sequence[Tuple[str, str]]:
arch = build_arch.replace('_', '-') arch = build_arch.replace('_', '-')
libc = 'libc6' # we currently don't support musl libc = 'libc6' # we currently don't support musl
def parse(line: str) -> Tuple[Tuple[str, str, str], str]: def parse(line):
lib, path = line.strip().split(' => ') lib, path = line.strip().split(' => ')
lib, typ = lib.split(' ', 1) lib, typ = lib.split(' ', 1)
for test_arch in ('x86-64', 'i386', 'aarch64'): for test_arch in ('x86-64', 'i386', 'aarch64'):
@@ -584,29 +577,26 @@ def find_libs(*args: str) -> Sequence[Tuple[str, str]]:
ldconfig = shutil.which("ldconfig") ldconfig = shutil.which("ldconfig")
assert ldconfig, "Make sure ldconfig is in PATH" assert ldconfig, "Make sure ldconfig is in PATH"
data = subprocess.run([ldconfig, "-p"], capture_output=True, text=True).stdout.split("\n")[1:] data = subprocess.run([ldconfig, "-p"], capture_output=True, text=True).stdout.split("\n")[1:]
find_libs.cache = { # type: ignore[attr-defined] find_libs.cache = { # type: ignore [attr-defined]
k: v for k, v in (parse(line) for line in data if "=>" in line) k: v for k, v in (parse(line) for line in data if "=>" in line)
} }
def find_lib(lib: str, arch: str, libc: str) -> Optional[str]: def find_lib(lib, arch, libc):
cache: Dict[Tuple[str, str, str], str] = getattr(find_libs, "cache") for k, v in find_libs.cache.items():
for k, v in cache.items():
if k == (lib, arch, libc): if k == (lib, arch, libc):
return v return v
for k, v, in cache.items(): for k, v, in find_libs.cache.items():
if k[0].startswith(lib) and k[1] == arch and k[2] == libc: if k[0].startswith(lib) and k[1] == arch and k[2] == libc:
return v return v
return None return None
res: List[Tuple[str, str]] = [] res = []
for arg in args: for arg in args:
# try exact match, empty libc, empty arch, empty arch and libc # try exact match, empty libc, empty arch, empty arch and libc
file = find_lib(arg, arch, libc) file = find_lib(arg, arch, libc)
file = file or find_lib(arg, arch, '') file = file or find_lib(arg, arch, '')
file = file or find_lib(arg, '', libc) file = file or find_lib(arg, '', libc)
file = file or find_lib(arg, '', '') file = file or find_lib(arg, '', '')
if not file:
raise ValueError(f"Could not find lib {arg}")
# resolve symlinks # resolve symlinks
for n in range(0, 5): for n in range(0, 5):
res.append((file, os.path.join('lib', os.path.basename(file)))) res.append((file, os.path.join('lib', os.path.basename(file))))

View File

@@ -59,12 +59,3 @@ class TestOptions(unittest.TestCase):
item_links = {1: ItemLinks.from_any(item_link_group), 2: ItemLinks.from_any(item_link_group)} item_links = {1: ItemLinks.from_any(item_link_group), 2: ItemLinks.from_any(item_link_group)}
for link in item_links.values(): for link in item_links.values():
self.assertEqual(link.value[0], item_link_group[0]) self.assertEqual(link.value[0], item_link_group[0])
def test_pickle_dumps(self):
"""Test options can be pickled into database for WebHost generation"""
import pickle
for gamename, world_type in AutoWorldRegister.world_types.items():
if not world_type.hidden:
for option_key, option in world_type.options_dataclass.type_hints.items():
with self.subTest(game=gamename, option=option_key):
pickle.dumps(option(option.default))

View File

@@ -10,7 +10,7 @@ from dataclasses import make_dataclass
from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, Optional, Set, TextIO, Tuple, from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, Optional, Set, TextIO, Tuple,
TYPE_CHECKING, Type, Union) TYPE_CHECKING, Type, Union)
from Options import item_and_loc_options, ItemsAccessibility, OptionGroup, PerGameCommonOptions from Options import item_and_loc_options, OptionGroup, PerGameCommonOptions
from BaseClasses import CollectionState from BaseClasses import CollectionState
if TYPE_CHECKING: if TYPE_CHECKING:
@@ -480,7 +480,6 @@ class World(metaclass=AutoWorldRegister):
group = cls(multiworld, new_player_id) group = cls(multiworld, new_player_id)
group.options = cls.options_dataclass(**{option_key: option.from_any(option.default) group.options = cls.options_dataclass(**{option_key: option.from_any(option.default)
for option_key, option in cls.options_dataclass.type_hints.items()}) for option_key, option in cls.options_dataclass.type_hints.items()})
group.options.accessibility = ItemsAccessibility(ItemsAccessibility.option_items)
return group return group

View File

@@ -738,7 +738,9 @@ class AquariaRegions:
self.__connect_regions("Sun Temple left area", "Veil left of sun temple", self.__connect_regions("Sun Temple left area", "Veil left of sun temple",
self.sun_temple_l, self.veil_tr_l) self.sun_temple_l, self.veil_tr_l)
self.__connect_regions("Sun Temple left area", "Sun Temple before boss area", self.__connect_regions("Sun Temple left area", "Sun Temple before boss area",
self.sun_temple_l, self.sun_temple_boss_path) self.sun_temple_l, self.sun_temple_boss_path,
lambda state: _has_light(state, self.player) or
_has_sun_crystal(state, self.player))
self.__connect_regions("Sun Temple before boss area", "Sun Temple boss area", self.__connect_regions("Sun Temple before boss area", "Sun Temple boss area",
self.sun_temple_boss_path, self.sun_temple_boss, self.sun_temple_boss_path, self.sun_temple_boss,
lambda state: _has_energy_attack_item(state, self.player)) lambda state: _has_energy_attack_item(state, self.player))
@@ -773,11 +775,14 @@ class AquariaRegions:
self.abyss_l, self.king_jellyfish_cave, self.abyss_l, self.king_jellyfish_cave,
lambda state: (_has_energy_form(state, self.player) and lambda state: (_has_energy_form(state, self.player) and
_has_beast_form(state, self.player)) or _has_beast_form(state, self.player)) or
_has_dual_form(state, self.player)) _has_dual_form(state, self.player))
self.__connect_regions("Abyss left area", "Abyss right area", self.__connect_regions("Abyss left area", "Abyss right area",
self.abyss_l, self.abyss_r) self.abyss_l, self.abyss_r)
self.__connect_regions("Abyss right area", "Abyss right area, transturtle", self.__connect_one_way_regions("Abyss right area", "Abyss right area, transturtle",
self.abyss_r, self.abyss_r_transturtle) self.abyss_r, self.abyss_r_transturtle)
self.__connect_one_way_regions("Abyss right area, transturtle", "Abyss right area",
self.abyss_r_transturtle, self.abyss_r,
lambda state: _has_light(state, self.player))
self.__connect_regions("Abyss right area", "Inside the whale", self.__connect_regions("Abyss right area", "Inside the whale",
self.abyss_r, self.whale, self.abyss_r, self.whale,
lambda state: _has_spirit_form(state, self.player) and lambda state: _has_spirit_form(state, self.player) and
@@ -1087,10 +1092,12 @@ class AquariaRegions:
lambda state: _has_light(state, self.player)) lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Open Water bottom left area to Abyss left area", self.player), add_rule(self.multiworld.get_entrance("Open Water bottom left area to Abyss left area", self.player),
lambda state: _has_light(state, self.player)) lambda state: _has_light(state, self.player))
add_rule(self.multiworld.get_entrance("Sun Temple left area to Sun Temple right area", self.player),
lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player))
add_rule(self.multiworld.get_entrance("Sun Temple right area to Sun Temple left area", self.player),
lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player))
add_rule(self.multiworld.get_entrance("Veil left of sun temple to Sun Temple left area", self.player), add_rule(self.multiworld.get_entrance("Veil left of sun temple to Sun Temple left area", self.player),
lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player)) lambda state: _has_light(state, self.player) or _has_sun_crystal(state, self.player))
add_rule(self.multiworld.get_entrance("Abyss right area, transturtle to Abyss right area", self.player),
lambda state: _has_light(state, self.player))
def __adjusting_manual_rules(self) -> None: def __adjusting_manual_rules(self) -> None:
add_rule(self.multiworld.get_location("Mithalas Cathedral, Mithalan Dress", self.player), add_rule(self.multiworld.get_location("Mithalas Cathedral, Mithalan Dress", self.player),
@@ -1144,10 +1151,6 @@ class AquariaRegions:
lambda state: state.has("Sun God beated", self.player)) lambda state: state.has("Sun God beated", self.player))
add_rule(self.multiworld.get_location("The Body center area, breaking Li's cage", self.player), add_rule(self.multiworld.get_location("The Body center area, breaking Li's cage", self.player),
lambda state: _has_tongue_cleared(state, self.player)) lambda state: _has_tongue_cleared(state, self.player))
add_rule(self.multiworld.get_location(
"Open Water top right area, bulb in the small path before Mithalas",
self.player), lambda state: _has_bind_song(state, self.player)
)
def __no_progression_hard_or_hidden_location(self) -> None: def __no_progression_hard_or_hidden_location(self) -> None:
self.multiworld.get_location("Energy Temple boss area, Fallen God Tooth", self.multiworld.get_location("Energy Temple boss area, Fallen God Tooth",

View File

@@ -130,13 +130,12 @@ class AquariaWorld(World):
return result return result
def __pre_fill_item(self, item_name: str, location_name: str, precollected, def __pre_fill_item(self, item_name: str, location_name: str, precollected) -> None:
itemClassification: ItemClassification = ItemClassification.useful) -> None:
"""Pre-assign an item to a location""" """Pre-assign an item to a location"""
if item_name not in precollected: if item_name not in precollected:
self.exclude.append(item_name) self.exclude.append(item_name)
data = item_table[item_name] data = item_table[item_name]
item = AquariaItem(item_name, itemClassification, data.id, self.player) item = AquariaItem(item_name, ItemClassification.useful, data.id, self.player)
self.multiworld.get_location(location_name, self.player).place_locked_item(item) self.multiworld.get_location(location_name, self.player).place_locked_item(item)
def get_filler_item_name(self): def get_filler_item_name(self):
@@ -165,8 +164,7 @@ class AquariaWorld(World):
self.__pre_fill_item("Transturtle Abyss right", "Abyss right area, Transturtle", precollected) self.__pre_fill_item("Transturtle Abyss right", "Abyss right area, Transturtle", precollected)
self.__pre_fill_item("Transturtle Final Boss", "Final Boss area, Transturtle", precollected) self.__pre_fill_item("Transturtle Final Boss", "Final Boss area, Transturtle", precollected)
# The last two are inverted because in the original game, they are special turtle that communicate directly # The last two are inverted because in the original game, they are special turtle that communicate directly
self.__pre_fill_item("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected, self.__pre_fill_item("Transturtle Simon Says", "Arnassi Ruins, Transturtle", precollected)
ItemClassification.progression)
self.__pre_fill_item("Transturtle Arnassi Ruins", "Simon Says area, Transturtle", precollected) self.__pre_fill_item("Transturtle Arnassi Ruins", "Simon Says area, Transturtle", precollected)
for name, data in item_table.items(): for name, data in item_table.items():
if name not in self.exclude: if name not in self.exclude:
@@ -214,8 +212,4 @@ class AquariaWorld(World):
"skip_first_vision": bool(self.options.skip_first_vision.value), "skip_first_vision": bool(self.options.skip_first_vision.value),
"unconfine_home_water_energy_door": self.options.unconfine_home_water.value in [1, 3], "unconfine_home_water_energy_door": self.options.unconfine_home_water.value in [1, 3],
"unconfine_home_water_transturtle": self.options.unconfine_home_water.value in [2, 3], "unconfine_home_water_transturtle": self.options.unconfine_home_water.value in [2, 3],
"bind_song_needed_to_get_under_rock_bulb": bool(self.options.bind_song_needed_to_get_under_rock_bulb),
"no_progression_hard_or_hidden_locations": bool(self.options.no_progression_hard_or_hidden_locations),
"light_needed_to_get_to_dark_places": bool(self.options.light_needed_to_get_to_dark_places),
"turtle_randomizer": self.options.turtle_randomizer.value,
} }

View File

@@ -8,8 +8,6 @@
## Optional Software ## Optional Software
- For sending [commands](/tutorial/Archipelago/commands/en) like `!hint`: the TextClient from [the most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases) - For sending [commands](/tutorial/Archipelago/commands/en) like `!hint`: the TextClient from [the most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases)
- [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest), for use with
[PopTracker](https://github.com/black-sliver/PopTracker/releases/latest)
## Installation and execution Procedures ## Installation and execution Procedures
@@ -115,16 +113,3 @@ sure that your executable has executable permission:
```bash ```bash
chmod +x aquaria_randomizer chmod +x aquaria_randomizer
``` ```
## Auto-Tracking
Aquaria has a fully functional map tracker that supports auto-tracking.
1. Download [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest) and
[PopTracker](https://github.com/black-sliver/PopTracker/releases/latest).
2. Put the tracker pack into /packs/ in your PopTracker install.
3. Open PopTracker, and load the Aquaria pack.
4. For autotracking, click on the "AP" symbol at the top.
5. Enter the Archipelago server address (the one you connected your client to), slot name, and password.
This pack will automatically prompt you to update if one is available.

View File

@@ -2,14 +2,9 @@
## Logiciels nécessaires ## Logiciels nécessaires
- Une copie du jeu Aquaria non-modifiée (disponible sur la majorité des sites de ventes de jeux vidéos en ligne) - Le jeu Aquaria original (trouvable sur la majorité des sites de ventes de jeux vidéo en ligne)
- Le client du Randomizer d'Aquaria [Aquaria randomizer] - Le client Randomizer d'Aquaria [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases)
(https://github.com/tioui/Aquaria_Randomizer/releases)
## Logiciels optionnels
- De manière optionnel, pour pouvoir envoyer des [commandes](/tutorial/Archipelago/commands/en) comme `!hint`: utilisez le client texte de [la version la plus récente d'Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) - De manière optionnel, pour pouvoir envoyer des [commandes](/tutorial/Archipelago/commands/en) comme `!hint`: utilisez le client texte de [la version la plus récente d'Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
- [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest), pour utiliser avec [PopTracker](https://github.com/black-sliver/PopTracker/releases/latest)
## Procédures d'installation et d'exécution ## Procédures d'installation et d'exécution
@@ -121,15 +116,3 @@ pour vous assurer que votre fichier est exécutable:
```bash ```bash
chmod +x aquaria_randomizer chmod +x aquaria_randomizer
``` ```
## Tracking automatique
Aquaria a un tracker complet qui supporte le tracking automatique.
1. Téléchargez [Aquaria AP Tracker](https://github.com/palex00/aquaria-ap-tracker/releases/latest) et [PopTracker](https://github.com/black-sliver/PopTracker/releases/latest).
2. Mettre le fichier compressé du tracker dans le sous-répertoire /packs/ du répertoire d'installation de PopTracker.
3. Lancez PopTracker, et ouvrez le pack d'Aquaria.
4. Pour activer le tracking automatique, cliquez sur le symbole "AP" dans le haut de la fenêtre.
5. Entrez l'adresse du serveur Archipelago (le serveur auquel vous avez connecté le client), le nom de votre slot, et le mot de passe (si un mot de passe est nécessaire).
Le logiciel vous indiquera si une mise à jour du pack est disponible.

View File

@@ -89,7 +89,6 @@ class DarkSouls3World(World):
self.all_excluded_locations = set() self.all_excluded_locations = set()
def generate_early(self) -> None: def generate_early(self) -> None:
self.created_regions = set()
self.all_excluded_locations.update(self.options.exclude_locations.value) self.all_excluded_locations.update(self.options.exclude_locations.value)
# Inform Universal Tracker where Yhorm is being randomized to. # Inform Universal Tracker where Yhorm is being randomized to.
@@ -295,7 +294,6 @@ class DarkSouls3World(World):
new_region.locations.append(new_location) new_region.locations.append(new_location)
self.multiworld.regions.append(new_region) self.multiworld.regions.append(new_region)
self.created_regions.add(region_name)
return new_region return new_region
def create_items(self) -> None: def create_items(self) -> None:
@@ -1307,7 +1305,7 @@ class DarkSouls3World(World):
def _add_entrance_rule(self, region: str, rule: Union[CollectionRule, str]) -> None: def _add_entrance_rule(self, region: str, rule: Union[CollectionRule, str]) -> None:
"""Sets a rule for the entrance to the given region.""" """Sets a rule for the entrance to the given region."""
assert region in location_tables assert region in location_tables
if region not in self.created_regions: return if not any(region == reg for reg in self.multiworld.regions.region_cache[self.player]): return
if isinstance(rule, str): if isinstance(rule, str):
if " -> " not in rule: if " -> " not in rule:
assert item_dictionary[rule].classification == ItemClassification.progression assert item_dictionary[rule].classification == ItemClassification.progression

View File

@@ -77,7 +77,7 @@ option_docstrings = {
"RandomizeLoreTablets": "Randomize Lore items into the itempool, one per Lore Tablet, and place randomized item " "RandomizeLoreTablets": "Randomize Lore items into the itempool, one per Lore Tablet, and place randomized item "
"grants on the tablets themselves.\n You must still read the tablet to get the item.", "grants on the tablets themselves.\n You must still read the tablet to get the item.",
"PreciseMovement": "Places skips into logic which require extremely precise player movement, possibly without " "PreciseMovement": "Places skips into logic which require extremely precise player movement, possibly without "
"movement skills such as\n dash or claw.", "movement skills such as\n dash or hook.",
"ProficientCombat": "Places skips into logic which require proficient combat, possibly with limited items.", "ProficientCombat": "Places skips into logic which require proficient combat, possibly with limited items.",
"BackgroundObjectPogos": "Places skips into logic for locations which are reachable via pogoing off of " "BackgroundObjectPogos": "Places skips into logic for locations which are reachable via pogoing off of "
"background objects.", "background objects.",

View File

@@ -220,8 +220,6 @@ class MessengerRules:
} }
self.location_rules = { self.location_rules = {
# hq
"Money Wrench": self.can_shop,
# ninja village # ninja village
"Ninja Village Seal - Tree House": "Ninja Village Seal - Tree House":
self.has_dart, self.has_dart,

View File

@@ -1,6 +1,5 @@
from typing import Dict from typing import Dict
from BaseClasses import CollectionState
from . import MessengerTestBase from . import MessengerTestBase
from ..shop import SHOP_ITEMS, FIGURINES from ..shop import SHOP_ITEMS, FIGURINES
@@ -90,15 +89,3 @@ class PlandoTest(MessengerTestBase):
self.assertTrue(loc in FIGURINES) self.assertTrue(loc in FIGURINES)
self.assertEqual(len(figures), len(FIGURINES)) self.assertEqual(len(figures), len(FIGURINES))
max_cost_state = CollectionState(self.multiworld)
self.assertFalse(self.world.get_location("Money Wrench").can_reach(max_cost_state))
prog_shards = []
for item in self.multiworld.itempool:
if "Time Shard " in item.name:
value = int(item.name.strip("Time Shard ()"))
if value >= 100:
prog_shards.append(item)
for shard in prog_shards:
max_cost_state.collect(shard, True)
self.assertTrue(self.world.get_location("Money Wrench").can_reach(max_cost_state))

View File

@@ -29,7 +29,7 @@ def shuffle_structures(self: "MinecraftWorld") -> None:
# Connect plando structures first # Connect plando structures first
if self.options.plando_connections: if self.options.plando_connections:
for conn in self.options.plando_connections: for conn in self.plando_connections:
set_pair(conn.entrance, conn.exit) set_pair(conn.entrance, conn.exit)
# The algorithm tries to place the most restrictive structures first. This algorithm always works on the # The algorithm tries to place the most restrictive structures first. This algorithm always works on the

View File

@@ -100,13 +100,13 @@ item_table: Dict[str, ItemData] = {
"Wand (Tier 5)": ItemData(110010, "Wands", ItemClassification.useful, 1), "Wand (Tier 5)": ItemData(110010, "Wands", ItemClassification.useful, 1),
"Wand (Tier 6)": ItemData(110011, "Wands", ItemClassification.useful, 1), "Wand (Tier 6)": ItemData(110011, "Wands", ItemClassification.useful, 1),
"Kantele": ItemData(110012, "Wands", ItemClassification.useful), "Kantele": ItemData(110012, "Wands", ItemClassification.useful),
"Fire Immunity Perk": ItemData(110013, "Perks", ItemClassification.progression | ItemClassification.useful, 1), "Fire Immunity Perk": ItemData(110013, "Perks", ItemClassification.progression, 1),
"Toxic Immunity Perk": ItemData(110014, "Perks", ItemClassification.progression | ItemClassification.useful, 1), "Toxic Immunity Perk": ItemData(110014, "Perks", ItemClassification.progression, 1),
"Explosion Immunity Perk": ItemData(110015, "Perks", ItemClassification.progression | ItemClassification.useful, 1), "Explosion Immunity Perk": ItemData(110015, "Perks", ItemClassification.progression, 1),
"Melee Immunity Perk": ItemData(110016, "Perks", ItemClassification.progression | ItemClassification.useful, 1), "Melee Immunity Perk": ItemData(110016, "Perks", ItemClassification.progression, 1),
"Electricity Immunity Perk": ItemData(110017, "Perks", ItemClassification.progression | ItemClassification.useful, 1), "Electricity Immunity Perk": ItemData(110017, "Perks", ItemClassification.progression, 1),
"Tinker with Wands Everywhere Perk": ItemData(110018, "Perks", ItemClassification.progression | ItemClassification.useful, 1), "Tinker with Wands Everywhere Perk": ItemData(110018, "Perks", ItemClassification.progression, 1),
"All-Seeing Eye Perk": ItemData(110019, "Perks", ItemClassification.progression | ItemClassification.useful, 1), "All-Seeing Eye Perk": ItemData(110019, "Perks", ItemClassification.progression, 1),
"Spatial Awareness Perk": ItemData(110020, "Perks", ItemClassification.progression), "Spatial Awareness Perk": ItemData(110020, "Perks", ItemClassification.progression),
"Extra Life Perk": ItemData(110021, "Repeatable Perks", ItemClassification.useful, 1), "Extra Life Perk": ItemData(110021, "Repeatable Perks", ItemClassification.useful, 1),
"Orb": ItemData(110022, "Orbs", ItemClassification.progression_skip_balancing), "Orb": ItemData(110022, "Orbs", ItemClassification.progression_skip_balancing),

View File

@@ -184,10 +184,6 @@ class OOTWorld(World):
"Small Key Ring (Spirit Temple)", "Small Key Ring (Thieves Hideout)", "Small Key Ring (Water Temple)", "Small Key Ring (Spirit Temple)", "Small Key Ring (Thieves Hideout)", "Small Key Ring (Water Temple)",
"Boss Key (Fire Temple)", "Boss Key (Forest Temple)", "Boss Key (Ganons Castle)", "Boss Key (Fire Temple)", "Boss Key (Forest Temple)", "Boss Key (Ganons Castle)",
"Boss Key (Shadow Temple)", "Boss Key (Spirit Temple)", "Boss Key (Water Temple)"}, "Boss Key (Shadow Temple)", "Boss Key (Spirit Temple)", "Boss Key (Water Temple)"},
# aliases
"Longshot": {"Progressive Hookshot"}, # fuzzy hinting thought Longshot was Slingshot
"Hookshot": {"Progressive Hookshot"}, # for consistency, mostly
} }
location_name_groups = build_location_name_groups() location_name_groups = build_location_name_groups()

View File

@@ -177,7 +177,7 @@ class PokemonEmeraldWorld(World):
for species_name in self.options.trainer_party_blacklist.value for species_name in self.options.trainer_party_blacklist.value
if species_name != "_Legendaries" if species_name != "_Legendaries"
} }
if "_Legendaries" in self.options.trainer_party_blacklist.value: if "_Legendaries" in self.options.starter_blacklist.value:
self.blacklisted_opponent_pokemon |= LEGENDARY_POKEMON self.blacklisted_opponent_pokemon |= LEGENDARY_POKEMON
# In race mode we don't patch any item location information into the ROM # In race mode we don't patch any item location information into the ROM

View File

@@ -117,11 +117,6 @@ LEGENDARY_NAMES = {k.lower(): v for k, v in {
DEFEATED_LEGENDARY_FLAG_MAP = {data.constants[f"FLAG_DEFEATED_{name}"]: name for name in LEGENDARY_NAMES.values()} DEFEATED_LEGENDARY_FLAG_MAP = {data.constants[f"FLAG_DEFEATED_{name}"]: name for name in LEGENDARY_NAMES.values()}
CAUGHT_LEGENDARY_FLAG_MAP = {data.constants[f"FLAG_CAUGHT_{name}"]: name for name in LEGENDARY_NAMES.values()} CAUGHT_LEGENDARY_FLAG_MAP = {data.constants[f"FLAG_CAUGHT_{name}"]: name for name in LEGENDARY_NAMES.values()}
SHOAL_CAVE_MAPS = tuple(data.constants[map_name] for map_name in [
"MAP_SHOAL_CAVE_LOW_TIDE_ENTRANCE_ROOM",
"MAP_SHOAL_CAVE_LOW_TIDE_INNER_ROOM",
])
class PokemonEmeraldClient(BizHawkClient): class PokemonEmeraldClient(BizHawkClient):
game = "Pokemon Emerald" game = "Pokemon Emerald"
@@ -419,17 +414,13 @@ class PokemonEmeraldClient(BizHawkClient):
read_result = await bizhawk.guarded_read( read_result = await bizhawk.guarded_read(
ctx.bizhawk_ctx, ctx.bizhawk_ctx,
[ [(sb1_address + 0x4, 2, "System Bus")],
(sb1_address + 0x4, 2, "System Bus"), # Current map [guards["SAVE BLOCK 1"]]
(sb1_address + 0x1450 + (data.constants["FLAG_SYS_SHOAL_TIDE"] // 8), 1, "System Bus"),
],
[guards["IN OVERWORLD"], guards["SAVE BLOCK 1"]]
) )
if read_result is None: # Save block moved if read_result is None: # Save block moved
return return
current_map = int.from_bytes(read_result[0], "big") current_map = int.from_bytes(read_result[0], "big")
shoal_cave = int(read_result[1][0] & (1 << (data.constants["FLAG_SYS_SHOAL_TIDE"] % 8)) > 0)
if current_map != self.current_map: if current_map != self.current_map:
self.current_map = current_map self.current_map = current_map
await ctx.send_msgs([{ await ctx.send_msgs([{
@@ -438,7 +429,6 @@ class PokemonEmeraldClient(BizHawkClient):
"data": { "data": {
"type": "MapUpdate", "type": "MapUpdate",
"mapId": current_map, "mapId": current_map,
**({"tide": shoal_cave} if current_map in SHOAL_CAVE_MAPS else {}),
}, },
}]) }])

View File

@@ -417,16 +417,13 @@ class HiddenTraps(Traps):
"""List of traps that may be in the item pool to find""" """List of traps that may be in the item pool to find"""
visibility = Visibility.none visibility = Visibility.none
class HiddenDeathLink(DeathLink): class OptionsHider:
"""When you die, everyone who enabled death link dies. Of course, the reverse is true too.""" @classmethod
visibility = Visibility.none def hidden(cls, option: Type[Option[Any]]) -> Type[Option]:
new_option = AssembleOptions(f"{option}Hidden", option.__bases__, vars(option).copy())
def hidden(option: Type[Option[Any]]) -> Type[Option]: new_option.visibility = Visibility.none
new_option = AssembleOptions(f"{option.__name__}Hidden", option.__bases__, vars(option).copy()) new_option.__doc__ = option.__doc__
new_option.visibility = Visibility.none return new_option
new_option.__doc__ = option.__doc__
globals()[f"{option.__name__}Hidden"] = new_option
return new_option
class HasReplacedCamelCase(Toggle): class HasReplacedCamelCase(Toggle):
"""For internal use will display a warning message if true""" """For internal use will display a warning message if true"""
@@ -434,41 +431,41 @@ class HasReplacedCamelCase(Toggle):
@dataclass @dataclass
class BackwardsCompatiableTimespinnerOptions(TimespinnerOptions): class BackwardsCompatiableTimespinnerOptions(TimespinnerOptions):
StartWithJewelryBox: hidden(StartWithJewelryBox) # type: ignore StartWithJewelryBox: OptionsHider.hidden(StartWithJewelryBox) # type: ignore
DownloadableItems: hidden(DownloadableItems) # type: ignore DownloadableItems: OptionsHider.hidden(DownloadableItems) # type: ignore
EyeSpy: hidden(EyeSpy) # type: ignore EyeSpy: OptionsHider.hidden(EyeSpy) # type: ignore
StartWithMeyef: hidden(StartWithMeyef) # type: ignore StartWithMeyef: OptionsHider.hidden(StartWithMeyef) # type: ignore
QuickSeed: hidden(QuickSeed) # type: ignore QuickSeed: OptionsHider.hidden(QuickSeed) # type: ignore
SpecificKeycards: hidden(SpecificKeycards) # type: ignore SpecificKeycards: OptionsHider.hidden(SpecificKeycards) # type: ignore
Inverted: hidden(Inverted) # type: ignore Inverted: OptionsHider.hidden(Inverted) # type: ignore
GyreArchives: hidden(GyreArchives) # type: ignore GyreArchives: OptionsHider.hidden(GyreArchives) # type: ignore
Cantoran: hidden(Cantoran) # type: ignore Cantoran: OptionsHider.hidden(Cantoran) # type: ignore
LoreChecks: hidden(LoreChecks) # type: ignore LoreChecks: OptionsHider.hidden(LoreChecks) # type: ignore
BossRando: hidden(BossRando) # type: ignore BossRando: OptionsHider.hidden(BossRando) # type: ignore
DamageRando: hidden(DamageRando) # type: ignore DamageRando: OptionsHider.hidden(DamageRando) # type: ignore
DamageRandoOverrides: HiddenDamageRandoOverrides DamageRandoOverrides: HiddenDamageRandoOverrides
HpCap: hidden(HpCap) # type: ignore HpCap: OptionsHider.hidden(HpCap) # type: ignore
LevelCap: hidden(LevelCap) # type: ignore LevelCap: OptionsHider.hidden(LevelCap) # type: ignore
ExtraEarringsXP: hidden(ExtraEarringsXP) # type: ignore ExtraEarringsXP: OptionsHider.hidden(ExtraEarringsXP) # type: ignore
BossHealing: hidden(BossHealing) # type: ignore BossHealing: OptionsHider.hidden(BossHealing) # type: ignore
ShopFill: hidden(ShopFill) # type: ignore ShopFill: OptionsHider.hidden(ShopFill) # type: ignore
ShopWarpShards: hidden(ShopWarpShards) # type: ignore ShopWarpShards: OptionsHider.hidden(ShopWarpShards) # type: ignore
ShopMultiplier: hidden(ShopMultiplier) # type: ignore ShopMultiplier: OptionsHider.hidden(ShopMultiplier) # type: ignore
LootPool: hidden(LootPool) # type: ignore LootPool: OptionsHider.hidden(LootPool) # type: ignore
DropRateCategory: hidden(DropRateCategory) # type: ignore DropRateCategory: OptionsHider.hidden(DropRateCategory) # type: ignore
FixedDropRate: hidden(FixedDropRate) # type: ignore FixedDropRate: OptionsHider.hidden(FixedDropRate) # type: ignore
LootTierDistro: hidden(LootTierDistro) # type: ignore LootTierDistro: OptionsHider.hidden(LootTierDistro) # type: ignore
ShowBestiary: hidden(ShowBestiary) # type: ignore ShowBestiary: OptionsHider.hidden(ShowBestiary) # type: ignore
ShowDrops: hidden(ShowDrops) # type: ignore ShowDrops: OptionsHider.hidden(ShowDrops) # type: ignore
EnterSandman: hidden(EnterSandman) # type: ignore EnterSandman: OptionsHider.hidden(EnterSandman) # type: ignore
DadPercent: hidden(DadPercent) # type: ignore DadPercent: OptionsHider.hidden(DadPercent) # type: ignore
RisingTides: hidden(RisingTides) # type: ignore RisingTides: OptionsHider.hidden(RisingTides) # type: ignore
RisingTidesOverrides: HiddenRisingTidesOverrides RisingTidesOverrides: HiddenRisingTidesOverrides
UnchainedKeys: hidden(UnchainedKeys) # type: ignore UnchainedKeys: OptionsHider.hidden(UnchainedKeys) # type: ignore
PresentAccessWithWheelAndSpindle: hidden(PresentAccessWithWheelAndSpindle) # type: ignore PresentAccessWithWheelAndSpindle: OptionsHider.hidden(PresentAccessWithWheelAndSpindle) # type: ignore
TrapChance: hidden(TrapChance) # type: ignore TrapChance: OptionsHider.hidden(TrapChance) # type: ignore
Traps: HiddenTraps # type: ignore Traps: HiddenTraps # type: ignore
DeathLink: HiddenDeathLink # type: ignore DeathLink: OptionsHider.hidden(DeathLink) # type: ignore
has_replaced_options: HasReplacedCamelCase has_replaced_options: HasReplacedCamelCase
def handle_backward_compatibility(self) -> None: def handle_backward_compatibility(self) -> None:

View File

@@ -190,7 +190,7 @@ class TimespinnerWorld(World):
if self.options.has_replaced_options: if self.options.has_replaced_options:
warning = \ warning = \
f"NOTICE: Timespinner options for player '{self.player_name}' were renamed from PascalCase to snake_case, " \ f"NOTICE: Timespinner options for player '{self.player_name}' where renamed from PasCalCase to snake_case, " \
"please update your yaml" "please update your yaml"
spoiler_handle.write("\n") spoiler_handle.write("\n")

View File

@@ -274,12 +274,6 @@ class TunicWorld(World):
if items_to_create[page] > 0: if items_to_create[page] > 0:
tunic_items.append(self.create_item(page, ItemClassification.useful)) tunic_items.append(self.create_item(page, ItemClassification.useful))
items_to_create[page] = 0 items_to_create[page] = 0
# if ice grapple logic is on, probably really want icebolt
elif self.options.ice_grappling:
page = "Pages 52-53 (Icebolt)"
if items_to_create[page] > 0:
tunic_items.append(self.create_item(page, ItemClassification.progression | ItemClassification.useful))
items_to_create[page] = 0
if self.options.maskless: if self.options.maskless:
tunic_items.append(self.create_item("Scavenger Mask", ItemClassification.useful)) tunic_items.append(self.create_item("Scavenger Mask", ItemClassification.useful))

View File

@@ -1,10 +1,10 @@
from itertools import groupby from itertools import groupby
from typing import Dict, List, Set, NamedTuple from typing import Dict, List, Set, NamedTuple
from BaseClasses import ItemClassification as IC from BaseClasses import ItemClassification
class TunicItemData(NamedTuple): class TunicItemData(NamedTuple):
classification: IC classification: ItemClassification
quantity_in_item_pool: int quantity_in_item_pool: int
item_id_offset: int item_id_offset: int
item_group: str = "" item_group: str = ""
@@ -13,157 +13,157 @@ class TunicItemData(NamedTuple):
item_base_id = 509342400 item_base_id = 509342400
item_table: Dict[str, TunicItemData] = { item_table: Dict[str, TunicItemData] = {
"Firecracker x2": TunicItemData(IC.filler, 3, 0, "Bombs"), "Firecracker x2": TunicItemData(ItemClassification.filler, 3, 0, "Bombs"),
"Firecracker x3": TunicItemData(IC.filler, 3, 1, "Bombs"), "Firecracker x3": TunicItemData(ItemClassification.filler, 3, 1, "Bombs"),
"Firecracker x4": TunicItemData(IC.filler, 3, 2, "Bombs"), "Firecracker x4": TunicItemData(ItemClassification.filler, 3, 2, "Bombs"),
"Firecracker x5": TunicItemData(IC.filler, 1, 3, "Bombs"), "Firecracker x5": TunicItemData(ItemClassification.filler, 1, 3, "Bombs"),
"Firecracker x6": TunicItemData(IC.filler, 2, 4, "Bombs"), "Firecracker x6": TunicItemData(ItemClassification.filler, 2, 4, "Bombs"),
"Fire Bomb x2": TunicItemData(IC.filler, 2, 5, "Bombs"), "Fire Bomb x2": TunicItemData(ItemClassification.filler, 2, 5, "Bombs"),
"Fire Bomb x3": TunicItemData(IC.filler, 1, 6, "Bombs"), "Fire Bomb x3": TunicItemData(ItemClassification.filler, 1, 6, "Bombs"),
"Ice Bomb x2": TunicItemData(IC.filler, 2, 7, "Bombs"), "Ice Bomb x2": TunicItemData(ItemClassification.filler, 2, 7, "Bombs"),
"Ice Bomb x3": TunicItemData(IC.filler, 2, 8, "Bombs"), "Ice Bomb x3": TunicItemData(ItemClassification.filler, 2, 8, "Bombs"),
"Ice Bomb x5": TunicItemData(IC.filler, 1, 9, "Bombs"), "Ice Bomb x5": TunicItemData(ItemClassification.filler, 1, 9, "Bombs"),
"Lure": TunicItemData(IC.filler, 4, 10, "Consumables"), "Lure": TunicItemData(ItemClassification.filler, 4, 10, "Consumables"),
"Lure x2": TunicItemData(IC.filler, 1, 11, "Consumables"), "Lure x2": TunicItemData(ItemClassification.filler, 1, 11, "Consumables"),
"Pepper x2": TunicItemData(IC.filler, 4, 12, "Consumables"), "Pepper x2": TunicItemData(ItemClassification.filler, 4, 12, "Consumables"),
"Ivy x3": TunicItemData(IC.filler, 2, 13, "Consumables"), "Ivy x3": TunicItemData(ItemClassification.filler, 2, 13, "Consumables"),
"Effigy": TunicItemData(IC.useful, 12, 14, "Money"), "Effigy": TunicItemData(ItemClassification.useful, 12, 14, "Money"),
"HP Berry": TunicItemData(IC.filler, 2, 15, "Consumables"), "HP Berry": TunicItemData(ItemClassification.filler, 2, 15, "Consumables"),
"HP Berry x2": TunicItemData(IC.filler, 4, 16, "Consumables"), "HP Berry x2": TunicItemData(ItemClassification.filler, 4, 16, "Consumables"),
"HP Berry x3": TunicItemData(IC.filler, 2, 17, "Consumables"), "HP Berry x3": TunicItemData(ItemClassification.filler, 2, 17, "Consumables"),
"MP Berry": TunicItemData(IC.filler, 4, 18, "Consumables"), "MP Berry": TunicItemData(ItemClassification.filler, 4, 18, "Consumables"),
"MP Berry x2": TunicItemData(IC.filler, 2, 19, "Consumables"), "MP Berry x2": TunicItemData(ItemClassification.filler, 2, 19, "Consumables"),
"MP Berry x3": TunicItemData(IC.filler, 7, 20, "Consumables"), "MP Berry x3": TunicItemData(ItemClassification.filler, 7, 20, "Consumables"),
"Fairy": TunicItemData(IC.progression, 20, 21), "Fairy": TunicItemData(ItemClassification.progression, 20, 21),
"Stick": TunicItemData(IC.progression | IC.useful, 1, 22, "Weapons"), "Stick": TunicItemData(ItemClassification.progression, 1, 22, "Weapons"),
"Sword": TunicItemData(IC.progression | IC.useful, 3, 23, "Weapons"), "Sword": TunicItemData(ItemClassification.progression, 3, 23, "Weapons"),
"Sword Upgrade": TunicItemData(IC.progression | IC.useful, 4, 24, "Weapons"), "Sword Upgrade": TunicItemData(ItemClassification.progression, 4, 24, "Weapons"),
"Magic Wand": TunicItemData(IC.progression | IC.useful, 1, 25, "Weapons"), "Magic Wand": TunicItemData(ItemClassification.progression, 1, 25, "Weapons"),
"Magic Dagger": TunicItemData(IC.progression | IC.useful, 1, 26), "Magic Dagger": TunicItemData(ItemClassification.progression, 1, 26),
"Magic Orb": TunicItemData(IC.progression | IC.useful, 1, 27), "Magic Orb": TunicItemData(ItemClassification.progression, 1, 27),
"Hero's Laurels": TunicItemData(IC.progression | IC.useful, 1, 28), "Hero's Laurels": TunicItemData(ItemClassification.progression, 1, 28),
"Lantern": TunicItemData(IC.progression, 1, 29), "Lantern": TunicItemData(ItemClassification.progression, 1, 29),
"Gun": TunicItemData(IC.progression | IC.useful, 1, 30, "Weapons"), "Gun": TunicItemData(ItemClassification.progression, 1, 30, "Weapons"),
"Shield": TunicItemData(IC.useful, 1, 31), "Shield": TunicItemData(ItemClassification.useful, 1, 31),
"Dath Stone": TunicItemData(IC.useful, 1, 32), "Dath Stone": TunicItemData(ItemClassification.useful, 1, 32),
"Hourglass": TunicItemData(IC.useful, 1, 33), "Hourglass": TunicItemData(ItemClassification.useful, 1, 33),
"Old House Key": TunicItemData(IC.progression, 1, 34, "Keys"), "Old House Key": TunicItemData(ItemClassification.progression, 1, 34, "Keys"),
"Key": TunicItemData(IC.progression, 2, 35, "Keys"), "Key": TunicItemData(ItemClassification.progression, 2, 35, "Keys"),
"Fortress Vault Key": TunicItemData(IC.progression, 1, 36, "Keys"), "Fortress Vault Key": TunicItemData(ItemClassification.progression, 1, 36, "Keys"),
"Flask Shard": TunicItemData(IC.useful, 12, 37), "Flask Shard": TunicItemData(ItemClassification.useful, 12, 37),
"Potion Flask": TunicItemData(IC.useful, 5, 38, "Flask"), "Potion Flask": TunicItemData(ItemClassification.useful, 5, 38, "Flask"),
"Golden Coin": TunicItemData(IC.progression, 17, 39), "Golden Coin": TunicItemData(ItemClassification.progression, 17, 39),
"Card Slot": TunicItemData(IC.useful, 4, 40), "Card Slot": TunicItemData(ItemClassification.useful, 4, 40),
"Red Questagon": TunicItemData(IC.progression_skip_balancing, 1, 41, "Hexagons"), "Red Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 41, "Hexagons"),
"Green Questagon": TunicItemData(IC.progression_skip_balancing, 1, 42, "Hexagons"), "Green Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 42, "Hexagons"),
"Blue Questagon": TunicItemData(IC.progression_skip_balancing, 1, 43, "Hexagons"), "Blue Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 43, "Hexagons"),
"Gold Questagon": TunicItemData(IC.progression_skip_balancing, 0, 44, "Hexagons"), "Gold Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 0, 44, "Hexagons"),
"ATT Offering": TunicItemData(IC.useful, 4, 45, "Offerings"), "ATT Offering": TunicItemData(ItemClassification.useful, 4, 45, "Offerings"),
"DEF Offering": TunicItemData(IC.useful, 4, 46, "Offerings"), "DEF Offering": TunicItemData(ItemClassification.useful, 4, 46, "Offerings"),
"Potion Offering": TunicItemData(IC.useful, 3, 47, "Offerings"), "Potion Offering": TunicItemData(ItemClassification.useful, 3, 47, "Offerings"),
"HP Offering": TunicItemData(IC.useful, 6, 48, "Offerings"), "HP Offering": TunicItemData(ItemClassification.useful, 6, 48, "Offerings"),
"MP Offering": TunicItemData(IC.useful, 3, 49, "Offerings"), "MP Offering": TunicItemData(ItemClassification.useful, 3, 49, "Offerings"),
"SP Offering": TunicItemData(IC.useful, 2, 50, "Offerings"), "SP Offering": TunicItemData(ItemClassification.useful, 2, 50, "Offerings"),
"Hero Relic - ATT": TunicItemData(IC.progression_skip_balancing, 1, 51, "Hero Relics"), "Hero Relic - ATT": TunicItemData(ItemClassification.progression_skip_balancing, 1, 51, "Hero Relics"),
"Hero Relic - DEF": TunicItemData(IC.progression_skip_balancing, 1, 52, "Hero Relics"), "Hero Relic - DEF": TunicItemData(ItemClassification.progression_skip_balancing, 1, 52, "Hero Relics"),
"Hero Relic - HP": TunicItemData(IC.progression_skip_balancing, 1, 53, "Hero Relics"), "Hero Relic - HP": TunicItemData(ItemClassification.progression_skip_balancing, 1, 53, "Hero Relics"),
"Hero Relic - MP": TunicItemData(IC.progression_skip_balancing, 1, 54, "Hero Relics"), "Hero Relic - MP": TunicItemData(ItemClassification.progression_skip_balancing, 1, 54, "Hero Relics"),
"Hero Relic - POTION": TunicItemData(IC.progression_skip_balancing, 1, 55, "Hero Relics"), "Hero Relic - POTION": TunicItemData(ItemClassification.progression_skip_balancing, 1, 55, "Hero Relics"),
"Hero Relic - SP": TunicItemData(IC.progression_skip_balancing, 1, 56, "Hero Relics"), "Hero Relic - SP": TunicItemData(ItemClassification.progression_skip_balancing, 1, 56, "Hero Relics"),
"Orange Peril Ring": TunicItemData(IC.useful, 1, 57, "Cards"), "Orange Peril Ring": TunicItemData(ItemClassification.useful, 1, 57, "Cards"),
"Tincture": TunicItemData(IC.useful, 1, 58, "Cards"), "Tincture": TunicItemData(ItemClassification.useful, 1, 58, "Cards"),
"Scavenger Mask": TunicItemData(IC.progression, 1, 59, "Cards"), "Scavenger Mask": TunicItemData(ItemClassification.progression, 1, 59, "Cards"),
"Cyan Peril Ring": TunicItemData(IC.useful, 1, 60, "Cards"), "Cyan Peril Ring": TunicItemData(ItemClassification.useful, 1, 60, "Cards"),
"Bracer": TunicItemData(IC.useful, 1, 61, "Cards"), "Bracer": TunicItemData(ItemClassification.useful, 1, 61, "Cards"),
"Dagger Strap": TunicItemData(IC.useful, 1, 62, "Cards"), "Dagger Strap": TunicItemData(ItemClassification.useful, 1, 62, "Cards"),
"Inverted Ash": TunicItemData(IC.useful, 1, 63, "Cards"), "Inverted Ash": TunicItemData(ItemClassification.useful, 1, 63, "Cards"),
"Lucky Cup": TunicItemData(IC.useful, 1, 64, "Cards"), "Lucky Cup": TunicItemData(ItemClassification.useful, 1, 64, "Cards"),
"Magic Echo": TunicItemData(IC.useful, 1, 65, "Cards"), "Magic Echo": TunicItemData(ItemClassification.useful, 1, 65, "Cards"),
"Anklet": TunicItemData(IC.useful, 1, 66, "Cards"), "Anklet": TunicItemData(ItemClassification.useful, 1, 66, "Cards"),
"Muffling Bell": TunicItemData(IC.useful, 1, 67, "Cards"), "Muffling Bell": TunicItemData(ItemClassification.useful, 1, 67, "Cards"),
"Glass Cannon": TunicItemData(IC.useful, 1, 68, "Cards"), "Glass Cannon": TunicItemData(ItemClassification.useful, 1, 68, "Cards"),
"Perfume": TunicItemData(IC.useful, 1, 69, "Cards"), "Perfume": TunicItemData(ItemClassification.useful, 1, 69, "Cards"),
"Louder Echo": TunicItemData(IC.useful, 1, 70, "Cards"), "Louder Echo": TunicItemData(ItemClassification.useful, 1, 70, "Cards"),
"Aura's Gem": TunicItemData(IC.useful, 1, 71, "Cards"), "Aura's Gem": TunicItemData(ItemClassification.useful, 1, 71, "Cards"),
"Bone Card": TunicItemData(IC.useful, 1, 72, "Cards"), "Bone Card": TunicItemData(ItemClassification.useful, 1, 72, "Cards"),
"Mr Mayor": TunicItemData(IC.useful, 1, 73, "Golden Treasures"), "Mr Mayor": TunicItemData(ItemClassification.useful, 1, 73, "Golden Treasures"),
"Secret Legend": TunicItemData(IC.useful, 1, 74, "Golden Treasures"), "Secret Legend": TunicItemData(ItemClassification.useful, 1, 74, "Golden Treasures"),
"Sacred Geometry": TunicItemData(IC.useful, 1, 75, "Golden Treasures"), "Sacred Geometry": TunicItemData(ItemClassification.useful, 1, 75, "Golden Treasures"),
"Vintage": TunicItemData(IC.useful, 1, 76, "Golden Treasures"), "Vintage": TunicItemData(ItemClassification.useful, 1, 76, "Golden Treasures"),
"Just Some Pals": TunicItemData(IC.useful, 1, 77, "Golden Treasures"), "Just Some Pals": TunicItemData(ItemClassification.useful, 1, 77, "Golden Treasures"),
"Regal Weasel": TunicItemData(IC.useful, 1, 78, "Golden Treasures"), "Regal Weasel": TunicItemData(ItemClassification.useful, 1, 78, "Golden Treasures"),
"Spring Falls": TunicItemData(IC.useful, 1, 79, "Golden Treasures"), "Spring Falls": TunicItemData(ItemClassification.useful, 1, 79, "Golden Treasures"),
"Power Up": TunicItemData(IC.useful, 1, 80, "Golden Treasures"), "Power Up": TunicItemData(ItemClassification.useful, 1, 80, "Golden Treasures"),
"Back To Work": TunicItemData(IC.useful, 1, 81, "Golden Treasures"), "Back To Work": TunicItemData(ItemClassification.useful, 1, 81, "Golden Treasures"),
"Phonomath": TunicItemData(IC.useful, 1, 82, "Golden Treasures"), "Phonomath": TunicItemData(ItemClassification.useful, 1, 82, "Golden Treasures"),
"Dusty": TunicItemData(IC.useful, 1, 83, "Golden Treasures"), "Dusty": TunicItemData(ItemClassification.useful, 1, 83, "Golden Treasures"),
"Forever Friend": TunicItemData(IC.useful, 1, 84, "Golden Treasures"), "Forever Friend": TunicItemData(ItemClassification.useful, 1, 84, "Golden Treasures"),
"Fool Trap": TunicItemData(IC.trap, 0, 85), "Fool Trap": TunicItemData(ItemClassification.trap, 0, 85),
"Money x1": TunicItemData(IC.filler, 3, 86, "Money"), "Money x1": TunicItemData(ItemClassification.filler, 3, 86, "Money"),
"Money x10": TunicItemData(IC.filler, 1, 87, "Money"), "Money x10": TunicItemData(ItemClassification.filler, 1, 87, "Money"),
"Money x15": TunicItemData(IC.filler, 10, 88, "Money"), "Money x15": TunicItemData(ItemClassification.filler, 10, 88, "Money"),
"Money x16": TunicItemData(IC.filler, 1, 89, "Money"), "Money x16": TunicItemData(ItemClassification.filler, 1, 89, "Money"),
"Money x20": TunicItemData(IC.filler, 17, 90, "Money"), "Money x20": TunicItemData(ItemClassification.filler, 17, 90, "Money"),
"Money x25": TunicItemData(IC.filler, 14, 91, "Money"), "Money x25": TunicItemData(ItemClassification.filler, 14, 91, "Money"),
"Money x30": TunicItemData(IC.filler, 4, 92, "Money"), "Money x30": TunicItemData(ItemClassification.filler, 4, 92, "Money"),
"Money x32": TunicItemData(IC.filler, 4, 93, "Money"), "Money x32": TunicItemData(ItemClassification.filler, 4, 93, "Money"),
"Money x40": TunicItemData(IC.filler, 3, 94, "Money"), "Money x40": TunicItemData(ItemClassification.filler, 3, 94, "Money"),
"Money x48": TunicItemData(IC.filler, 1, 95, "Money"), "Money x48": TunicItemData(ItemClassification.filler, 1, 95, "Money"),
"Money x50": TunicItemData(IC.filler, 7, 96, "Money"), "Money x50": TunicItemData(ItemClassification.filler, 7, 96, "Money"),
"Money x64": TunicItemData(IC.filler, 1, 97, "Money"), "Money x64": TunicItemData(ItemClassification.filler, 1, 97, "Money"),
"Money x100": TunicItemData(IC.filler, 5, 98, "Money"), "Money x100": TunicItemData(ItemClassification.filler, 5, 98, "Money"),
"Money x128": TunicItemData(IC.useful, 3, 99, "Money"), "Money x128": TunicItemData(ItemClassification.useful, 3, 99, "Money"),
"Money x200": TunicItemData(IC.useful, 1, 100, "Money"), "Money x200": TunicItemData(ItemClassification.useful, 1, 100, "Money"),
"Money x255": TunicItemData(IC.useful, 1, 101, "Money"), "Money x255": TunicItemData(ItemClassification.useful, 1, 101, "Money"),
"Pages 0-1": TunicItemData(IC.useful, 1, 102, "Pages"), "Pages 0-1": TunicItemData(ItemClassification.useful, 1, 102, "Pages"),
"Pages 2-3": TunicItemData(IC.useful, 1, 103, "Pages"), "Pages 2-3": TunicItemData(ItemClassification.useful, 1, 103, "Pages"),
"Pages 4-5": TunicItemData(IC.useful, 1, 104, "Pages"), "Pages 4-5": TunicItemData(ItemClassification.useful, 1, 104, "Pages"),
"Pages 6-7": TunicItemData(IC.useful, 1, 105, "Pages"), "Pages 6-7": TunicItemData(ItemClassification.useful, 1, 105, "Pages"),
"Pages 8-9": TunicItemData(IC.useful, 1, 106, "Pages"), "Pages 8-9": TunicItemData(ItemClassification.useful, 1, 106, "Pages"),
"Pages 10-11": TunicItemData(IC.useful, 1, 107, "Pages"), "Pages 10-11": TunicItemData(ItemClassification.useful, 1, 107, "Pages"),
"Pages 12-13": TunicItemData(IC.useful, 1, 108, "Pages"), "Pages 12-13": TunicItemData(ItemClassification.useful, 1, 108, "Pages"),
"Pages 14-15": TunicItemData(IC.useful, 1, 109, "Pages"), "Pages 14-15": TunicItemData(ItemClassification.useful, 1, 109, "Pages"),
"Pages 16-17": TunicItemData(IC.useful, 1, 110, "Pages"), "Pages 16-17": TunicItemData(ItemClassification.useful, 1, 110, "Pages"),
"Pages 18-19": TunicItemData(IC.useful, 1, 111, "Pages"), "Pages 18-19": TunicItemData(ItemClassification.useful, 1, 111, "Pages"),
"Pages 20-21": TunicItemData(IC.useful, 1, 112, "Pages"), "Pages 20-21": TunicItemData(ItemClassification.useful, 1, 112, "Pages"),
"Pages 22-23": TunicItemData(IC.useful, 1, 113, "Pages"), "Pages 22-23": TunicItemData(ItemClassification.useful, 1, 113, "Pages"),
"Pages 24-25 (Prayer)": TunicItemData(IC.progression | IC.useful, 1, 114, "Pages"), "Pages 24-25 (Prayer)": TunicItemData(ItemClassification.progression, 1, 114, "Pages"),
"Pages 26-27": TunicItemData(IC.useful, 1, 115, "Pages"), "Pages 26-27": TunicItemData(ItemClassification.useful, 1, 115, "Pages"),
"Pages 28-29": TunicItemData(IC.useful, 1, 116, "Pages"), "Pages 28-29": TunicItemData(ItemClassification.useful, 1, 116, "Pages"),
"Pages 30-31": TunicItemData(IC.useful, 1, 117, "Pages"), "Pages 30-31": TunicItemData(ItemClassification.useful, 1, 117, "Pages"),
"Pages 32-33": TunicItemData(IC.useful, 1, 118, "Pages"), "Pages 32-33": TunicItemData(ItemClassification.useful, 1, 118, "Pages"),
"Pages 34-35": TunicItemData(IC.useful, 1, 119, "Pages"), "Pages 34-35": TunicItemData(ItemClassification.useful, 1, 119, "Pages"),
"Pages 36-37": TunicItemData(IC.useful, 1, 120, "Pages"), "Pages 36-37": TunicItemData(ItemClassification.useful, 1, 120, "Pages"),
"Pages 38-39": TunicItemData(IC.useful, 1, 121, "Pages"), "Pages 38-39": TunicItemData(ItemClassification.useful, 1, 121, "Pages"),
"Pages 40-41": TunicItemData(IC.useful, 1, 122, "Pages"), "Pages 40-41": TunicItemData(ItemClassification.useful, 1, 122, "Pages"),
"Pages 42-43 (Holy Cross)": TunicItemData(IC.progression | IC.useful, 1, 123, "Pages"), "Pages 42-43 (Holy Cross)": TunicItemData(ItemClassification.progression, 1, 123, "Pages"),
"Pages 44-45": TunicItemData(IC.useful, 1, 124, "Pages"), "Pages 44-45": TunicItemData(ItemClassification.useful, 1, 124, "Pages"),
"Pages 46-47": TunicItemData(IC.useful, 1, 125, "Pages"), "Pages 46-47": TunicItemData(ItemClassification.useful, 1, 125, "Pages"),
"Pages 48-49": TunicItemData(IC.useful, 1, 126, "Pages"), "Pages 48-49": TunicItemData(ItemClassification.useful, 1, 126, "Pages"),
"Pages 50-51": TunicItemData(IC.useful, 1, 127, "Pages"), "Pages 50-51": TunicItemData(ItemClassification.useful, 1, 127, "Pages"),
"Pages 52-53 (Icebolt)": TunicItemData(IC.progression, 1, 128, "Pages"), "Pages 52-53 (Icebolt)": TunicItemData(ItemClassification.progression, 1, 128, "Pages"),
"Pages 54-55": TunicItemData(IC.useful, 1, 129, "Pages"), "Pages 54-55": TunicItemData(ItemClassification.useful, 1, 129, "Pages"),
"Ladders near Weathervane": TunicItemData(IC.progression, 0, 130, "Ladders"), "Ladders near Weathervane": TunicItemData(ItemClassification.progression, 0, 130, "Ladders"),
"Ladders near Overworld Checkpoint": TunicItemData(IC.progression, 0, 131, "Ladders"), "Ladders near Overworld Checkpoint": TunicItemData(ItemClassification.progression, 0, 131, "Ladders"),
"Ladders near Patrol Cave": TunicItemData(IC.progression, 0, 132, "Ladders"), "Ladders near Patrol Cave": TunicItemData(ItemClassification.progression, 0, 132, "Ladders"),
"Ladder near Temple Rafters": TunicItemData(IC.progression, 0, 133, "Ladders"), "Ladder near Temple Rafters": TunicItemData(ItemClassification.progression, 0, 133, "Ladders"),
"Ladders near Dark Tomb": TunicItemData(IC.progression, 0, 134, "Ladders"), "Ladders near Dark Tomb": TunicItemData(ItemClassification.progression, 0, 134, "Ladders"),
"Ladder to Quarry": TunicItemData(IC.progression, 0, 135, "Ladders"), "Ladder to Quarry": TunicItemData(ItemClassification.progression, 0, 135, "Ladders"),
"Ladders to West Bell": TunicItemData(IC.progression, 0, 136, "Ladders"), "Ladders to West Bell": TunicItemData(ItemClassification.progression, 0, 136, "Ladders"),
"Ladders in Overworld Town": TunicItemData(IC.progression, 0, 137, "Ladders"), "Ladders in Overworld Town": TunicItemData(ItemClassification.progression, 0, 137, "Ladders"),
"Ladder to Ruined Atoll": TunicItemData(IC.progression, 0, 138, "Ladders"), "Ladder to Ruined Atoll": TunicItemData(ItemClassification.progression, 0, 138, "Ladders"),
"Ladder to Swamp": TunicItemData(IC.progression, 0, 139, "Ladders"), "Ladder to Swamp": TunicItemData(ItemClassification.progression, 0, 139, "Ladders"),
"Ladders in Well": TunicItemData(IC.progression, 0, 140, "Ladders"), "Ladders in Well": TunicItemData(ItemClassification.progression, 0, 140, "Ladders"),
"Ladder in Dark Tomb": TunicItemData(IC.progression, 0, 141, "Ladders"), "Ladder in Dark Tomb": TunicItemData(ItemClassification.progression, 0, 141, "Ladders"),
"Ladder to East Forest": TunicItemData(IC.progression, 0, 142, "Ladders"), "Ladder to East Forest": TunicItemData(ItemClassification.progression, 0, 142, "Ladders"),
"Ladders to Lower Forest": TunicItemData(IC.progression, 0, 143, "Ladders"), "Ladders to Lower Forest": TunicItemData(ItemClassification.progression, 0, 143, "Ladders"),
"Ladder to Beneath the Vault": TunicItemData(IC.progression, 0, 144, "Ladders"), "Ladder to Beneath the Vault": TunicItemData(ItemClassification.progression, 0, 144, "Ladders"),
"Ladders in Hourglass Cave": TunicItemData(IC.progression, 0, 145, "Ladders"), "Ladders in Hourglass Cave": TunicItemData(ItemClassification.progression, 0, 145, "Ladders"),
"Ladders in South Atoll": TunicItemData(IC.progression, 0, 146, "Ladders"), "Ladders in South Atoll": TunicItemData(ItemClassification.progression, 0, 146, "Ladders"),
"Ladders to Frog's Domain": TunicItemData(IC.progression, 0, 147, "Ladders"), "Ladders to Frog's Domain": TunicItemData(ItemClassification.progression, 0, 147, "Ladders"),
"Ladders in Library": TunicItemData(IC.progression, 0, 148, "Ladders"), "Ladders in Library": TunicItemData(ItemClassification.progression, 0, 148, "Ladders"),
"Ladders in Lower Quarry": TunicItemData(IC.progression, 0, 149, "Ladders"), "Ladders in Lower Quarry": TunicItemData(ItemClassification.progression, 0, 149, "Ladders"),
"Ladders in Swamp": TunicItemData(IC.progression, 0, 150, "Ladders"), "Ladders in Swamp": TunicItemData(ItemClassification.progression, 0, 150, "Ladders"),
} }
# items to be replaced by fool traps # items to be replaced by fool traps
@@ -208,7 +208,7 @@ slot_data_item_names = [
item_name_to_id: Dict[str, int] = {name: item_base_id + data.item_id_offset for name, data in item_table.items()} item_name_to_id: Dict[str, int] = {name: item_base_id + data.item_id_offset for name, data in item_table.items()}
filler_items: List[str] = [name for name, data in item_table.items() if data.classification == IC.filler] filler_items: List[str] = [name for name, data in item_table.items() if data.classification == ItemClassification.filler]
def get_item_group(item_name: str) -> str: def get_item_group(item_name: str) -> str:

View File

@@ -16,7 +16,7 @@ class YachtDiceItem(Item):
item_table = { item_table = {
"Dice": ItemData(16871244000, ItemClassification.progression | ItemClassification.useful), "Dice": ItemData(16871244000, ItemClassification.progression),
"Dice Fragment": ItemData(16871244001, ItemClassification.progression), "Dice Fragment": ItemData(16871244001, ItemClassification.progression),
"Roll": ItemData(16871244002, ItemClassification.progression), "Roll": ItemData(16871244002, ItemClassification.progression),
"Roll Fragment": ItemData(16871244003, ItemClassification.progression), "Roll Fragment": ItemData(16871244003, ItemClassification.progression),
@@ -64,7 +64,7 @@ item_table = {
# These points are included in the logic and might be necessary to progress. # These points are included in the logic and might be necessary to progress.
"1 Point": ItemData(16871244301, ItemClassification.progression_skip_balancing), "1 Point": ItemData(16871244301, ItemClassification.progression_skip_balancing),
"10 Points": ItemData(16871244302, ItemClassification.progression), "10 Points": ItemData(16871244302, ItemClassification.progression),
"100 Points": ItemData(16871244303, ItemClassification.progression | ItemClassification.useful), "100 Points": ItemData(16871244303, ItemClassification.progression),
} }
# item groups for better hinting # item groups for better hinting

View File

@@ -101,15 +101,14 @@ def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mu
return yachtdice_cache[player][tup] return yachtdice_cache[player][tup]
# sort categories because for the step multiplier, you will want low-scoring categories first # sort categories because for the step multiplier, you will want low-scoring categories first
# to avoid errors with order changing when obtaining rolls, we order assuming 4 rolls categories.sort(key=lambda category: category.mean_score(num_dice, num_rolls))
categories.sort(key=lambda category: category.mean_score(num_dice, 4))
# function to add two discrete distribution. # function to add two discrete distribution.
# defaultdict is a dict where you don't need to check if an id is present, you can just use += (lot faster) # defaultdict is a dict where you don't need to check if an id is present, you can just use += (lot faster)
def add_distributions(dist1, dist2): def add_distributions(dist1, dist2):
combined_dist = defaultdict(float) combined_dist = defaultdict(float)
for val2, prob2 in dist2.items(): for val1, prob1 in dist1.items():
for val1, prob1 in dist1.items(): for val2, prob2 in dist2.items():
combined_dist[val1 + val2] += prob1 * prob2 combined_dist[val1 + val2] += prob1 * prob2
return dict(combined_dist) return dict(combined_dist)

File diff suppressed because it is too large Load Diff

View File

@@ -56,7 +56,7 @@ class YachtDiceWorld(World):
item_name_groups = item_groups item_name_groups = item_groups
ap_world_version = "2.1.4" ap_world_version = "2.1.3"
def _get_yachtdice_data(self): def _get_yachtdice_data(self):
return { return {
@@ -468,7 +468,7 @@ class YachtDiceWorld(World):
menu.exits.append(connection) menu.exits.append(connection)
connection.connect(board) connection.connect(board)
self.multiworld.regions += [menu, board] self.multiworld.regions += [menu, board]
def get_filler_item_name(self) -> str: def get_filler_item_name(self) -> str:
return "Good RNG" return "Good RNG"