Compare commits

..

1 Commits

23 changed files with 33 additions and 78 deletions

View File

@@ -413,8 +413,7 @@ class CommonContext:
await self.server.socket.close() await self.server.socket.close()
if self.server_task is not None: if self.server_task is not None:
await self.server_task await self.server_task
if self.ui: self.ui.update_hints()
self.ui.update_hints()
async def send_msgs(self, msgs: typing.List[typing.Any]) -> None: async def send_msgs(self, msgs: typing.List[typing.Any]) -> None:
""" `msgs` JSON serializable """ """ `msgs` JSON serializable """

View File

@@ -348,10 +348,10 @@ def accessibility_corrections(multiworld: MultiWorld, state: CollectionState, lo
if (location.item is not None and location.item.advancement and location.address is not None and not if (location.item is not None and location.item.advancement and location.address is not None and not
location.locked and location.item.player not in minimal_players): location.locked and location.item.player not in minimal_players):
pool.append(location.item) pool.append(location.item)
state.remove(location.item)
location.item = None location.item = None
if location in state.advancements: if location in state.advancements:
state.advancements.remove(location) state.advancements.remove(location)
state.remove(location.item)
locations.append(location) locations.append(location)
if pool and locations: if pool and locations:
locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY) locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY)

View File

@@ -279,30 +279,22 @@ def get_choice(option, root, value=None) -> Any:
raise RuntimeError(f"All options specified in \"{option}\" are weighted as zero.") raise RuntimeError(f"All options specified in \"{option}\" are weighted as zero.")
class SafeFormatter(string.Formatter): class SafeDict(dict):
def get_value(self, key, args, kwargs): def __missing__(self, key):
if isinstance(key, int): return '{' + key + '}'
if key < len(args):
return args[key]
else:
return "{" + str(key) + "}"
else:
return kwargs.get(key, "{" + key + "}")
def handle_name(name: str, player: int, name_counter: Counter): def handle_name(name: str, player: int, name_counter: Counter):
name_counter[name.lower()] += 1 name_counter[name.lower()] += 1
number = name_counter[name.lower()] number = name_counter[name.lower()]
new_name = "%".join([x.replace("%number%", "{number}").replace("%player%", "{player}") for x in name.split("%%")]) new_name = "%".join([x.replace("%number%", "{number}").replace("%player%", "{player}") for x in name.split("%%")])
new_name = string.Formatter().vformat(new_name, (), SafeDict(number=number,
new_name = SafeFormatter().vformat(new_name, (), {"number": number, NUMBER=(number if number > 1 else ''),
"NUMBER": (number if number > 1 else ''), player=player,
"player": player, PLAYER=(player if player > 1 else '')))
"PLAYER": (player if player > 1 else '')})
# Run .strip twice for edge case where after the initial .slice new_name has a leading whitespace. # Run .strip twice for edge case where after the initial .slice new_name has a leading whitespace.
# Could cause issues for some clients that cannot handle the additional whitespace. # Could cause issues for some clients that cannot handle the additional whitespace.
new_name = new_name.strip()[:16].strip() new_name = new_name.strip()[:16].strip()
if new_name == "Archipelago": if new_name == "Archipelago":
raise Exception(f"You cannot name yourself \"{new_name}\"") raise Exception(f"You cannot name yourself \"{new_name}\"")
return new_name return new_name

View File

@@ -47,7 +47,7 @@ class Version(typing.NamedTuple):
return ".".join(str(item) for item in self) return ".".join(str(item) for item in self)
__version__ = "0.6.2" __version__ = "0.6.0"
version_tuple = tuplize_version(__version__) version_tuple = tuplize_version(__version__)
is_linux = sys.platform.startswith("linux") is_linux = sys.platform.startswith("linux")

View File

@@ -35,12 +35,6 @@ def start_playing():
@app.route('/games/<string:game>/info/<string:lang>') @app.route('/games/<string:game>/info/<string:lang>')
@cache.cached() @cache.cached()
def game_info(game, lang): def game_info(game, lang):
try:
world = AutoWorldRegister.world_types[game]
if lang not in world.web.game_info_languages:
raise KeyError("Sorry, this game's info page is not available in that language yet.")
except KeyError:
return abort(404)
return render_template('gameInfo.html', game=game, lang=lang, theme=get_world_theme(game)) return render_template('gameInfo.html', game=game, lang=lang, theme=get_world_theme(game))
@@ -58,12 +52,6 @@ def games():
@app.route('/tutorial/<string:game>/<string:file>/<string:lang>') @app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
@cache.cached() @cache.cached()
def tutorial(game, file, lang): def tutorial(game, file, lang):
try:
world = AutoWorldRegister.world_types[game]
if lang not in [tut.link.split("/")[1] for tut in world.web.tutorials]:
raise KeyError("Sorry, the tutorial is not available in that language yet.")
except KeyError:
return abort(404)
return render_template("tutorial.html", game=game, file=file, lang=lang, theme=get_world_theme(game)) return render_template("tutorial.html", game=game, file=file, lang=lang, theme=get_world_theme(game))

View File

@@ -6,7 +6,7 @@ from typing import Dict, Union
from docutils.core import publish_parts from docutils.core import publish_parts
import yaml import yaml
from flask import redirect, render_template, request, Response, abort from flask import redirect, render_template, request, Response
import Options import Options
from Utils import local_path from Utils import local_path
@@ -142,10 +142,7 @@ def weighted_options_old():
@app.route("/games/<string:game>/weighted-options") @app.route("/games/<string:game>/weighted-options")
@cache.cached() @cache.cached()
def weighted_options(game: str): def weighted_options(game: str):
try: return render_options_page("weightedOptions/weightedOptions.html", game, is_complex=True)
return render_options_page("weightedOptions/weightedOptions.html", game, is_complex=True)
except KeyError:
return abort(404)
@app.route("/games/<string:game>/generate-weighted-yaml", methods=["POST"]) @app.route("/games/<string:game>/generate-weighted-yaml", methods=["POST"])
@@ -200,10 +197,7 @@ def generate_weighted_yaml(game: str):
@app.route("/games/<string:game>/player-options") @app.route("/games/<string:game>/player-options")
@cache.cached() @cache.cached()
def player_options(game: str): def player_options(game: str):
try: return render_options_page("playerOptions/playerOptions.html", game, is_complex=False)
return render_options_page("playerOptions/playerOptions.html", game, is_complex=False)
except KeyError:
return abort(404)
# YAML generator for player-options # YAML generator for player-options

View File

@@ -42,5 +42,10 @@ window.addEventListener('load', () => {
scrollTarget?.scrollIntoView(); scrollTarget?.scrollIntoView();
} }
}); });
}).catch((error) => {
console.error(error);
gameInfo.innerHTML =
`<h2>This page is out of logic!</h2>
<h3>Click <a href="${window.location.origin}">here</a> to return to safety.</h3>`;
}); });
}); });

View File

@@ -49,5 +49,10 @@ window.addEventListener('load', () => {
scrollTarget?.scrollIntoView(); scrollTarget?.scrollIntoView();
} }
}); });
}).catch((error) => {
console.error(error);
tutorialWrapper.innerHTML =
`<h2>This page is out of logic!</h2>
<h3>Click <a href="${window.location.origin}/tutorial">here</a> to return to safety.</h3>`;
}); });
}); });

View File

@@ -296,7 +296,7 @@ class SelectableLabel(RecycleDataViewBehavior, TooltipLabel):
else: else:
# Not a fan of the following few lines, but they work. # Not a fan of the following few lines, but they work.
temp = MarkupLabel(text=self.text).markup temp = MarkupLabel(text=self.text).markup
text = "".join(part for part in temp if not part.startswith("[")) text = "".join(part for part in temp if not part.startswith(("[color", "[/color]", "[ref=", "[/ref]")))
cmdinput = App.get_running_app().textinput cmdinput = App.get_running_app().textinput
if not cmdinput.text: if not cmdinput.text:
input_text = get_input_text_from_response(text, App.get_running_app().last_autofillable_command) input_text = get_input_text_from_response(text, App.get_running_app().last_autofillable_command)

View File

@@ -110,16 +110,6 @@ class AutoLogicRegister(type):
elif not item_name.startswith("__"): elif not item_name.startswith("__"):
if hasattr(CollectionState, item_name): if hasattr(CollectionState, item_name):
raise Exception(f"Name conflict on Logic Mixin {name} trying to overwrite {item_name}") raise Exception(f"Name conflict on Logic Mixin {name} trying to overwrite {item_name}")
assert callable(function) or "init_mixin" in dct, (
f"{name} defined class variable {item_name} without also having init_mixin.\n\n"
"Explanation:\n"
"Class variables that will be mutated need to be inintialized as instance variables in init_mixin.\n"
"If your LogicMixin variables aren't actually mutable / you don't intend to mutate them, "
"there is no point in using LogixMixin.\n"
"LogicMixin exists to track custom state variables that change when items are collected/removed."
)
setattr(CollectionState, item_name, function) setattr(CollectionState, item_name, function)
return new_class return new_class

View File

@@ -88,6 +88,7 @@ processes = weakref.WeakSet()
def launch_subprocess(func: Callable, name: str | None = None, args: Tuple[str, ...] = ()) -> None: def launch_subprocess(func: Callable, name: str | None = None, args: Tuple[str, ...] = ()) -> None:
global processes
import multiprocessing import multiprocessing
process = multiprocessing.Process(target=func, name=name, args=args) process = multiprocessing.Process(target=func, name=name, args=args)
process.start() process.start()

View File

@@ -121,7 +121,6 @@ class ALTTPWeb(WebWorld):
) )
tutorials = [setup_en, setup_de, setup_es, setup_fr, msu, msu_es, msu_fr, plando, oof_sound] tutorials = [setup_en, setup_de, setup_es, setup_fr, msu, msu_es, msu_fr, plando, oof_sound]
game_info_languages = ["en", "fr"]
class ALTTPWorld(World): class ALTTPWorld(World):

View File

@@ -41,7 +41,6 @@ class AquariaWeb(WebWorld):
) )
tutorials = [setup, setup_fr] tutorials = [setup, setup_fr]
game_info_languages = ["en", "fr"]
class AquariaWorld(World): class AquariaWorld(World):

View File

@@ -31,7 +31,6 @@ class CliqueWebWorld(WebWorld):
) )
tutorials = [setup_en, setup_de] tutorials = [setup_en, setup_de]
game_info_languages = ["en", "de"]
class CliqueWorld(World): class CliqueWorld(World):

View File

@@ -34,7 +34,6 @@ class DLCqwebworld(WebWorld):
["Deoxis"] ["Deoxis"]
) )
tutorials = [setup_en, setup_fr] tutorials = [setup_en, setup_fr]
game_info_languages = ["en", "fr"]
class DLCqworld(World): class DLCqworld(World):

View File

@@ -43,7 +43,6 @@ class FFMQWebWorld(WebWorld):
) )
tutorials = [setup_en, setup_fr] tutorials = [setup_en, setup_fr]
game_info_languages = ["en", "fr"]
class FFMQWorld(World): class FFMQWorld(World):

View File

@@ -130,7 +130,6 @@ class OOTWeb(WebWorld):
tutorials = [setup, setup_fr, setup_de] tutorials = [setup, setup_fr, setup_de]
option_groups = oot_option_groups option_groups = oot_option_groups
game_info_languages = ["en", "de"]
class OOTWorld(World): class OOTWorld(World):

View File

@@ -1,11 +1,3 @@
# 2.4.1
### Fixes
- Fixed handling of shuffle option for badges/HMs in the case that the player sets those items to nonlocal or uses
plando to put an item in one of those locations, or in the case that fill gets itself stuck on these items and has to
retry.
# 2.4.0 # 2.4.0
### Features ### Features
@@ -22,6 +14,9 @@ _not_ used for logical access (the seed will never require you to catch somethin
- Now excludes the location "Navel Rock Top - Hidden Item Sacred Ash" if your goal is Champion and you didn't randomize - Now excludes the location "Navel Rock Top - Hidden Item Sacred Ash" if your goal is Champion and you didn't randomize
event tickets. event tickets.
- Fixed handling of shuffle option for badges/HMs in the case that the player sets those items to nonlocal or uses
plando to put an item in one of those locations, or in the case that fill gets itself stuck on these items and has to
retry.
# 2.3.0 # 2.3.0

View File

@@ -2414,7 +2414,6 @@ def door_shuffle(world, multiworld, player, badges, badge_locs):
loc.place_locked_item(badge) loc.place_locked_item(badge)
state = multiworld.state.copy() state = multiworld.state.copy()
state.allow_partial_entrances = True
for item, data in item_table.items(): for item, data in item_table.items():
if (data.id or item in poke_data.pokemon_data) and data.classification == ItemClassification.progression \ if (data.id or item in poke_data.pokemon_data) and data.classification == ItemClassification.progression \
and ("Badge" not in item or world.options.badgesanity): and ("Badge" not in item or world.options.badgesanity):

View File

@@ -41,7 +41,6 @@ class Starcraft2WebWorld(WebWorld):
) )
tutorials = [setup_en, setup_fr] tutorials = [setup_en, setup_fr]
game_info_languages = ["en", "fr"]
class SC2World(World): class SC2World(World):

View File

@@ -88,8 +88,6 @@ Notes:
See the [Archipelago Plando Guide](../../../tutorial/Archipelago/plando/en) for more information on Plando and Connection Plando. See the [Archipelago Plando Guide](../../../tutorial/Archipelago/plando/en) for more information on Plando and Connection Plando.
## Is there anything else I should know? ## Is there anything else I should know?
- You can go to [The TUNIC Randomizer Website](https://rando.tunic.run/) for a list of randomizer features as well as some helpful tips. You can go to [The TUNIC Randomizer Website](https://rando.tunic.run/) for a list of randomizer features as well as some helpful tips.
- You can use the Fairy Seeking Spell (ULU RDR) to locate the nearest unchecked location. You can use the Fairy Seeking Spell (ULU RDR) to locate the nearest unchecked location.
- You can use the Entrance Seeking Spell (RDR ULU) to locate the nearest unused entrance. You can use the Entrance Seeking Spell (RDR ULU) to locate the nearest unused entrance.
- Death Link can be toggled in game, and it can be set to receive traps instead of deaths.
- Trap Link can be toggled in-game as well, which makes it so other players with Trap Link enabled will share the effects of traps with you, and vice versa. Trap Link functions cross-game, but only with other games that have Trap Link implemented, and only some traps can be shared, depending on the game.

View File

@@ -330,11 +330,7 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal
else: else:
if not portal2: if not portal2:
raise Exception(f"Could not find entrance named {p_exit} for " raise Exception(f"Could not find entrance named {p_exit} for "
f"plando connections in {player_name}'s YAML.\n" f"plando connections in {player_name}'s YAML.")
f"If you are using Universal Tracker, the most likely reason for this error "
f"is that the host generated with a newer version of the APWorld.\n"
f"Please check the TUNIC Randomizer Github and place the newest APWorld in your "
f"custom_worlds folder, and remove the one in lib/worlds if there is one there.")
dead_ends.remove(portal2) dead_ends.remove(portal2)
# update the traversal chart to say you can get from portal1's region to portal2's and vice versa # update the traversal chart to say you can get from portal1's region to portal2's and vice versa

View File

@@ -617,7 +617,7 @@ if is_easter_time():
easter_special_option_group = OptionGroup("EASTER SPECIAL", [ easter_special_option_group = OptionGroup("EASTER SPECIAL", [
EasterEggHunt, EasterEggHunt,
]) ])
witness_option_groups = [easter_special_option_group, *witness_option_groups] witness_option_groups.insert(2, easter_special_option_group)
else: else:
silly_options_group = next(group for group in witness_option_groups if group.name == "Silly Options") silly_options_group = next(group for group in witness_option_groups if group.name == "Silly Options")
silly_options_group.options.append(EasterEggHunt) silly_options_group.options.append(EasterEggHunt)