Compare commits
29 Commits
revert-404
...
NewSoupVi-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1357e32afe | ||
|
|
511bb212ed | ||
|
|
77ee6d73bc | ||
|
|
33daebef57 | ||
|
|
05ec14e23c | ||
|
|
049a8780b5 | ||
|
|
703e3393a6 | ||
|
|
f709d61d04 | ||
|
|
c6d2971d67 | ||
|
|
af14045c3a | ||
|
|
ede59ef5a1 | ||
|
|
63d471514f | ||
|
|
ff297f2951 | ||
|
|
a0f49dd7d9 | ||
|
|
79cec89e24 | ||
|
|
2b0cab82fa | ||
|
|
48822227b5 | ||
|
|
375b5796d9 | ||
|
|
c12ed316cf | ||
|
|
26577b16dc | ||
|
|
af0b5f8cf2 | ||
|
|
618564c60a | ||
|
|
f2ac937d1e | ||
|
|
d4d777b101 | ||
|
|
b772d42df5 | ||
|
|
e8f3aa96da | ||
|
|
2d0bdebaa9 | ||
|
|
ef4d1e77e3 | ||
|
|
f495bf7261 |
@@ -355,6 +355,8 @@ class CommonContext:
|
||||
|
||||
self.item_names = self.NameLookupDict(self, "item")
|
||||
self.location_names = self.NameLookupDict(self, "location")
|
||||
self.versions = {}
|
||||
self.checksums = {}
|
||||
|
||||
self.jsontotextparser = JSONtoTextParser(self)
|
||||
self.rawjsontotextparser = RawJSONtoTextParser(self)
|
||||
@@ -571,26 +573,34 @@ class CommonContext:
|
||||
needed_updates.add(game)
|
||||
continue
|
||||
|
||||
local_version: int = network_data_package["games"].get(game, {}).get("version", 0)
|
||||
local_checksum: typing.Optional[str] = network_data_package["games"].get(game, {}).get("checksum")
|
||||
# no action required if local version is new enough
|
||||
if (not remote_checksum and (remote_version > local_version or remote_version == 0)) \
|
||||
or remote_checksum != local_checksum:
|
||||
cached_game = Utils.load_data_package_for_checksum(game, remote_checksum)
|
||||
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)
|
||||
cached_version: int = self.versions.get(game, 0)
|
||||
cached_checksum: typing.Optional[str] = self.checksums.get(game)
|
||||
# no action required if cached version is new enough
|
||||
if (not remote_checksum and (remote_version > cached_version or remote_version == 0)) \
|
||||
or remote_checksum != cached_checksum:
|
||||
local_version: int = network_data_package["games"].get(game, {}).get("version", 0)
|
||||
local_checksum: typing.Optional[str] = network_data_package["games"].get(game, {}).get("checksum")
|
||||
if ((remote_checksum or remote_version <= local_version and remote_version != 0)
|
||||
and remote_checksum == local_checksum):
|
||||
self.update_game(network_data_package["games"][game], game)
|
||||
else:
|
||||
self.update_game(cached_game, game)
|
||||
cached_game = Utils.load_data_package_for_checksum(game, remote_checksum)
|
||||
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:
|
||||
await self.send_msgs([{"cmd": "GetDataPackage", "games": [game_name]} for game_name in needed_updates])
|
||||
|
||||
def update_game(self, game_package: dict, game: str):
|
||||
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.versions[game] = game_package.get("version", 0)
|
||||
self.checksums[game] = game_package.get("checksum")
|
||||
|
||||
def update_data_package(self, data_package: dict):
|
||||
for game, game_data in data_package["games"].items():
|
||||
|
||||
@@ -35,7 +35,9 @@ from Utils import is_frozen, user_path, local_path, init_logging, open_filename,
|
||||
|
||||
|
||||
def open_host_yaml():
|
||||
file = settings.get_settings().filename
|
||||
s = settings.get_settings()
|
||||
file = s.filename
|
||||
s.save()
|
||||
assert file, "host.yaml missing"
|
||||
if is_linux:
|
||||
exe = which('sensible-editor') or which('gedit') or \
|
||||
|
||||
@@ -185,11 +185,9 @@ class Context:
|
||||
slot_info: typing.Dict[int, NetworkSlot]
|
||||
generator_version = Version(0, 0, 0)
|
||||
checksums: typing.Dict[str, str]
|
||||
item_names: typing.Dict[str, typing.Dict[int, str]] = (
|
||||
collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown item (ID:{code})')))
|
||||
item_names: typing.Dict[str, typing.Dict[int, str]]
|
||||
item_name_groups: typing.Dict[str, typing.Dict[str, typing.Set[str]]]
|
||||
location_names: typing.Dict[str, typing.Dict[int, str]] = (
|
||||
collections.defaultdict(lambda: Utils.KeyedDefaultDict(lambda code: f'Unknown location (ID:{code})')))
|
||||
location_names: typing.Dict[str, typing.Dict[int, 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_location_and_group_names: typing.Dict[str, typing.Set[str]]
|
||||
@@ -198,7 +196,6 @@ class Context:
|
||||
""" each sphere is { player: { location_id, ... } } """
|
||||
logger: logging.Logger
|
||||
|
||||
|
||||
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",
|
||||
remaining_mode: str = "disabled", auto_shutdown: typing.SupportsFloat = 0, compatibility: int = 2,
|
||||
@@ -269,6 +266,10 @@ class Context:
|
||||
self.location_name_groups = {}
|
||||
self.all_item_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._load_game_data()
|
||||
@@ -1153,7 +1154,10 @@ class CommandProcessor(metaclass=CommandMeta):
|
||||
if not raw:
|
||||
return
|
||||
try:
|
||||
command = shlex.split(raw, comments=False)
|
||||
try:
|
||||
command = shlex.split(raw, comments=False)
|
||||
except ValueError: # most likely: "ValueError: No closing quotation"
|
||||
command = raw.split()
|
||||
basecommand = command[0]
|
||||
if basecommand[0] == self.marker:
|
||||
method = self.commands.get(basecommand[1:].lower(), None)
|
||||
|
||||
4
Utils.py
@@ -423,7 +423,7 @@ class RestrictedUnpickler(pickle.Unpickler):
|
||||
if module == "NetUtils" and name in {"NetworkItem", "ClientStatus", "Hint", "SlotType", "NetworkSlot"}:
|
||||
return getattr(self.net_utils_module, name)
|
||||
# Options and Plando are unpickled by WebHost -> Generate
|
||||
if module == "worlds.generic" and name in {"PlandoItem", "PlandoConnection"}:
|
||||
if module == "worlds.generic" and name == "PlandoItem":
|
||||
if not self.generic_properties_module:
|
||||
self.generic_properties_module = importlib.import_module("worlds.generic")
|
||||
return getattr(self.generic_properties_module, name)
|
||||
@@ -434,7 +434,7 @@ class RestrictedUnpickler(pickle.Unpickler):
|
||||
else:
|
||||
mod = importlib.import_module(module)
|
||||
obj = getattr(mod, name)
|
||||
if issubclass(obj, self.options_module.Option):
|
||||
if issubclass(obj, (self.options_module.Option, self.options_module.PlandoConnection)):
|
||||
return obj
|
||||
# Forbid everything else.
|
||||
raise pickle.UnpicklingError(f"global '{module}.{name}' is forbidden")
|
||||
|
||||
@@ -5,6 +5,7 @@ from typing import Any, IO, Dict, Iterator, List, Tuple, Union
|
||||
import jinja2.exceptions
|
||||
from flask import request, redirect, url_for, render_template, Response, session, abort, send_from_directory
|
||||
from pony.orm import count, commit, db_session
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
from worlds.AutoWorld import AutoWorldRegister
|
||||
from . import app, cache
|
||||
@@ -69,14 +70,28 @@ def tutorial_landing():
|
||||
|
||||
@app.route('/faq/<string:lang>/')
|
||||
@cache.cached()
|
||||
def faq(lang):
|
||||
return render_template("faq.html", lang=lang)
|
||||
def faq(lang: str):
|
||||
import markdown
|
||||
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>/')
|
||||
@cache.cached()
|
||||
def terms(lang):
|
||||
return render_template("glossary.html", lang=lang)
|
||||
def glossary(lang: str):
|
||||
import markdown
|
||||
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>')
|
||||
|
||||
@@ -9,3 +9,5 @@ bokeh>=3.1.1; python_version <= '3.8'
|
||||
bokeh>=3.4.3; python_version == '3.9'
|
||||
bokeh>=3.5.2; python_version >= '3.10'
|
||||
markupsafe>=2.1.5
|
||||
Markdown>=3.7
|
||||
mdx-breakless-lists>=1.0.1
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
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>`;
|
||||
});
|
||||
});
|
||||
@@ -1,51 +0,0 @@
|
||||
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>`;
|
||||
});
|
||||
});
|
||||
@@ -288,6 +288,11 @@ const applyPresets = (presetName) => {
|
||||
}
|
||||
});
|
||||
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"
|
||||
|
||||
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 6.2 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 8.6 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 119 KiB |
66
WebHostLib/static/static/branding/header-logo-full.svg
Normal file
@@ -0,0 +1,66 @@
|
||||
<?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>
|
||||
|
After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 3.3 KiB |
@@ -1,66 +1 @@
|
||||
<?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>
|
||||
<?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>
|
||||
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 250 KiB After Width: | Height: | Size: 204 KiB |
|
Before Width: | Height: | Size: 210 KiB After Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 292 KiB After Width: | Height: | Size: 249 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 161 KiB After Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 126 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 258 B |
@@ -1,17 +0,0 @@
|
||||
{% 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 %}
|
||||
@@ -1,17 +0,0 @@
|
||||
{% 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 %}
|
||||
13
WebHostLib/templates/markdown_document.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% 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 %}
|
||||
@@ -268,6 +268,7 @@ 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.
|
||||
|
||||
* [Connect](#Connect)
|
||||
* [ConnectUpdate](#ConnectUpdate)
|
||||
* [Sync](#Sync)
|
||||
* [LocationChecks](#LocationChecks)
|
||||
* [LocationScouts](#LocationScouts)
|
||||
|
||||
98
setup.py
@@ -5,7 +5,6 @@ import platform
|
||||
import shutil
|
||||
import sys
|
||||
import sysconfig
|
||||
import typing
|
||||
import warnings
|
||||
import zipfile
|
||||
import urllib.request
|
||||
@@ -14,14 +13,14 @@ import json
|
||||
import threading
|
||||
import subprocess
|
||||
|
||||
from collections.abc import Iterable
|
||||
from hashlib import sha3_512
|
||||
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
|
||||
requirement = 'cx-Freeze==7.2.0'
|
||||
try:
|
||||
requirement = 'cx-Freeze==7.2.0'
|
||||
import pkg_resources
|
||||
try:
|
||||
pkg_resources.require(requirement)
|
||||
@@ -30,7 +29,7 @@ try:
|
||||
install_cx_freeze = True
|
||||
except ImportError:
|
||||
install_cx_freeze = True
|
||||
pkg_resources = None # type: ignore [assignment]
|
||||
pkg_resources = None # type: ignore[assignment]
|
||||
|
||||
if install_cx_freeze:
|
||||
# check if pip is available
|
||||
@@ -61,7 +60,7 @@ from Cython.Build import cythonize
|
||||
|
||||
|
||||
# On Python < 3.10 LogicMixin is not currently supported.
|
||||
non_apworlds: set = {
|
||||
non_apworlds: Set[str] = {
|
||||
"A Link to the Past",
|
||||
"Adventure",
|
||||
"ArchipIDLE",
|
||||
@@ -84,7 +83,7 @@ non_apworlds: set = {
|
||||
if sys.version_info < (3,10):
|
||||
non_apworlds.add("Hollow Knight")
|
||||
|
||||
def download_SNI():
|
||||
def download_SNI() -> None:
|
||||
print("Updating SNI")
|
||||
machine_to_go = {
|
||||
"x86_64": "amd64",
|
||||
@@ -114,8 +113,8 @@ def download_SNI():
|
||||
if source_url and source_url.endswith(".zip"):
|
||||
with urllib.request.urlopen(source_url) as download:
|
||||
with zipfile.ZipFile(io.BytesIO(download.read()), "r") as zf:
|
||||
for member in zf.infolist():
|
||||
zf.extract(member, path="SNI")
|
||||
for zf_member in zf.infolist():
|
||||
zf.extract(zf_member, path="SNI")
|
||||
print(f"Downloaded SNI from {source_url}")
|
||||
|
||||
elif source_url and (source_url.endswith(".tar.xz") or source_url.endswith(".tar.gz")):
|
||||
@@ -129,11 +128,13 @@ def download_SNI():
|
||||
raise ValueError(f"Unexpected file '{member.name}' in {source_url}")
|
||||
elif member.isdir() and not sni_dir:
|
||||
sni_dir = member.name
|
||||
elif member.isfile() and not sni_dir or not member.name.startswith(sni_dir):
|
||||
elif member.isfile() and not sni_dir or sni_dir and not member.name.startswith(sni_dir):
|
||||
raise ValueError(f"Expected folder before '{member.name}' in {source_url}")
|
||||
elif member.isfile() and sni_dir:
|
||||
tf.extract(member)
|
||||
# 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)
|
||||
os.rename(sni_dir, "SNI")
|
||||
print(f"Downloaded SNI from {source_url}")
|
||||
@@ -145,7 +146,7 @@ def download_SNI():
|
||||
print(f"No SNI found for system spec {platform_name} {machine_name}")
|
||||
|
||||
|
||||
signtool: typing.Optional[str]
|
||||
signtool: Optional[str]
|
||||
if os.path.exists("X:/pw.txt"):
|
||||
print("Using signtool")
|
||||
with open("X:/pw.txt", encoding="utf-8-sig") as f:
|
||||
@@ -197,13 +198,13 @@ extra_data = ["LICENSE", "data", "EnemizerCLI", "SNI"]
|
||||
extra_libs = ["libssl.so", "libcrypto.so"] if is_linux else []
|
||||
|
||||
|
||||
def remove_sprites_from_folder(folder):
|
||||
def remove_sprites_from_folder(folder: Path) -> None:
|
||||
for file in os.listdir(folder):
|
||||
if file != ".gitignore":
|
||||
os.remove(folder / file)
|
||||
|
||||
|
||||
def _threaded_hash(filepath):
|
||||
def _threaded_hash(filepath: Union[str, Path]) -> str:
|
||||
hasher = sha3_512()
|
||||
hasher.update(open(filepath, "rb").read())
|
||||
return base64.b85encode(hasher.digest()).decode()
|
||||
@@ -217,11 +218,11 @@ class BuildCommand(setuptools.command.build.build):
|
||||
yes: bool
|
||||
last_yes: bool = False # used by sub commands of build
|
||||
|
||||
def initialize_options(self):
|
||||
def initialize_options(self) -> None:
|
||||
super().initialize_options()
|
||||
type(self).last_yes = self.yes = False
|
||||
|
||||
def finalize_options(self):
|
||||
def finalize_options(self) -> None:
|
||||
super().finalize_options()
|
||||
type(self).last_yes = self.yes
|
||||
|
||||
@@ -233,27 +234,27 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
('extra-data=', None, 'Additional files to add.'),
|
||||
]
|
||||
yes: bool
|
||||
extra_data: Iterable # [any] not available in 3.8
|
||||
extra_libs: Iterable # work around broken include_files
|
||||
extra_data: Iterable[str]
|
||||
extra_libs: Iterable[str] # work around broken include_files
|
||||
|
||||
buildfolder: Path
|
||||
libfolder: Path
|
||||
library: Path
|
||||
buildtime: datetime.datetime
|
||||
|
||||
def initialize_options(self):
|
||||
def initialize_options(self) -> None:
|
||||
super().initialize_options()
|
||||
self.yes = BuildCommand.last_yes
|
||||
self.extra_data = []
|
||||
self.extra_libs = []
|
||||
|
||||
def finalize_options(self):
|
||||
def finalize_options(self) -> None:
|
||||
super().finalize_options()
|
||||
self.buildfolder = self.build_exe
|
||||
self.libfolder = Path(self.buildfolder, "lib")
|
||||
self.library = Path(self.libfolder, "library.zip")
|
||||
|
||||
def installfile(self, path, subpath=None, keep_content: bool = False):
|
||||
def installfile(self, path: Path, subpath: Optional[Union[str, Path]] = None, keep_content: bool = False) -> None:
|
||||
folder = self.buildfolder
|
||||
if subpath:
|
||||
folder /= subpath
|
||||
@@ -268,7 +269,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
else:
|
||||
print('Warning,', path, 'not found')
|
||||
|
||||
def create_manifest(self, create_hashes=False):
|
||||
def create_manifest(self, create_hashes: bool = False) -> None:
|
||||
# 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.
|
||||
hashes = {}
|
||||
@@ -290,7 +291,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
json.dump(manifest, open(manifestpath, "wt"), indent=4)
|
||||
print("Created Manifest")
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
# start downloading sni asap
|
||||
sni_thread = threading.Thread(target=download_SNI, name="SNI Downloader")
|
||||
sni_thread.start()
|
||||
@@ -341,7 +342,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
|
||||
# post build steps
|
||||
if is_windows: # kivy_deps is win32 only, linux picks them up automatically
|
||||
from kivy_deps import sdl2, glew
|
||||
from kivy_deps import sdl2, glew # type: ignore
|
||||
for folder in sdl2.dep_bins + glew.dep_bins:
|
||||
shutil.copytree(folder, self.libfolder, dirs_exist_ok=True)
|
||||
print(f"copying {folder} -> {self.libfolder}")
|
||||
@@ -362,7 +363,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
self.installfile(Path(data))
|
||||
|
||||
# kivi data files
|
||||
import kivy
|
||||
import kivy # type: ignore[import-untyped]
|
||||
shutil.copytree(os.path.join(os.path.dirname(kivy.__file__), "data"),
|
||||
self.buildfolder / "data",
|
||||
dirs_exist_ok=True)
|
||||
@@ -372,7 +373,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
from worlds.AutoWorld import AutoWorldRegister
|
||||
assert not non_apworlds - set(AutoWorldRegister.world_types), \
|
||||
f"Unknown world {non_apworlds - set(AutoWorldRegister.world_types)} designated for .apworld"
|
||||
folders_to_remove: typing.List[str] = []
|
||||
folders_to_remove: List[str] = []
|
||||
disabled_worlds_folder = "worlds_disabled"
|
||||
for entry in os.listdir(disabled_worlds_folder):
|
||||
if os.path.isdir(os.path.join(disabled_worlds_folder, entry)):
|
||||
@@ -393,7 +394,7 @@ class BuildExeCommand(cx_Freeze.command.build_exe.build_exe):
|
||||
shutil.rmtree(world_directory)
|
||||
shutil.copyfile("meta.yaml", self.buildfolder / "Players" / "Templates" / "meta.yaml")
|
||||
try:
|
||||
from maseya import z3pr
|
||||
from maseya import z3pr # type: ignore[import-untyped]
|
||||
except ImportError:
|
||||
print("Maseya Palette Shuffle not found, skipping data files.")
|
||||
else:
|
||||
@@ -444,16 +445,16 @@ class AppImageCommand(setuptools.Command):
|
||||
("app-exec=", None, "The application to run inside the image."),
|
||||
("yes", "y", 'Answer "yes" to all questions.'),
|
||||
]
|
||||
build_folder: typing.Optional[Path]
|
||||
dist_file: typing.Optional[Path]
|
||||
app_dir: typing.Optional[Path]
|
||||
build_folder: Optional[Path]
|
||||
dist_file: Optional[Path]
|
||||
app_dir: Optional[Path]
|
||||
app_name: str
|
||||
app_exec: typing.Optional[Path]
|
||||
app_icon: typing.Optional[Path] # source file
|
||||
app_exec: Optional[Path]
|
||||
app_icon: Optional[Path] # source file
|
||||
app_id: str # lower case name, used for icon and .desktop
|
||||
yes: bool
|
||||
|
||||
def write_desktop(self):
|
||||
def write_desktop(self) -> None:
|
||||
assert self.app_dir, "Invalid app_dir"
|
||||
desktop_filename = self.app_dir / f"{self.app_id}.desktop"
|
||||
with open(desktop_filename, 'w', encoding="utf-8") as f:
|
||||
@@ -468,7 +469,7 @@ class AppImageCommand(setuptools.Command):
|
||||
)))
|
||||
desktop_filename.chmod(0o755)
|
||||
|
||||
def write_launcher(self, default_exe: Path):
|
||||
def write_launcher(self, default_exe: Path) -> None:
|
||||
assert self.app_dir, "Invalid app_dir"
|
||||
launcher_filename = self.app_dir / "AppRun"
|
||||
with open(launcher_filename, 'w', encoding="utf-8") as f:
|
||||
@@ -491,7 +492,7 @@ $APPDIR/$exe "$@"
|
||||
""")
|
||||
launcher_filename.chmod(0o755)
|
||||
|
||||
def install_icon(self, src: Path, name: typing.Optional[str] = None, symlink: typing.Optional[Path] = None):
|
||||
def install_icon(self, src: Path, name: Optional[str] = None, symlink: Optional[Path] = None) -> None:
|
||||
assert self.app_dir, "Invalid app_dir"
|
||||
try:
|
||||
from PIL import Image
|
||||
@@ -513,7 +514,8 @@ $APPDIR/$exe "$@"
|
||||
if symlink:
|
||||
symlink.symlink_to(dest_file.relative_to(symlink.parent))
|
||||
|
||||
def initialize_options(self):
|
||||
def initialize_options(self) -> None:
|
||||
assert self.distribution.metadata.name
|
||||
self.build_folder = None
|
||||
self.app_dir = None
|
||||
self.app_name = self.distribution.metadata.name
|
||||
@@ -527,17 +529,22 @@ $APPDIR/$exe "$@"
|
||||
))
|
||||
self.yes = False
|
||||
|
||||
def finalize_options(self):
|
||||
def finalize_options(self) -> None:
|
||||
assert self.build_folder
|
||||
if not self.app_dir:
|
||||
self.app_dir = self.build_folder.parent / "AppDir"
|
||||
self.app_id = self.app_name.lower()
|
||||
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
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)
|
||||
if self.app_dir.is_dir():
|
||||
shutil.rmtree(self.app_dir)
|
||||
self.app_dir.mkdir(parents=True)
|
||||
opt_dir = self.app_dir / "opt" / self.distribution.metadata.name
|
||||
opt_dir = self.app_dir / "opt" / self.app_name
|
||||
shutil.copytree(self.build_folder, opt_dir)
|
||||
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)
|
||||
@@ -548,7 +555,7 @@ $APPDIR/$exe "$@"
|
||||
subprocess.call(f'ARCH={build_arch} ./appimagetool -n "{self.app_dir}" "{self.dist_file}"', shell=True)
|
||||
|
||||
|
||||
def find_libs(*args: str) -> typing.Sequence[typing.Tuple[str, str]]:
|
||||
def find_libs(*args: str) -> Sequence[Tuple[str, str]]:
|
||||
"""Try to find system libraries to be included."""
|
||||
if not args:
|
||||
return []
|
||||
@@ -556,7 +563,7 @@ def find_libs(*args: str) -> typing.Sequence[typing.Tuple[str, str]]:
|
||||
arch = build_arch.replace('_', '-')
|
||||
libc = 'libc6' # we currently don't support musl
|
||||
|
||||
def parse(line):
|
||||
def parse(line: str) -> Tuple[Tuple[str, str, str], str]:
|
||||
lib, path = line.strip().split(' => ')
|
||||
lib, typ = lib.split(' ', 1)
|
||||
for test_arch in ('x86-64', 'i386', 'aarch64'):
|
||||
@@ -577,26 +584,29 @@ def find_libs(*args: str) -> typing.Sequence[typing.Tuple[str, str]]:
|
||||
ldconfig = shutil.which("ldconfig")
|
||||
assert ldconfig, "Make sure ldconfig is in PATH"
|
||||
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)
|
||||
}
|
||||
|
||||
def find_lib(lib, arch, libc):
|
||||
for k, v in find_libs.cache.items():
|
||||
def find_lib(lib: str, arch: str, libc: str) -> Optional[str]:
|
||||
cache: Dict[Tuple[str, str, str], str] = getattr(find_libs, "cache")
|
||||
for k, v in cache.items():
|
||||
if k == (lib, arch, libc):
|
||||
return v
|
||||
for k, v, in find_libs.cache.items():
|
||||
for k, v, in cache.items():
|
||||
if k[0].startswith(lib) and k[1] == arch and k[2] == libc:
|
||||
return v
|
||||
return None
|
||||
|
||||
res = []
|
||||
res: List[Tuple[str, str]] = []
|
||||
for arg in args:
|
||||
# try exact match, empty libc, empty arch, empty arch and libc
|
||||
file = find_lib(arg, arch, libc)
|
||||
file = file or find_lib(arg, arch, '')
|
||||
file = file or find_lib(arg, '', libc)
|
||||
file = file or find_lib(arg, '', '')
|
||||
if not file:
|
||||
raise ValueError(f"Could not find lib {arg}")
|
||||
# resolve symlinks
|
||||
for n in range(0, 5):
|
||||
res.append((file, os.path.join('lib', os.path.basename(file))))
|
||||
|
||||
@@ -59,3 +59,12 @@ class TestOptions(unittest.TestCase):
|
||||
item_links = {1: ItemLinks.from_any(item_link_group), 2: ItemLinks.from_any(item_link_group)}
|
||||
for link in item_links.values():
|
||||
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))
|
||||
|
||||
@@ -10,7 +10,7 @@ from dataclasses import make_dataclass
|
||||
from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping, Optional, Set, TextIO, Tuple,
|
||||
TYPE_CHECKING, Type, Union)
|
||||
|
||||
from Options import item_and_loc_options, OptionGroup, PerGameCommonOptions
|
||||
from Options import item_and_loc_options, ItemsAccessibility, OptionGroup, PerGameCommonOptions
|
||||
from BaseClasses import CollectionState
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -480,6 +480,7 @@ class World(metaclass=AutoWorldRegister):
|
||||
group = cls(multiworld, new_player_id)
|
||||
group.options = cls.options_dataclass(**{option_key: option.from_any(option.default)
|
||||
for option_key, option in cls.options_dataclass.type_hints.items()})
|
||||
group.options.accessibility = ItemsAccessibility(ItemsAccessibility.option_items)
|
||||
|
||||
return group
|
||||
|
||||
|
||||
@@ -738,9 +738,7 @@ class AquariaRegions:
|
||||
self.__connect_regions("Sun Temple left area", "Veil left of sun temple",
|
||||
self.sun_temple_l, self.veil_tr_l)
|
||||
self.__connect_regions("Sun Temple left area", "Sun Temple before boss area",
|
||||
self.sun_temple_l, self.sun_temple_boss_path,
|
||||
lambda state: _has_light(state, self.player) or
|
||||
_has_sun_crystal(state, self.player))
|
||||
self.sun_temple_l, self.sun_temple_boss_path)
|
||||
self.__connect_regions("Sun Temple before boss area", "Sun Temple boss area",
|
||||
self.sun_temple_boss_path, self.sun_temple_boss,
|
||||
lambda state: _has_energy_attack_item(state, self.player))
|
||||
@@ -775,14 +773,11 @@ class AquariaRegions:
|
||||
self.abyss_l, self.king_jellyfish_cave,
|
||||
lambda state: (_has_energy_form(state, self.player) and
|
||||
_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.abyss_l, self.abyss_r)
|
||||
self.__connect_one_way_regions("Abyss right area", "Abyss right area, transturtle",
|
||||
self.__connect_regions("Abyss right area", "Abyss right area, 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.abyss_r, self.whale,
|
||||
lambda state: _has_spirit_form(state, self.player) and
|
||||
@@ -1092,12 +1087,10 @@ class AquariaRegions:
|
||||
lambda state: _has_light(state, 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))
|
||||
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),
|
||||
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:
|
||||
add_rule(self.multiworld.get_location("Mithalas Cathedral, Mithalan Dress", self.player),
|
||||
@@ -1151,6 +1144,10 @@ class AquariaRegions:
|
||||
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),
|
||||
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:
|
||||
self.multiworld.get_location("Energy Temple boss area, Fallen God Tooth",
|
||||
|
||||
@@ -130,12 +130,13 @@ class AquariaWorld(World):
|
||||
|
||||
return result
|
||||
|
||||
def __pre_fill_item(self, item_name: str, location_name: str, precollected) -> None:
|
||||
def __pre_fill_item(self, item_name: str, location_name: str, precollected,
|
||||
itemClassification: ItemClassification = ItemClassification.useful) -> None:
|
||||
"""Pre-assign an item to a location"""
|
||||
if item_name not in precollected:
|
||||
self.exclude.append(item_name)
|
||||
data = item_table[item_name]
|
||||
item = AquariaItem(item_name, ItemClassification.useful, data.id, self.player)
|
||||
item = AquariaItem(item_name, itemClassification, data.id, self.player)
|
||||
self.multiworld.get_location(location_name, self.player).place_locked_item(item)
|
||||
|
||||
def get_filler_item_name(self):
|
||||
@@ -164,7 +165,8 @@ class AquariaWorld(World):
|
||||
self.__pre_fill_item("Transturtle Abyss right", "Abyss right 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
|
||||
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)
|
||||
for name, data in item_table.items():
|
||||
if name not in self.exclude:
|
||||
@@ -212,4 +214,8 @@ class AquariaWorld(World):
|
||||
"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_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,
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
## 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)
|
||||
- [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
|
||||
|
||||
@@ -113,3 +115,16 @@ sure that your executable has executable permission:
|
||||
```bash
|
||||
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.
|
||||
|
||||
@@ -2,9 +2,14 @@
|
||||
|
||||
## Logiciels nécessaires
|
||||
|
||||
- Le jeu Aquaria original (trouvable sur la majorité des sites de ventes de jeux vidéo en ligne)
|
||||
- Le client Randomizer d'Aquaria [Aquaria randomizer](https://github.com/tioui/Aquaria_Randomizer/releases)
|
||||
- Une copie du jeu Aquaria non-modifiée (disponible sur la majorité des sites de ventes de jeux vidéos en ligne)
|
||||
- Le client du Randomizer d'Aquaria [Aquaria randomizer]
|
||||
(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)
|
||||
- [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
|
||||
|
||||
@@ -116,3 +121,15 @@ pour vous assurer que votre fichier est exécutable:
|
||||
```bash
|
||||
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.
|
||||
|
||||
@@ -77,7 +77,7 @@ option_docstrings = {
|
||||
"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.",
|
||||
"PreciseMovement": "Places skips into logic which require extremely precise player movement, possibly without "
|
||||
"movement skills such as\n dash or hook.",
|
||||
"movement skills such as\n dash or claw.",
|
||||
"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 "
|
||||
"background objects.",
|
||||
|
||||
@@ -220,6 +220,8 @@ class MessengerRules:
|
||||
}
|
||||
|
||||
self.location_rules = {
|
||||
# hq
|
||||
"Money Wrench": self.can_shop,
|
||||
# ninja village
|
||||
"Ninja Village Seal - Tree House":
|
||||
self.has_dart,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import Dict
|
||||
|
||||
from BaseClasses import CollectionState
|
||||
from . import MessengerTestBase
|
||||
from ..shop import SHOP_ITEMS, FIGURINES
|
||||
|
||||
@@ -89,3 +90,15 @@ class PlandoTest(MessengerTestBase):
|
||||
|
||||
self.assertTrue(loc in 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))
|
||||
|
||||
@@ -29,7 +29,7 @@ def shuffle_structures(self: "MinecraftWorld") -> None:
|
||||
|
||||
# Connect plando structures first
|
||||
if self.options.plando_connections:
|
||||
for conn in self.plando_connections:
|
||||
for conn in self.options.plando_connections:
|
||||
set_pair(conn.entrance, conn.exit)
|
||||
|
||||
# The algorithm tries to place the most restrictive structures first. This algorithm always works on the
|
||||
|
||||
@@ -100,13 +100,13 @@ item_table: Dict[str, ItemData] = {
|
||||
"Wand (Tier 5)": ItemData(110010, "Wands", ItemClassification.useful, 1),
|
||||
"Wand (Tier 6)": ItemData(110011, "Wands", ItemClassification.useful, 1),
|
||||
"Kantele": ItemData(110012, "Wands", ItemClassification.useful),
|
||||
"Fire Immunity Perk": ItemData(110013, "Perks", ItemClassification.progression, 1),
|
||||
"Toxic Immunity Perk": ItemData(110014, "Perks", ItemClassification.progression, 1),
|
||||
"Explosion Immunity Perk": ItemData(110015, "Perks", ItemClassification.progression, 1),
|
||||
"Melee Immunity Perk": ItemData(110016, "Perks", ItemClassification.progression, 1),
|
||||
"Electricity Immunity Perk": ItemData(110017, "Perks", ItemClassification.progression, 1),
|
||||
"Tinker with Wands Everywhere Perk": ItemData(110018, "Perks", ItemClassification.progression, 1),
|
||||
"All-Seeing Eye Perk": ItemData(110019, "Perks", ItemClassification.progression, 1),
|
||||
"Fire Immunity Perk": ItemData(110013, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Toxic Immunity Perk": ItemData(110014, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Explosion Immunity Perk": ItemData(110015, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Melee Immunity Perk": ItemData(110016, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Electricity Immunity Perk": ItemData(110017, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Tinker with Wands Everywhere Perk": ItemData(110018, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"All-Seeing Eye Perk": ItemData(110019, "Perks", ItemClassification.progression | ItemClassification.useful, 1),
|
||||
"Spatial Awareness Perk": ItemData(110020, "Perks", ItemClassification.progression),
|
||||
"Extra Life Perk": ItemData(110021, "Repeatable Perks", ItemClassification.useful, 1),
|
||||
"Orb": ItemData(110022, "Orbs", ItemClassification.progression_skip_balancing),
|
||||
|
||||
@@ -184,6 +184,10 @@ class OOTWorld(World):
|
||||
"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 (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()
|
||||
|
||||
@@ -177,7 +177,7 @@ class PokemonEmeraldWorld(World):
|
||||
for species_name in self.options.trainer_party_blacklist.value
|
||||
if species_name != "_Legendaries"
|
||||
}
|
||||
if "_Legendaries" in self.options.starter_blacklist.value:
|
||||
if "_Legendaries" in self.options.trainer_party_blacklist.value:
|
||||
self.blacklisted_opponent_pokemon |= LEGENDARY_POKEMON
|
||||
|
||||
# In race mode we don't patch any item location information into the ROM
|
||||
|
||||
@@ -117,6 +117,11 @@ 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()}
|
||||
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):
|
||||
game = "Pokemon Emerald"
|
||||
@@ -414,13 +419,17 @@ class PokemonEmeraldClient(BizHawkClient):
|
||||
|
||||
read_result = await bizhawk.guarded_read(
|
||||
ctx.bizhawk_ctx,
|
||||
[(sb1_address + 0x4, 2, "System Bus")],
|
||||
[guards["SAVE BLOCK 1"]]
|
||||
[
|
||||
(sb1_address + 0x4, 2, "System Bus"), # Current map
|
||||
(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
|
||||
return
|
||||
|
||||
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:
|
||||
self.current_map = current_map
|
||||
await ctx.send_msgs([{
|
||||
@@ -429,6 +438,7 @@ class PokemonEmeraldClient(BizHawkClient):
|
||||
"data": {
|
||||
"type": "MapUpdate",
|
||||
"mapId": current_map,
|
||||
**({"tide": shoal_cave} if current_map in SHOAL_CAVE_MAPS else {}),
|
||||
},
|
||||
}])
|
||||
|
||||
|
||||
@@ -417,13 +417,16 @@ class HiddenTraps(Traps):
|
||||
"""List of traps that may be in the item pool to find"""
|
||||
visibility = Visibility.none
|
||||
|
||||
class OptionsHider:
|
||||
@classmethod
|
||||
def hidden(cls, option: Type[Option[Any]]) -> Type[Option]:
|
||||
new_option = AssembleOptions(f"{option}Hidden", option.__bases__, vars(option).copy())
|
||||
new_option.visibility = Visibility.none
|
||||
new_option.__doc__ = option.__doc__
|
||||
return new_option
|
||||
class HiddenDeathLink(DeathLink):
|
||||
"""When you die, everyone who enabled death link dies. Of course, the reverse is true too."""
|
||||
visibility = Visibility.none
|
||||
|
||||
def hidden(option: Type[Option[Any]]) -> Type[Option]:
|
||||
new_option = AssembleOptions(f"{option.__name__}Hidden", option.__bases__, vars(option).copy())
|
||||
new_option.visibility = Visibility.none
|
||||
new_option.__doc__ = option.__doc__
|
||||
globals()[f"{option.__name__}Hidden"] = new_option
|
||||
return new_option
|
||||
|
||||
class HasReplacedCamelCase(Toggle):
|
||||
"""For internal use will display a warning message if true"""
|
||||
@@ -431,41 +434,41 @@ class HasReplacedCamelCase(Toggle):
|
||||
|
||||
@dataclass
|
||||
class BackwardsCompatiableTimespinnerOptions(TimespinnerOptions):
|
||||
StartWithJewelryBox: OptionsHider.hidden(StartWithJewelryBox) # type: ignore
|
||||
DownloadableItems: OptionsHider.hidden(DownloadableItems) # type: ignore
|
||||
EyeSpy: OptionsHider.hidden(EyeSpy) # type: ignore
|
||||
StartWithMeyef: OptionsHider.hidden(StartWithMeyef) # type: ignore
|
||||
QuickSeed: OptionsHider.hidden(QuickSeed) # type: ignore
|
||||
SpecificKeycards: OptionsHider.hidden(SpecificKeycards) # type: ignore
|
||||
Inverted: OptionsHider.hidden(Inverted) # type: ignore
|
||||
GyreArchives: OptionsHider.hidden(GyreArchives) # type: ignore
|
||||
Cantoran: OptionsHider.hidden(Cantoran) # type: ignore
|
||||
LoreChecks: OptionsHider.hidden(LoreChecks) # type: ignore
|
||||
BossRando: OptionsHider.hidden(BossRando) # type: ignore
|
||||
DamageRando: OptionsHider.hidden(DamageRando) # type: ignore
|
||||
StartWithJewelryBox: hidden(StartWithJewelryBox) # type: ignore
|
||||
DownloadableItems: hidden(DownloadableItems) # type: ignore
|
||||
EyeSpy: hidden(EyeSpy) # type: ignore
|
||||
StartWithMeyef: hidden(StartWithMeyef) # type: ignore
|
||||
QuickSeed: hidden(QuickSeed) # type: ignore
|
||||
SpecificKeycards: hidden(SpecificKeycards) # type: ignore
|
||||
Inverted: hidden(Inverted) # type: ignore
|
||||
GyreArchives: hidden(GyreArchives) # type: ignore
|
||||
Cantoran: hidden(Cantoran) # type: ignore
|
||||
LoreChecks: hidden(LoreChecks) # type: ignore
|
||||
BossRando: hidden(BossRando) # type: ignore
|
||||
DamageRando: hidden(DamageRando) # type: ignore
|
||||
DamageRandoOverrides: HiddenDamageRandoOverrides
|
||||
HpCap: OptionsHider.hidden(HpCap) # type: ignore
|
||||
LevelCap: OptionsHider.hidden(LevelCap) # type: ignore
|
||||
ExtraEarringsXP: OptionsHider.hidden(ExtraEarringsXP) # type: ignore
|
||||
BossHealing: OptionsHider.hidden(BossHealing) # type: ignore
|
||||
ShopFill: OptionsHider.hidden(ShopFill) # type: ignore
|
||||
ShopWarpShards: OptionsHider.hidden(ShopWarpShards) # type: ignore
|
||||
ShopMultiplier: OptionsHider.hidden(ShopMultiplier) # type: ignore
|
||||
LootPool: OptionsHider.hidden(LootPool) # type: ignore
|
||||
DropRateCategory: OptionsHider.hidden(DropRateCategory) # type: ignore
|
||||
FixedDropRate: OptionsHider.hidden(FixedDropRate) # type: ignore
|
||||
LootTierDistro: OptionsHider.hidden(LootTierDistro) # type: ignore
|
||||
ShowBestiary: OptionsHider.hidden(ShowBestiary) # type: ignore
|
||||
ShowDrops: OptionsHider.hidden(ShowDrops) # type: ignore
|
||||
EnterSandman: OptionsHider.hidden(EnterSandman) # type: ignore
|
||||
DadPercent: OptionsHider.hidden(DadPercent) # type: ignore
|
||||
RisingTides: OptionsHider.hidden(RisingTides) # type: ignore
|
||||
HpCap: hidden(HpCap) # type: ignore
|
||||
LevelCap: hidden(LevelCap) # type: ignore
|
||||
ExtraEarringsXP: hidden(ExtraEarringsXP) # type: ignore
|
||||
BossHealing: hidden(BossHealing) # type: ignore
|
||||
ShopFill: hidden(ShopFill) # type: ignore
|
||||
ShopWarpShards: hidden(ShopWarpShards) # type: ignore
|
||||
ShopMultiplier: hidden(ShopMultiplier) # type: ignore
|
||||
LootPool: hidden(LootPool) # type: ignore
|
||||
DropRateCategory: hidden(DropRateCategory) # type: ignore
|
||||
FixedDropRate: hidden(FixedDropRate) # type: ignore
|
||||
LootTierDistro: hidden(LootTierDistro) # type: ignore
|
||||
ShowBestiary: hidden(ShowBestiary) # type: ignore
|
||||
ShowDrops: hidden(ShowDrops) # type: ignore
|
||||
EnterSandman: hidden(EnterSandman) # type: ignore
|
||||
DadPercent: hidden(DadPercent) # type: ignore
|
||||
RisingTides: hidden(RisingTides) # type: ignore
|
||||
RisingTidesOverrides: HiddenRisingTidesOverrides
|
||||
UnchainedKeys: OptionsHider.hidden(UnchainedKeys) # type: ignore
|
||||
PresentAccessWithWheelAndSpindle: OptionsHider.hidden(PresentAccessWithWheelAndSpindle) # type: ignore
|
||||
TrapChance: OptionsHider.hidden(TrapChance) # type: ignore
|
||||
UnchainedKeys: hidden(UnchainedKeys) # type: ignore
|
||||
PresentAccessWithWheelAndSpindle: hidden(PresentAccessWithWheelAndSpindle) # type: ignore
|
||||
TrapChance: hidden(TrapChance) # type: ignore
|
||||
Traps: HiddenTraps # type: ignore
|
||||
DeathLink: OptionsHider.hidden(DeathLink) # type: ignore
|
||||
DeathLink: HiddenDeathLink # type: ignore
|
||||
has_replaced_options: HasReplacedCamelCase
|
||||
|
||||
def handle_backward_compatibility(self) -> None:
|
||||
|
||||
@@ -190,7 +190,7 @@ class TimespinnerWorld(World):
|
||||
|
||||
if self.options.has_replaced_options:
|
||||
warning = \
|
||||
f"NOTICE: Timespinner options for player '{self.player_name}' where renamed from PasCalCase to snake_case, " \
|
||||
f"NOTICE: Timespinner options for player '{self.player_name}' were renamed from PascalCase to snake_case, " \
|
||||
"please update your yaml"
|
||||
|
||||
spoiler_handle.write("\n")
|
||||
|
||||
@@ -274,6 +274,12 @@ class TunicWorld(World):
|
||||
if items_to_create[page] > 0:
|
||||
tunic_items.append(self.create_item(page, ItemClassification.useful))
|
||||
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:
|
||||
tunic_items.append(self.create_item("Scavenger Mask", ItemClassification.useful))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
from itertools import groupby
|
||||
from typing import Dict, List, Set, NamedTuple
|
||||
from BaseClasses import ItemClassification
|
||||
from BaseClasses import ItemClassification as IC
|
||||
|
||||
|
||||
class TunicItemData(NamedTuple):
|
||||
classification: ItemClassification
|
||||
classification: IC
|
||||
quantity_in_item_pool: int
|
||||
item_id_offset: int
|
||||
item_group: str = ""
|
||||
@@ -13,157 +13,157 @@ class TunicItemData(NamedTuple):
|
||||
item_base_id = 509342400
|
||||
|
||||
item_table: Dict[str, TunicItemData] = {
|
||||
"Firecracker x2": TunicItemData(ItemClassification.filler, 3, 0, "Bombs"),
|
||||
"Firecracker x3": TunicItemData(ItemClassification.filler, 3, 1, "Bombs"),
|
||||
"Firecracker x4": TunicItemData(ItemClassification.filler, 3, 2, "Bombs"),
|
||||
"Firecracker x5": TunicItemData(ItemClassification.filler, 1, 3, "Bombs"),
|
||||
"Firecracker x6": TunicItemData(ItemClassification.filler, 2, 4, "Bombs"),
|
||||
"Fire Bomb x2": TunicItemData(ItemClassification.filler, 2, 5, "Bombs"),
|
||||
"Fire Bomb x3": TunicItemData(ItemClassification.filler, 1, 6, "Bombs"),
|
||||
"Ice Bomb x2": TunicItemData(ItemClassification.filler, 2, 7, "Bombs"),
|
||||
"Ice Bomb x3": TunicItemData(ItemClassification.filler, 2, 8, "Bombs"),
|
||||
"Ice Bomb x5": TunicItemData(ItemClassification.filler, 1, 9, "Bombs"),
|
||||
"Lure": TunicItemData(ItemClassification.filler, 4, 10, "Consumables"),
|
||||
"Lure x2": TunicItemData(ItemClassification.filler, 1, 11, "Consumables"),
|
||||
"Pepper x2": TunicItemData(ItemClassification.filler, 4, 12, "Consumables"),
|
||||
"Ivy x3": TunicItemData(ItemClassification.filler, 2, 13, "Consumables"),
|
||||
"Effigy": TunicItemData(ItemClassification.useful, 12, 14, "Money"),
|
||||
"HP Berry": TunicItemData(ItemClassification.filler, 2, 15, "Consumables"),
|
||||
"HP Berry x2": TunicItemData(ItemClassification.filler, 4, 16, "Consumables"),
|
||||
"HP Berry x3": TunicItemData(ItemClassification.filler, 2, 17, "Consumables"),
|
||||
"MP Berry": TunicItemData(ItemClassification.filler, 4, 18, "Consumables"),
|
||||
"MP Berry x2": TunicItemData(ItemClassification.filler, 2, 19, "Consumables"),
|
||||
"MP Berry x3": TunicItemData(ItemClassification.filler, 7, 20, "Consumables"),
|
||||
"Fairy": TunicItemData(ItemClassification.progression, 20, 21),
|
||||
"Stick": TunicItemData(ItemClassification.progression, 1, 22, "Weapons"),
|
||||
"Sword": TunicItemData(ItemClassification.progression, 3, 23, "Weapons"),
|
||||
"Sword Upgrade": TunicItemData(ItemClassification.progression, 4, 24, "Weapons"),
|
||||
"Magic Wand": TunicItemData(ItemClassification.progression, 1, 25, "Weapons"),
|
||||
"Magic Dagger": TunicItemData(ItemClassification.progression, 1, 26),
|
||||
"Magic Orb": TunicItemData(ItemClassification.progression, 1, 27),
|
||||
"Hero's Laurels": TunicItemData(ItemClassification.progression, 1, 28),
|
||||
"Lantern": TunicItemData(ItemClassification.progression, 1, 29),
|
||||
"Gun": TunicItemData(ItemClassification.progression, 1, 30, "Weapons"),
|
||||
"Shield": TunicItemData(ItemClassification.useful, 1, 31),
|
||||
"Dath Stone": TunicItemData(ItemClassification.useful, 1, 32),
|
||||
"Hourglass": TunicItemData(ItemClassification.useful, 1, 33),
|
||||
"Old House Key": TunicItemData(ItemClassification.progression, 1, 34, "Keys"),
|
||||
"Key": TunicItemData(ItemClassification.progression, 2, 35, "Keys"),
|
||||
"Fortress Vault Key": TunicItemData(ItemClassification.progression, 1, 36, "Keys"),
|
||||
"Flask Shard": TunicItemData(ItemClassification.useful, 12, 37),
|
||||
"Potion Flask": TunicItemData(ItemClassification.useful, 5, 38, "Flask"),
|
||||
"Golden Coin": TunicItemData(ItemClassification.progression, 17, 39),
|
||||
"Card Slot": TunicItemData(ItemClassification.useful, 4, 40),
|
||||
"Red Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 41, "Hexagons"),
|
||||
"Green Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 42, "Hexagons"),
|
||||
"Blue Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 1, 43, "Hexagons"),
|
||||
"Gold Questagon": TunicItemData(ItemClassification.progression_skip_balancing, 0, 44, "Hexagons"),
|
||||
"ATT Offering": TunicItemData(ItemClassification.useful, 4, 45, "Offerings"),
|
||||
"DEF Offering": TunicItemData(ItemClassification.useful, 4, 46, "Offerings"),
|
||||
"Potion Offering": TunicItemData(ItemClassification.useful, 3, 47, "Offerings"),
|
||||
"HP Offering": TunicItemData(ItemClassification.useful, 6, 48, "Offerings"),
|
||||
"MP Offering": TunicItemData(ItemClassification.useful, 3, 49, "Offerings"),
|
||||
"SP Offering": TunicItemData(ItemClassification.useful, 2, 50, "Offerings"),
|
||||
"Hero Relic - ATT": TunicItemData(ItemClassification.progression_skip_balancing, 1, 51, "Hero Relics"),
|
||||
"Hero Relic - DEF": TunicItemData(ItemClassification.progression_skip_balancing, 1, 52, "Hero Relics"),
|
||||
"Hero Relic - HP": TunicItemData(ItemClassification.progression_skip_balancing, 1, 53, "Hero Relics"),
|
||||
"Hero Relic - MP": TunicItemData(ItemClassification.progression_skip_balancing, 1, 54, "Hero Relics"),
|
||||
"Hero Relic - POTION": TunicItemData(ItemClassification.progression_skip_balancing, 1, 55, "Hero Relics"),
|
||||
"Hero Relic - SP": TunicItemData(ItemClassification.progression_skip_balancing, 1, 56, "Hero Relics"),
|
||||
"Orange Peril Ring": TunicItemData(ItemClassification.useful, 1, 57, "Cards"),
|
||||
"Tincture": TunicItemData(ItemClassification.useful, 1, 58, "Cards"),
|
||||
"Scavenger Mask": TunicItemData(ItemClassification.progression, 1, 59, "Cards"),
|
||||
"Cyan Peril Ring": TunicItemData(ItemClassification.useful, 1, 60, "Cards"),
|
||||
"Bracer": TunicItemData(ItemClassification.useful, 1, 61, "Cards"),
|
||||
"Dagger Strap": TunicItemData(ItemClassification.useful, 1, 62, "Cards"),
|
||||
"Inverted Ash": TunicItemData(ItemClassification.useful, 1, 63, "Cards"),
|
||||
"Lucky Cup": TunicItemData(ItemClassification.useful, 1, 64, "Cards"),
|
||||
"Magic Echo": TunicItemData(ItemClassification.useful, 1, 65, "Cards"),
|
||||
"Anklet": TunicItemData(ItemClassification.useful, 1, 66, "Cards"),
|
||||
"Muffling Bell": TunicItemData(ItemClassification.useful, 1, 67, "Cards"),
|
||||
"Glass Cannon": TunicItemData(ItemClassification.useful, 1, 68, "Cards"),
|
||||
"Perfume": TunicItemData(ItemClassification.useful, 1, 69, "Cards"),
|
||||
"Louder Echo": TunicItemData(ItemClassification.useful, 1, 70, "Cards"),
|
||||
"Aura's Gem": TunicItemData(ItemClassification.useful, 1, 71, "Cards"),
|
||||
"Bone Card": TunicItemData(ItemClassification.useful, 1, 72, "Cards"),
|
||||
"Mr Mayor": TunicItemData(ItemClassification.useful, 1, 73, "Golden Treasures"),
|
||||
"Secret Legend": TunicItemData(ItemClassification.useful, 1, 74, "Golden Treasures"),
|
||||
"Sacred Geometry": TunicItemData(ItemClassification.useful, 1, 75, "Golden Treasures"),
|
||||
"Vintage": TunicItemData(ItemClassification.useful, 1, 76, "Golden Treasures"),
|
||||
"Just Some Pals": TunicItemData(ItemClassification.useful, 1, 77, "Golden Treasures"),
|
||||
"Regal Weasel": TunicItemData(ItemClassification.useful, 1, 78, "Golden Treasures"),
|
||||
"Spring Falls": TunicItemData(ItemClassification.useful, 1, 79, "Golden Treasures"),
|
||||
"Power Up": TunicItemData(ItemClassification.useful, 1, 80, "Golden Treasures"),
|
||||
"Back To Work": TunicItemData(ItemClassification.useful, 1, 81, "Golden Treasures"),
|
||||
"Phonomath": TunicItemData(ItemClassification.useful, 1, 82, "Golden Treasures"),
|
||||
"Dusty": TunicItemData(ItemClassification.useful, 1, 83, "Golden Treasures"),
|
||||
"Forever Friend": TunicItemData(ItemClassification.useful, 1, 84, "Golden Treasures"),
|
||||
"Fool Trap": TunicItemData(ItemClassification.trap, 0, 85),
|
||||
"Money x1": TunicItemData(ItemClassification.filler, 3, 86, "Money"),
|
||||
"Money x10": TunicItemData(ItemClassification.filler, 1, 87, "Money"),
|
||||
"Money x15": TunicItemData(ItemClassification.filler, 10, 88, "Money"),
|
||||
"Money x16": TunicItemData(ItemClassification.filler, 1, 89, "Money"),
|
||||
"Money x20": TunicItemData(ItemClassification.filler, 17, 90, "Money"),
|
||||
"Money x25": TunicItemData(ItemClassification.filler, 14, 91, "Money"),
|
||||
"Money x30": TunicItemData(ItemClassification.filler, 4, 92, "Money"),
|
||||
"Money x32": TunicItemData(ItemClassification.filler, 4, 93, "Money"),
|
||||
"Money x40": TunicItemData(ItemClassification.filler, 3, 94, "Money"),
|
||||
"Money x48": TunicItemData(ItemClassification.filler, 1, 95, "Money"),
|
||||
"Money x50": TunicItemData(ItemClassification.filler, 7, 96, "Money"),
|
||||
"Money x64": TunicItemData(ItemClassification.filler, 1, 97, "Money"),
|
||||
"Money x100": TunicItemData(ItemClassification.filler, 5, 98, "Money"),
|
||||
"Money x128": TunicItemData(ItemClassification.useful, 3, 99, "Money"),
|
||||
"Money x200": TunicItemData(ItemClassification.useful, 1, 100, "Money"),
|
||||
"Money x255": TunicItemData(ItemClassification.useful, 1, 101, "Money"),
|
||||
"Pages 0-1": TunicItemData(ItemClassification.useful, 1, 102, "Pages"),
|
||||
"Pages 2-3": TunicItemData(ItemClassification.useful, 1, 103, "Pages"),
|
||||
"Pages 4-5": TunicItemData(ItemClassification.useful, 1, 104, "Pages"),
|
||||
"Pages 6-7": TunicItemData(ItemClassification.useful, 1, 105, "Pages"),
|
||||
"Pages 8-9": TunicItemData(ItemClassification.useful, 1, 106, "Pages"),
|
||||
"Pages 10-11": TunicItemData(ItemClassification.useful, 1, 107, "Pages"),
|
||||
"Pages 12-13": TunicItemData(ItemClassification.useful, 1, 108, "Pages"),
|
||||
"Pages 14-15": TunicItemData(ItemClassification.useful, 1, 109, "Pages"),
|
||||
"Pages 16-17": TunicItemData(ItemClassification.useful, 1, 110, "Pages"),
|
||||
"Pages 18-19": TunicItemData(ItemClassification.useful, 1, 111, "Pages"),
|
||||
"Pages 20-21": TunicItemData(ItemClassification.useful, 1, 112, "Pages"),
|
||||
"Pages 22-23": TunicItemData(ItemClassification.useful, 1, 113, "Pages"),
|
||||
"Pages 24-25 (Prayer)": TunicItemData(ItemClassification.progression, 1, 114, "Pages"),
|
||||
"Pages 26-27": TunicItemData(ItemClassification.useful, 1, 115, "Pages"),
|
||||
"Pages 28-29": TunicItemData(ItemClassification.useful, 1, 116, "Pages"),
|
||||
"Pages 30-31": TunicItemData(ItemClassification.useful, 1, 117, "Pages"),
|
||||
"Pages 32-33": TunicItemData(ItemClassification.useful, 1, 118, "Pages"),
|
||||
"Pages 34-35": TunicItemData(ItemClassification.useful, 1, 119, "Pages"),
|
||||
"Pages 36-37": TunicItemData(ItemClassification.useful, 1, 120, "Pages"),
|
||||
"Pages 38-39": TunicItemData(ItemClassification.useful, 1, 121, "Pages"),
|
||||
"Pages 40-41": TunicItemData(ItemClassification.useful, 1, 122, "Pages"),
|
||||
"Pages 42-43 (Holy Cross)": TunicItemData(ItemClassification.progression, 1, 123, "Pages"),
|
||||
"Pages 44-45": TunicItemData(ItemClassification.useful, 1, 124, "Pages"),
|
||||
"Pages 46-47": TunicItemData(ItemClassification.useful, 1, 125, "Pages"),
|
||||
"Pages 48-49": TunicItemData(ItemClassification.useful, 1, 126, "Pages"),
|
||||
"Pages 50-51": TunicItemData(ItemClassification.useful, 1, 127, "Pages"),
|
||||
"Pages 52-53 (Icebolt)": TunicItemData(ItemClassification.progression, 1, 128, "Pages"),
|
||||
"Pages 54-55": TunicItemData(ItemClassification.useful, 1, 129, "Pages"),
|
||||
"Ladders near Weathervane": TunicItemData(ItemClassification.progression, 0, 130, "Ladders"),
|
||||
"Ladders near Overworld Checkpoint": TunicItemData(ItemClassification.progression, 0, 131, "Ladders"),
|
||||
"Ladders near Patrol Cave": TunicItemData(ItemClassification.progression, 0, 132, "Ladders"),
|
||||
"Ladder near Temple Rafters": TunicItemData(ItemClassification.progression, 0, 133, "Ladders"),
|
||||
"Ladders near Dark Tomb": TunicItemData(ItemClassification.progression, 0, 134, "Ladders"),
|
||||
"Ladder to Quarry": TunicItemData(ItemClassification.progression, 0, 135, "Ladders"),
|
||||
"Ladders to West Bell": TunicItemData(ItemClassification.progression, 0, 136, "Ladders"),
|
||||
"Ladders in Overworld Town": TunicItemData(ItemClassification.progression, 0, 137, "Ladders"),
|
||||
"Ladder to Ruined Atoll": TunicItemData(ItemClassification.progression, 0, 138, "Ladders"),
|
||||
"Ladder to Swamp": TunicItemData(ItemClassification.progression, 0, 139, "Ladders"),
|
||||
"Ladders in Well": TunicItemData(ItemClassification.progression, 0, 140, "Ladders"),
|
||||
"Ladder in Dark Tomb": TunicItemData(ItemClassification.progression, 0, 141, "Ladders"),
|
||||
"Ladder to East Forest": TunicItemData(ItemClassification.progression, 0, 142, "Ladders"),
|
||||
"Ladders to Lower Forest": TunicItemData(ItemClassification.progression, 0, 143, "Ladders"),
|
||||
"Ladder to Beneath the Vault": TunicItemData(ItemClassification.progression, 0, 144, "Ladders"),
|
||||
"Ladders in Hourglass Cave": TunicItemData(ItemClassification.progression, 0, 145, "Ladders"),
|
||||
"Ladders in South Atoll": TunicItemData(ItemClassification.progression, 0, 146, "Ladders"),
|
||||
"Ladders to Frog's Domain": TunicItemData(ItemClassification.progression, 0, 147, "Ladders"),
|
||||
"Ladders in Library": TunicItemData(ItemClassification.progression, 0, 148, "Ladders"),
|
||||
"Ladders in Lower Quarry": TunicItemData(ItemClassification.progression, 0, 149, "Ladders"),
|
||||
"Ladders in Swamp": TunicItemData(ItemClassification.progression, 0, 150, "Ladders"),
|
||||
"Firecracker x2": TunicItemData(IC.filler, 3, 0, "Bombs"),
|
||||
"Firecracker x3": TunicItemData(IC.filler, 3, 1, "Bombs"),
|
||||
"Firecracker x4": TunicItemData(IC.filler, 3, 2, "Bombs"),
|
||||
"Firecracker x5": TunicItemData(IC.filler, 1, 3, "Bombs"),
|
||||
"Firecracker x6": TunicItemData(IC.filler, 2, 4, "Bombs"),
|
||||
"Fire Bomb x2": TunicItemData(IC.filler, 2, 5, "Bombs"),
|
||||
"Fire Bomb x3": TunicItemData(IC.filler, 1, 6, "Bombs"),
|
||||
"Ice Bomb x2": TunicItemData(IC.filler, 2, 7, "Bombs"),
|
||||
"Ice Bomb x3": TunicItemData(IC.filler, 2, 8, "Bombs"),
|
||||
"Ice Bomb x5": TunicItemData(IC.filler, 1, 9, "Bombs"),
|
||||
"Lure": TunicItemData(IC.filler, 4, 10, "Consumables"),
|
||||
"Lure x2": TunicItemData(IC.filler, 1, 11, "Consumables"),
|
||||
"Pepper x2": TunicItemData(IC.filler, 4, 12, "Consumables"),
|
||||
"Ivy x3": TunicItemData(IC.filler, 2, 13, "Consumables"),
|
||||
"Effigy": TunicItemData(IC.useful, 12, 14, "Money"),
|
||||
"HP Berry": TunicItemData(IC.filler, 2, 15, "Consumables"),
|
||||
"HP Berry x2": TunicItemData(IC.filler, 4, 16, "Consumables"),
|
||||
"HP Berry x3": TunicItemData(IC.filler, 2, 17, "Consumables"),
|
||||
"MP Berry": TunicItemData(IC.filler, 4, 18, "Consumables"),
|
||||
"MP Berry x2": TunicItemData(IC.filler, 2, 19, "Consumables"),
|
||||
"MP Berry x3": TunicItemData(IC.filler, 7, 20, "Consumables"),
|
||||
"Fairy": TunicItemData(IC.progression, 20, 21),
|
||||
"Stick": TunicItemData(IC.progression | IC.useful, 1, 22, "Weapons"),
|
||||
"Sword": TunicItemData(IC.progression | IC.useful, 3, 23, "Weapons"),
|
||||
"Sword Upgrade": TunicItemData(IC.progression | IC.useful, 4, 24, "Weapons"),
|
||||
"Magic Wand": TunicItemData(IC.progression | IC.useful, 1, 25, "Weapons"),
|
||||
"Magic Dagger": TunicItemData(IC.progression | IC.useful, 1, 26),
|
||||
"Magic Orb": TunicItemData(IC.progression | IC.useful, 1, 27),
|
||||
"Hero's Laurels": TunicItemData(IC.progression | IC.useful, 1, 28),
|
||||
"Lantern": TunicItemData(IC.progression, 1, 29),
|
||||
"Gun": TunicItemData(IC.progression | IC.useful, 1, 30, "Weapons"),
|
||||
"Shield": TunicItemData(IC.useful, 1, 31),
|
||||
"Dath Stone": TunicItemData(IC.useful, 1, 32),
|
||||
"Hourglass": TunicItemData(IC.useful, 1, 33),
|
||||
"Old House Key": TunicItemData(IC.progression, 1, 34, "Keys"),
|
||||
"Key": TunicItemData(IC.progression, 2, 35, "Keys"),
|
||||
"Fortress Vault Key": TunicItemData(IC.progression, 1, 36, "Keys"),
|
||||
"Flask Shard": TunicItemData(IC.useful, 12, 37),
|
||||
"Potion Flask": TunicItemData(IC.useful, 5, 38, "Flask"),
|
||||
"Golden Coin": TunicItemData(IC.progression, 17, 39),
|
||||
"Card Slot": TunicItemData(IC.useful, 4, 40),
|
||||
"Red Questagon": TunicItemData(IC.progression_skip_balancing, 1, 41, "Hexagons"),
|
||||
"Green Questagon": TunicItemData(IC.progression_skip_balancing, 1, 42, "Hexagons"),
|
||||
"Blue Questagon": TunicItemData(IC.progression_skip_balancing, 1, 43, "Hexagons"),
|
||||
"Gold Questagon": TunicItemData(IC.progression_skip_balancing, 0, 44, "Hexagons"),
|
||||
"ATT Offering": TunicItemData(IC.useful, 4, 45, "Offerings"),
|
||||
"DEF Offering": TunicItemData(IC.useful, 4, 46, "Offerings"),
|
||||
"Potion Offering": TunicItemData(IC.useful, 3, 47, "Offerings"),
|
||||
"HP Offering": TunicItemData(IC.useful, 6, 48, "Offerings"),
|
||||
"MP Offering": TunicItemData(IC.useful, 3, 49, "Offerings"),
|
||||
"SP Offering": TunicItemData(IC.useful, 2, 50, "Offerings"),
|
||||
"Hero Relic - ATT": TunicItemData(IC.progression_skip_balancing, 1, 51, "Hero Relics"),
|
||||
"Hero Relic - DEF": TunicItemData(IC.progression_skip_balancing, 1, 52, "Hero Relics"),
|
||||
"Hero Relic - HP": TunicItemData(IC.progression_skip_balancing, 1, 53, "Hero Relics"),
|
||||
"Hero Relic - MP": TunicItemData(IC.progression_skip_balancing, 1, 54, "Hero Relics"),
|
||||
"Hero Relic - POTION": TunicItemData(IC.progression_skip_balancing, 1, 55, "Hero Relics"),
|
||||
"Hero Relic - SP": TunicItemData(IC.progression_skip_balancing, 1, 56, "Hero Relics"),
|
||||
"Orange Peril Ring": TunicItemData(IC.useful, 1, 57, "Cards"),
|
||||
"Tincture": TunicItemData(IC.useful, 1, 58, "Cards"),
|
||||
"Scavenger Mask": TunicItemData(IC.progression, 1, 59, "Cards"),
|
||||
"Cyan Peril Ring": TunicItemData(IC.useful, 1, 60, "Cards"),
|
||||
"Bracer": TunicItemData(IC.useful, 1, 61, "Cards"),
|
||||
"Dagger Strap": TunicItemData(IC.useful, 1, 62, "Cards"),
|
||||
"Inverted Ash": TunicItemData(IC.useful, 1, 63, "Cards"),
|
||||
"Lucky Cup": TunicItemData(IC.useful, 1, 64, "Cards"),
|
||||
"Magic Echo": TunicItemData(IC.useful, 1, 65, "Cards"),
|
||||
"Anklet": TunicItemData(IC.useful, 1, 66, "Cards"),
|
||||
"Muffling Bell": TunicItemData(IC.useful, 1, 67, "Cards"),
|
||||
"Glass Cannon": TunicItemData(IC.useful, 1, 68, "Cards"),
|
||||
"Perfume": TunicItemData(IC.useful, 1, 69, "Cards"),
|
||||
"Louder Echo": TunicItemData(IC.useful, 1, 70, "Cards"),
|
||||
"Aura's Gem": TunicItemData(IC.useful, 1, 71, "Cards"),
|
||||
"Bone Card": TunicItemData(IC.useful, 1, 72, "Cards"),
|
||||
"Mr Mayor": TunicItemData(IC.useful, 1, 73, "Golden Treasures"),
|
||||
"Secret Legend": TunicItemData(IC.useful, 1, 74, "Golden Treasures"),
|
||||
"Sacred Geometry": TunicItemData(IC.useful, 1, 75, "Golden Treasures"),
|
||||
"Vintage": TunicItemData(IC.useful, 1, 76, "Golden Treasures"),
|
||||
"Just Some Pals": TunicItemData(IC.useful, 1, 77, "Golden Treasures"),
|
||||
"Regal Weasel": TunicItemData(IC.useful, 1, 78, "Golden Treasures"),
|
||||
"Spring Falls": TunicItemData(IC.useful, 1, 79, "Golden Treasures"),
|
||||
"Power Up": TunicItemData(IC.useful, 1, 80, "Golden Treasures"),
|
||||
"Back To Work": TunicItemData(IC.useful, 1, 81, "Golden Treasures"),
|
||||
"Phonomath": TunicItemData(IC.useful, 1, 82, "Golden Treasures"),
|
||||
"Dusty": TunicItemData(IC.useful, 1, 83, "Golden Treasures"),
|
||||
"Forever Friend": TunicItemData(IC.useful, 1, 84, "Golden Treasures"),
|
||||
"Fool Trap": TunicItemData(IC.trap, 0, 85),
|
||||
"Money x1": TunicItemData(IC.filler, 3, 86, "Money"),
|
||||
"Money x10": TunicItemData(IC.filler, 1, 87, "Money"),
|
||||
"Money x15": TunicItemData(IC.filler, 10, 88, "Money"),
|
||||
"Money x16": TunicItemData(IC.filler, 1, 89, "Money"),
|
||||
"Money x20": TunicItemData(IC.filler, 17, 90, "Money"),
|
||||
"Money x25": TunicItemData(IC.filler, 14, 91, "Money"),
|
||||
"Money x30": TunicItemData(IC.filler, 4, 92, "Money"),
|
||||
"Money x32": TunicItemData(IC.filler, 4, 93, "Money"),
|
||||
"Money x40": TunicItemData(IC.filler, 3, 94, "Money"),
|
||||
"Money x48": TunicItemData(IC.filler, 1, 95, "Money"),
|
||||
"Money x50": TunicItemData(IC.filler, 7, 96, "Money"),
|
||||
"Money x64": TunicItemData(IC.filler, 1, 97, "Money"),
|
||||
"Money x100": TunicItemData(IC.filler, 5, 98, "Money"),
|
||||
"Money x128": TunicItemData(IC.useful, 3, 99, "Money"),
|
||||
"Money x200": TunicItemData(IC.useful, 1, 100, "Money"),
|
||||
"Money x255": TunicItemData(IC.useful, 1, 101, "Money"),
|
||||
"Pages 0-1": TunicItemData(IC.useful, 1, 102, "Pages"),
|
||||
"Pages 2-3": TunicItemData(IC.useful, 1, 103, "Pages"),
|
||||
"Pages 4-5": TunicItemData(IC.useful, 1, 104, "Pages"),
|
||||
"Pages 6-7": TunicItemData(IC.useful, 1, 105, "Pages"),
|
||||
"Pages 8-9": TunicItemData(IC.useful, 1, 106, "Pages"),
|
||||
"Pages 10-11": TunicItemData(IC.useful, 1, 107, "Pages"),
|
||||
"Pages 12-13": TunicItemData(IC.useful, 1, 108, "Pages"),
|
||||
"Pages 14-15": TunicItemData(IC.useful, 1, 109, "Pages"),
|
||||
"Pages 16-17": TunicItemData(IC.useful, 1, 110, "Pages"),
|
||||
"Pages 18-19": TunicItemData(IC.useful, 1, 111, "Pages"),
|
||||
"Pages 20-21": TunicItemData(IC.useful, 1, 112, "Pages"),
|
||||
"Pages 22-23": TunicItemData(IC.useful, 1, 113, "Pages"),
|
||||
"Pages 24-25 (Prayer)": TunicItemData(IC.progression | IC.useful, 1, 114, "Pages"),
|
||||
"Pages 26-27": TunicItemData(IC.useful, 1, 115, "Pages"),
|
||||
"Pages 28-29": TunicItemData(IC.useful, 1, 116, "Pages"),
|
||||
"Pages 30-31": TunicItemData(IC.useful, 1, 117, "Pages"),
|
||||
"Pages 32-33": TunicItemData(IC.useful, 1, 118, "Pages"),
|
||||
"Pages 34-35": TunicItemData(IC.useful, 1, 119, "Pages"),
|
||||
"Pages 36-37": TunicItemData(IC.useful, 1, 120, "Pages"),
|
||||
"Pages 38-39": TunicItemData(IC.useful, 1, 121, "Pages"),
|
||||
"Pages 40-41": TunicItemData(IC.useful, 1, 122, "Pages"),
|
||||
"Pages 42-43 (Holy Cross)": TunicItemData(IC.progression | IC.useful, 1, 123, "Pages"),
|
||||
"Pages 44-45": TunicItemData(IC.useful, 1, 124, "Pages"),
|
||||
"Pages 46-47": TunicItemData(IC.useful, 1, 125, "Pages"),
|
||||
"Pages 48-49": TunicItemData(IC.useful, 1, 126, "Pages"),
|
||||
"Pages 50-51": TunicItemData(IC.useful, 1, 127, "Pages"),
|
||||
"Pages 52-53 (Icebolt)": TunicItemData(IC.progression, 1, 128, "Pages"),
|
||||
"Pages 54-55": TunicItemData(IC.useful, 1, 129, "Pages"),
|
||||
"Ladders near Weathervane": TunicItemData(IC.progression, 0, 130, "Ladders"),
|
||||
"Ladders near Overworld Checkpoint": TunicItemData(IC.progression, 0, 131, "Ladders"),
|
||||
"Ladders near Patrol Cave": TunicItemData(IC.progression, 0, 132, "Ladders"),
|
||||
"Ladder near Temple Rafters": TunicItemData(IC.progression, 0, 133, "Ladders"),
|
||||
"Ladders near Dark Tomb": TunicItemData(IC.progression, 0, 134, "Ladders"),
|
||||
"Ladder to Quarry": TunicItemData(IC.progression, 0, 135, "Ladders"),
|
||||
"Ladders to West Bell": TunicItemData(IC.progression, 0, 136, "Ladders"),
|
||||
"Ladders in Overworld Town": TunicItemData(IC.progression, 0, 137, "Ladders"),
|
||||
"Ladder to Ruined Atoll": TunicItemData(IC.progression, 0, 138, "Ladders"),
|
||||
"Ladder to Swamp": TunicItemData(IC.progression, 0, 139, "Ladders"),
|
||||
"Ladders in Well": TunicItemData(IC.progression, 0, 140, "Ladders"),
|
||||
"Ladder in Dark Tomb": TunicItemData(IC.progression, 0, 141, "Ladders"),
|
||||
"Ladder to East Forest": TunicItemData(IC.progression, 0, 142, "Ladders"),
|
||||
"Ladders to Lower Forest": TunicItemData(IC.progression, 0, 143, "Ladders"),
|
||||
"Ladder to Beneath the Vault": TunicItemData(IC.progression, 0, 144, "Ladders"),
|
||||
"Ladders in Hourglass Cave": TunicItemData(IC.progression, 0, 145, "Ladders"),
|
||||
"Ladders in South Atoll": TunicItemData(IC.progression, 0, 146, "Ladders"),
|
||||
"Ladders to Frog's Domain": TunicItemData(IC.progression, 0, 147, "Ladders"),
|
||||
"Ladders in Library": TunicItemData(IC.progression, 0, 148, "Ladders"),
|
||||
"Ladders in Lower Quarry": TunicItemData(IC.progression, 0, 149, "Ladders"),
|
||||
"Ladders in Swamp": TunicItemData(IC.progression, 0, 150, "Ladders"),
|
||||
}
|
||||
|
||||
# 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()}
|
||||
|
||||
filler_items: List[str] = [name for name, data in item_table.items() if data.classification == ItemClassification.filler]
|
||||
filler_items: List[str] = [name for name, data in item_table.items() if data.classification == IC.filler]
|
||||
|
||||
|
||||
def get_item_group(item_name: str) -> str:
|
||||
|
||||
@@ -16,7 +16,7 @@ class YachtDiceItem(Item):
|
||||
|
||||
|
||||
item_table = {
|
||||
"Dice": ItemData(16871244000, ItemClassification.progression),
|
||||
"Dice": ItemData(16871244000, ItemClassification.progression | ItemClassification.useful),
|
||||
"Dice Fragment": ItemData(16871244001, ItemClassification.progression),
|
||||
"Roll": ItemData(16871244002, 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.
|
||||
"1 Point": ItemData(16871244301, ItemClassification.progression_skip_balancing),
|
||||
"10 Points": ItemData(16871244302, ItemClassification.progression),
|
||||
"100 Points": ItemData(16871244303, ItemClassification.progression),
|
||||
"100 Points": ItemData(16871244303, ItemClassification.progression | ItemClassification.useful),
|
||||
}
|
||||
|
||||
# item groups for better hinting
|
||||
|
||||
@@ -101,14 +101,15 @@ def dice_simulation_strings(categories, num_dice, num_rolls, fixed_mult, step_mu
|
||||
return yachtdice_cache[player][tup]
|
||||
|
||||
# sort categories because for the step multiplier, you will want low-scoring categories first
|
||||
categories.sort(key=lambda category: category.mean_score(num_dice, num_rolls))
|
||||
# to avoid errors with order changing when obtaining rolls, we order assuming 4 rolls
|
||||
categories.sort(key=lambda category: category.mean_score(num_dice, 4))
|
||||
|
||||
# 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)
|
||||
def add_distributions(dist1, dist2):
|
||||
combined_dist = defaultdict(float)
|
||||
for val1, prob1 in dist1.items():
|
||||
for val2, prob2 in dist2.items():
|
||||
for val2, prob2 in dist2.items():
|
||||
for val1, prob1 in dist1.items():
|
||||
combined_dist[val1 + val2] += prob1 * prob2
|
||||
return dict(combined_dist)
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ class YachtDiceWorld(World):
|
||||
|
||||
item_name_groups = item_groups
|
||||
|
||||
ap_world_version = "2.1.3"
|
||||
ap_world_version = "2.1.4"
|
||||
|
||||
def _get_yachtdice_data(self):
|
||||
return {
|
||||
@@ -468,7 +468,7 @@ class YachtDiceWorld(World):
|
||||
menu.exits.append(connection)
|
||||
connection.connect(board)
|
||||
self.multiworld.regions += [menu, board]
|
||||
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return "Good RNG"
|
||||
|
||||
|
||||