Compare commits
41 Commits
core_negat
...
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 | ||
|
|
2751ccdaab | ||
|
|
6287bc27a6 | ||
|
|
97f2c25924 | ||
|
|
e5a0ef799f | ||
|
|
216e0603e1 | ||
|
|
05a67386c6 | ||
|
|
0ec9039ca6 | ||
|
|
f06f95d03d | ||
|
|
5a853dfccd | ||
|
|
23469fa5c3 | ||
|
|
dc1da4e88b | ||
|
|
67f6b458d7 |
@@ -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)
|
||||
@@ -454,6 +456,7 @@ class CommonContext:
|
||||
if kwargs:
|
||||
payload.update(kwargs)
|
||||
await self.send_msgs([payload])
|
||||
await self.send_msgs([{"cmd": "Get", "keys": ["_read_race_mode"]}])
|
||||
|
||||
async def console_input(self) -> str:
|
||||
if self.ui:
|
||||
@@ -570,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 \
|
||||
|
||||
1
Main.py
@@ -338,6 +338,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
|
||||
"seed_name": multiworld.seed_name,
|
||||
"spheres": spheres,
|
||||
"datapackage": data_package,
|
||||
"race_mode": int(multiworld.is_race),
|
||||
}
|
||||
AutoWorld.call_all(multiworld, "modify_multidata", multidata)
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import math
|
||||
import operator
|
||||
import pickle
|
||||
import random
|
||||
import shlex
|
||||
import threading
|
||||
import time
|
||||
import typing
|
||||
@@ -184,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]]
|
||||
@@ -197,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,
|
||||
@@ -268,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()
|
||||
@@ -427,6 +429,8 @@ class Context:
|
||||
use_embedded_server_options: bool):
|
||||
|
||||
self.read_data = {}
|
||||
# there might be a better place to put this.
|
||||
self.read_data["race_mode"] = lambda: decoded_obj.get("race_mode", 0)
|
||||
mdata_ver = decoded_obj["minimum_versions"]["server"]
|
||||
if mdata_ver > version_tuple:
|
||||
raise RuntimeError(f"Supplied Multidata (.archipelago) requires a server of at least version {mdata_ver},"
|
||||
@@ -1150,7 +1154,10 @@ class CommandProcessor(metaclass=CommandMeta):
|
||||
if not raw:
|
||||
return
|
||||
try:
|
||||
command = raw.split()
|
||||
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")
|
||||
|
||||
@@ -81,6 +81,7 @@ def start_generation(options: Dict[str, Union[dict, str]], meta: Dict[str, Any])
|
||||
elif len(gen_options) > app.config["MAX_ROLL"]:
|
||||
flash(f"Sorry, generating of multiworlds is limited to {app.config['MAX_ROLL']} players. "
|
||||
f"If you have a larger group, please generate it yourself and upload it.")
|
||||
return redirect(url_for(request.endpoint, **(request.view_args or {})))
|
||||
elif len(gen_options) >= app.config["JOB_THRESHOLD"]:
|
||||
gen = Generation(
|
||||
options=pickle.dumps({name: vars(options) for name, options in gen_options.items()}),
|
||||
|
||||
@@ -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)
|
||||
@@ -395,6 +396,7 @@ Some special keys exist with specific return data, all of them have the prefix `
|
||||
| item_name_groups_{game_name} | dict\[str, list\[str\]\] | item_name_groups belonging to the requested game. |
|
||||
| location_name_groups_{game_name} | dict\[str, list\[str\]\] | location_name_groups belonging to the requested game. |
|
||||
| client_status_{team}_{slot} | [ClientStatus](#ClientStatus) | The current game status of the requested player. |
|
||||
| race_mode | int | 0 if race mode is disabled, and 1 if it's enabled. |
|
||||
|
||||
### Set
|
||||
Used to write data to the server's data storage, that data can then be shared across worlds or just saved for later. Values for keys in the data storage can be retrieved with a [Get](#Get) package, or monitored with a [SetNotify](#SetNotify) package.
|
||||
|
||||
3
kvui.py
@@ -243,6 +243,9 @@ class ServerLabel(HovererableLabel):
|
||||
f"\nYou currently have {ctx.hint_points} points."
|
||||
elif ctx.hint_cost == 0:
|
||||
text += "\n!hint is free to use."
|
||||
if ctx.stored_data and "_read_race_mode" in ctx.stored_data:
|
||||
text += "\nRace mode is enabled." \
|
||||
if ctx.stored_data["_read_race_mode"] else "\nRace mode is disabled."
|
||||
else:
|
||||
text += f"\nYou are not authenticated yet."
|
||||
|
||||
|
||||
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))
|
||||
|
||||
@@ -71,7 +71,7 @@ class TestTwoPlayerMulti(MultiworldTestBase):
|
||||
for world in self.multiworld.worlds.values():
|
||||
world.options.accessibility.value = Accessibility.option_full
|
||||
self.assertSteps(gen_steps)
|
||||
with self.subTest("filling multiworld", seed=self.multiworld.seed):
|
||||
distribute_items_restrictive(self.multiworld)
|
||||
call_all(self.multiworld, "post_fill")
|
||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||
with self.subTest("filling multiworld", games=world_type.game, seed=self.multiworld.seed):
|
||||
distribute_items_restrictive(self.multiworld)
|
||||
call_all(self.multiworld, "post_fill")
|
||||
self.assertTrue(self.fulfills_accessibility(), "Collected all locations, but can't beat the game")
|
||||
|
||||
73
test/webhost/test_generate.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import zipfile
|
||||
from io import BytesIO
|
||||
|
||||
from flask import url_for
|
||||
|
||||
from . import TestBase
|
||||
|
||||
|
||||
class TestGenerate(TestBase):
|
||||
def test_valid_yaml(self) -> None:
|
||||
"""
|
||||
Verify that posting a valid yaml will start generating a game.
|
||||
"""
|
||||
with self.app.app_context(), self.app.test_request_context():
|
||||
yaml_data = """
|
||||
name: Player1
|
||||
game: Archipelago
|
||||
Archipelago: {}
|
||||
"""
|
||||
response = self.client.post(url_for("generate"),
|
||||
data={"file": (BytesIO(yaml_data.encode("utf-8")), "test.yaml")},
|
||||
follow_redirects=True)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertTrue("/seed/" in response.request.path or
|
||||
"/wait/" in response.request.path,
|
||||
f"Response did not properly redirect ({response.request.path})")
|
||||
|
||||
def test_empty_zip(self) -> None:
|
||||
"""
|
||||
Verify that posting an empty zip will give an error.
|
||||
"""
|
||||
with self.app.app_context(), self.app.test_request_context():
|
||||
zip_data = BytesIO()
|
||||
zipfile.ZipFile(zip_data, "w").close()
|
||||
zip_data.seek(0)
|
||||
self.assertGreater(len(zip_data.read()), 0)
|
||||
zip_data.seek(0)
|
||||
response = self.client.post(url_for("generate"),
|
||||
data={"file": (zip_data, "test.zip")},
|
||||
follow_redirects=True)
|
||||
self.assertIn("user-message", response.text,
|
||||
"Request did not call flash()")
|
||||
self.assertIn("not find any valid files", response.text,
|
||||
"Response shows unexpected error")
|
||||
self.assertIn("generate-game-form", response.text,
|
||||
"Response did not get user back to the form")
|
||||
|
||||
def test_too_many_players(self) -> None:
|
||||
"""
|
||||
Verify that posting too many players will give an error.
|
||||
"""
|
||||
max_roll = self.app.config["MAX_ROLL"]
|
||||
# validate that max roll has a sensible value, otherwise we probably changed how it works
|
||||
self.assertIsInstance(max_roll, int)
|
||||
self.assertGreater(max_roll, 1)
|
||||
self.assertLess(max_roll, 100)
|
||||
# create a yaml with max_roll+1 players and watch it fail
|
||||
with self.app.app_context(), self.app.test_request_context():
|
||||
yaml_data = "---\n".join([
|
||||
f"name: Player{n}\n"
|
||||
"game: Archipelago\n"
|
||||
"Archipelago: {}\n"
|
||||
for n in range(1, max_roll + 2)
|
||||
])
|
||||
response = self.client.post(url_for("generate"),
|
||||
data={"file": (BytesIO(yaml_data.encode("utf-8")), "test.yaml")},
|
||||
follow_redirects=True)
|
||||
self.assertIn("user-message", response.text,
|
||||
"Request did not call flash()")
|
||||
self.assertIn("limited to", response.text,
|
||||
"Response shows unexpected error")
|
||||
self.assertIn("generate-game-form", response.text,
|
||||
"Response did not get user back to the form")
|
||||
@@ -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.
|
||||
|
||||
@@ -89,6 +89,7 @@ class DarkSouls3World(World):
|
||||
self.all_excluded_locations = set()
|
||||
|
||||
def generate_early(self) -> None:
|
||||
self.created_regions = set()
|
||||
self.all_excluded_locations.update(self.options.exclude_locations.value)
|
||||
|
||||
# Inform Universal Tracker where Yhorm is being randomized to.
|
||||
@@ -294,6 +295,7 @@ class DarkSouls3World(World):
|
||||
new_region.locations.append(new_location)
|
||||
|
||||
self.multiworld.regions.append(new_region)
|
||||
self.created_regions.add(region_name)
|
||||
return new_region
|
||||
|
||||
def create_items(self) -> None:
|
||||
@@ -1305,7 +1307,7 @@ class DarkSouls3World(World):
|
||||
def _add_entrance_rule(self, region: str, rule: Union[CollectionRule, str]) -> None:
|
||||
"""Sets a rule for the entrance to the given region."""
|
||||
assert region in location_tables
|
||||
if not any(region == reg for reg in self.multiworld.regions.region_cache[self.player]): return
|
||||
if region not in self.created_regions: return
|
||||
if isinstance(rule, str):
|
||||
if " -> " not in rule:
|
||||
assert item_dictionary[rule].classification == ItemClassification.progression
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -325,7 +325,7 @@ class KDL3World(World):
|
||||
|
||||
def generate_output(self, output_directory: str) -> None:
|
||||
try:
|
||||
patch = KDL3ProcedurePatch()
|
||||
patch = KDL3ProcedurePatch(player=self.player, player_name=self.player_name)
|
||||
patch_rom(self, patch)
|
||||
|
||||
self.rom_name = patch.name
|
||||
|
||||
@@ -101,7 +101,18 @@ class KH2World(World):
|
||||
if ability in self.goofy_ability_dict and self.goofy_ability_dict[ability] >= 1:
|
||||
self.goofy_ability_dict[ability] -= 1
|
||||
|
||||
slot_data = self.options.as_dict("Goal", "FinalXemnas", "LuckyEmblemsRequired", "BountyRequired")
|
||||
slot_data = self.options.as_dict(
|
||||
"Goal",
|
||||
"FinalXemnas",
|
||||
"LuckyEmblemsRequired",
|
||||
"BountyRequired",
|
||||
"FightLogic",
|
||||
"FinalFormLogic",
|
||||
"AutoFormLogic",
|
||||
"LevelDepth",
|
||||
"DonaldGoofyStatsanity",
|
||||
"CorSkipToggle"
|
||||
)
|
||||
slot_data.update({
|
||||
"hitlist": [], # remove this after next update
|
||||
"PoptrackerVersionCheck": 4.3,
|
||||
|
||||
@@ -81,23 +81,23 @@ talking:
|
||||
|
||||
; Give powder
|
||||
ld a, [$DB4C]
|
||||
cp $10
|
||||
cp $20
|
||||
jr nc, doNotGivePowder
|
||||
ld a, $10
|
||||
ld a, $20
|
||||
ld [$DB4C], a
|
||||
doNotGivePowder:
|
||||
|
||||
ld a, [$DB4D]
|
||||
cp $10
|
||||
cp $30
|
||||
jr nc, doNotGiveBombs
|
||||
ld a, $10
|
||||
ld a, $30
|
||||
ld [$DB4D], a
|
||||
doNotGiveBombs:
|
||||
|
||||
ld a, [$DB45]
|
||||
cp $10
|
||||
cp $30
|
||||
jr nc, doNotGiveArrows
|
||||
ld a, $10
|
||||
ld a, $30
|
||||
ld [$DB45], a
|
||||
doNotGiveArrows:
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -8,6 +8,9 @@
|
||||
|
||||
### Fixes
|
||||
|
||||
- Fixed a rare issue where receiving a wonder trade could partially corrupt the save data, preventing the player from
|
||||
receiving new items.
|
||||
- Fixed the client spamming the "goal complete" status update to the server instead of sending it once.
|
||||
- Fixed a logic issue where the "Mauville City - Coin Case from Lady in House" location only required a Harbor Mail if
|
||||
the player randomized NPC gifts.
|
||||
- The Dig tutor has its compatibility percentage raised to 50% if the player's TM/tutor compatibility is set lower.
|
||||
|
||||
@@ -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 {}),
|
||||
},
|
||||
}])
|
||||
|
||||
@@ -545,11 +555,12 @@ class PokemonEmeraldClient(BizHawkClient):
|
||||
if trade_is_sent == 0 and wonder_trade_pokemon_data[19] == 2:
|
||||
# Game has wonder trade data to send. Send it to data storage, remove it from the game's memory,
|
||||
# and mark that the game is waiting on receiving a trade
|
||||
Utils.async_start(self.wonder_trade_send(ctx, pokemon_data_to_json(wonder_trade_pokemon_data)))
|
||||
await bizhawk.write(ctx.bizhawk_ctx, [
|
||||
success = await bizhawk.guarded_write(ctx.bizhawk_ctx, [
|
||||
(sb1_address + 0x377C, bytes(0x50), "System Bus"),
|
||||
(sb1_address + 0x37CC, [1], "System Bus"),
|
||||
])
|
||||
], [guards["SAVE BLOCK 1"]])
|
||||
if success:
|
||||
Utils.async_start(self.wonder_trade_send(ctx, pokemon_data_to_json(wonder_trade_pokemon_data)))
|
||||
elif trade_is_sent != 0 and wonder_trade_pokemon_data[19] != 2:
|
||||
# Game is waiting on receiving a trade.
|
||||
if self.queued_received_trade is not None:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -14,7 +14,7 @@ from .data import static_items as static_witness_items
|
||||
from .data import static_locations as static_witness_locations
|
||||
from .data import static_logic as static_witness_logic
|
||||
from .data.item_definition_classes import DoorItemDefinition, ItemData
|
||||
from .data.utils import get_audio_logs
|
||||
from .data.utils import cast_not_none, get_audio_logs
|
||||
from .hints import CompactHintData, create_all_hints, make_compact_hint_data, make_laser_hints
|
||||
from .locations import WitnessPlayerLocations
|
||||
from .options import TheWitnessOptions, witness_option_groups
|
||||
@@ -55,7 +55,7 @@ class WitnessWorld(World):
|
||||
|
||||
item_name_to_id = {
|
||||
# ITEM_DATA doesn't have any event items in it
|
||||
name: cast(int, data.ap_code) for name, data in static_witness_items.ITEM_DATA.items()
|
||||
name: cast_not_none(data.ap_code) for name, data in static_witness_items.ITEM_DATA.items()
|
||||
}
|
||||
location_name_to_id = static_witness_locations.ALL_LOCATIONS_TO_ID
|
||||
item_name_groups = static_witness_items.ITEM_GROUPS
|
||||
@@ -336,7 +336,7 @@ class WitnessWorld(World):
|
||||
for item_name, hint in laser_hints.items():
|
||||
item_def = cast(DoorItemDefinition, static_witness_logic.ALL_ITEMS[item_name])
|
||||
self.laser_ids_to_hints[int(item_def.panel_id_hexes[0], 16)] = make_compact_hint_data(hint, self.player)
|
||||
already_hinted_locations.add(cast(Location, hint.location))
|
||||
already_hinted_locations.add(cast_not_none(hint.location))
|
||||
|
||||
# Audio Log Hints
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from math import floor
|
||||
from pkgutil import get_data
|
||||
from random import Random
|
||||
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Set, Tuple, TypeVar
|
||||
from typing import Any, Collection, Dict, FrozenSet, Iterable, List, Optional, Set, Tuple, TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
@@ -13,6 +13,11 @@ T = TypeVar("T")
|
||||
WitnessRule = FrozenSet[FrozenSet[str]]
|
||||
|
||||
|
||||
def cast_not_none(value: Optional[T]) -> T:
|
||||
assert value is not None
|
||||
return value
|
||||
|
||||
|
||||
def weighted_sample(world_random: Random, population: List[T], weights: List[float], k: int) -> List[T]:
|
||||
positions = range(len(population))
|
||||
indices: List[int] = []
|
||||
|
||||
@@ -15,7 +15,7 @@ from .data.item_definition_classes import (
|
||||
ProgressiveItemDefinition,
|
||||
WeightedItemDefinition,
|
||||
)
|
||||
from .data.utils import build_weighted_int_list
|
||||
from .data.utils import build_weighted_int_list, cast_not_none
|
||||
from .locations import WitnessPlayerLocations
|
||||
from .player_logic import WitnessPlayerLogic
|
||||
|
||||
@@ -200,7 +200,7 @@ class WitnessPlayerItems:
|
||||
"""
|
||||
return [
|
||||
# data.ap_code is guaranteed for a symbol definition
|
||||
cast(int, data.ap_code) for name, data in static_witness_items.ITEM_DATA.items()
|
||||
cast_not_none(data.ap_code) for name, data in static_witness_items.ITEM_DATA.items()
|
||||
if name not in self.item_data.keys() and data.definition.category is ItemCategory.SYMBOL
|
||||
]
|
||||
|
||||
@@ -211,8 +211,8 @@ class WitnessPlayerItems:
|
||||
if isinstance(item.definition, ProgressiveItemDefinition):
|
||||
# Note: we need to reference the static table here rather than the player-specific one because the child
|
||||
# items were removed from the pool when we pruned out all progression items not in the options.
|
||||
output[cast(int, item.ap_code)] = [cast(int, static_witness_items.ITEM_DATA[child_item].ap_code)
|
||||
for child_item in item.definition.child_item_names]
|
||||
output[cast_not_none(item.ap_code)] = [cast_not_none(static_witness_items.ITEM_DATA[child_item].ap_code)
|
||||
for child_item in item.definition.child_item_names]
|
||||
return output
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import Any, ClassVar, Dict, Iterable, List, Mapping, Union, cast
|
||||
from typing import Any, ClassVar, Dict, Iterable, List, Mapping, Union
|
||||
|
||||
from BaseClasses import CollectionState, Entrance, Item, Location, Region
|
||||
|
||||
@@ -7,6 +7,7 @@ from test.general import gen_steps, setup_multiworld
|
||||
from test.multiworld.test_multiworlds import MultiworldTestBase
|
||||
|
||||
from .. import WitnessWorld
|
||||
from ..data.utils import cast_not_none
|
||||
|
||||
|
||||
class WitnessTestBase(WorldTestBase):
|
||||
@@ -32,7 +33,7 @@ class WitnessTestBase(WorldTestBase):
|
||||
event_items = [item for item in self.multiworld.get_items() if item.name == item_name]
|
||||
self.assertTrue(event_items, f"Event item {item_name} does not exist.")
|
||||
|
||||
event_locations = [cast(Location, event_item.location) for event_item in event_items]
|
||||
event_locations = [cast_not_none(event_item.location) for event_item in event_items]
|
||||
|
||||
# Checking for an access dependency on an event item requires a bit of extra work,
|
||||
# as state.remove forces a sweep, which will pick up the event item again right after we tried to remove it.
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||