mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-14 11:33:47 -07:00
Compare commits
16 Commits
NewSoupVi-
...
NewSoupVi-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f6ffff674c | ||
|
|
507e051a5a | ||
|
|
b5bf9ed1d7 | ||
|
|
215eb7e473 | ||
|
|
f42233699a | ||
|
|
1bec68df4d | ||
|
|
d8576e72eb | ||
|
|
7265468e8d | ||
|
|
d07f36dedd | ||
|
|
364a1b71ec | ||
|
|
daee6d210f | ||
|
|
96be0071e6 | ||
|
|
ff8e1dfb47 | ||
|
|
7b21121df1 | ||
|
|
0fdc481082 | ||
|
|
92ca11b729 |
@@ -413,7 +413,8 @@ class CommonContext:
|
||||
await self.server.socket.close()
|
||||
if self.server_task is not None:
|
||||
await self.server_task
|
||||
self.ui.update_hints()
|
||||
if self.ui:
|
||||
self.ui.update_hints()
|
||||
|
||||
async def send_msgs(self, msgs: typing.List[typing.Any]) -> None:
|
||||
""" `msgs` JSON serializable """
|
||||
|
||||
2
Fill.py
2
Fill.py
@@ -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
|
||||
location.locked and location.item.player not in minimal_players):
|
||||
pool.append(location.item)
|
||||
state.remove(location.item)
|
||||
location.item = None
|
||||
if location in state.advancements:
|
||||
state.advancements.remove(location)
|
||||
state.remove(location.item)
|
||||
locations.append(location)
|
||||
if pool and locations:
|
||||
locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY)
|
||||
|
||||
22
Generate.py
22
Generate.py
@@ -279,22 +279,30 @@ def get_choice(option, root, value=None) -> Any:
|
||||
raise RuntimeError(f"All options specified in \"{option}\" are weighted as zero.")
|
||||
|
||||
|
||||
class SafeDict(dict):
|
||||
def __missing__(self, key):
|
||||
return '{' + key + '}'
|
||||
class SafeFormatter(string.Formatter):
|
||||
def get_value(self, key, args, kwargs):
|
||||
if isinstance(key, int):
|
||||
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):
|
||||
name_counter[name.lower()] += 1
|
||||
number = name_counter[name.lower()]
|
||||
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,
|
||||
NUMBER=(number if number > 1 else ''),
|
||||
player=player,
|
||||
PLAYER=(player if player > 1 else '')))
|
||||
|
||||
new_name = SafeFormatter().vformat(new_name, (), {"number": number,
|
||||
"NUMBER": (number if number > 1 else ''),
|
||||
"player": player,
|
||||
"PLAYER": (player if player > 1 else '')})
|
||||
# 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.
|
||||
new_name = new_name.strip()[:16].strip()
|
||||
|
||||
if new_name == "Archipelago":
|
||||
raise Exception(f"You cannot name yourself \"{new_name}\"")
|
||||
return new_name
|
||||
|
||||
2
Utils.py
2
Utils.py
@@ -47,7 +47,7 @@ class Version(typing.NamedTuple):
|
||||
return ".".join(str(item) for item in self)
|
||||
|
||||
|
||||
__version__ = "0.6.0"
|
||||
__version__ = "0.6.2"
|
||||
version_tuple = tuplize_version(__version__)
|
||||
|
||||
is_linux = sys.platform.startswith("linux")
|
||||
|
||||
@@ -35,6 +35,12 @@ def start_playing():
|
||||
@app.route('/games/<string:game>/info/<string:lang>')
|
||||
@cache.cached()
|
||||
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))
|
||||
|
||||
|
||||
@@ -52,6 +58,12 @@ def games():
|
||||
@app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
|
||||
@cache.cached()
|
||||
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))
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from typing import Dict, Union
|
||||
from docutils.core import publish_parts
|
||||
|
||||
import yaml
|
||||
from flask import redirect, render_template, request, Response
|
||||
from flask import redirect, render_template, request, Response, abort
|
||||
|
||||
import Options
|
||||
from Utils import local_path
|
||||
@@ -142,7 +142,10 @@ def weighted_options_old():
|
||||
@app.route("/games/<string:game>/weighted-options")
|
||||
@cache.cached()
|
||||
def weighted_options(game: str):
|
||||
return render_options_page("weightedOptions/weightedOptions.html", game, is_complex=True)
|
||||
try:
|
||||
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"])
|
||||
@@ -197,7 +200,10 @@ def generate_weighted_yaml(game: str):
|
||||
@app.route("/games/<string:game>/player-options")
|
||||
@cache.cached()
|
||||
def player_options(game: str):
|
||||
return render_options_page("playerOptions/playerOptions.html", game, is_complex=False)
|
||||
try:
|
||||
return render_options_page("playerOptions/playerOptions.html", game, is_complex=False)
|
||||
except KeyError:
|
||||
return abort(404)
|
||||
|
||||
|
||||
# YAML generator for player-options
|
||||
|
||||
@@ -42,10 +42,5 @@ window.addEventListener('load', () => {
|
||||
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>`;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -49,10 +49,5 @@ window.addEventListener('load', () => {
|
||||
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>`;
|
||||
});
|
||||
});
|
||||
|
||||
2
kvui.py
2
kvui.py
@@ -296,7 +296,7 @@ class SelectableLabel(RecycleDataViewBehavior, TooltipLabel):
|
||||
else:
|
||||
# Not a fan of the following few lines, but they work.
|
||||
temp = MarkupLabel(text=self.text).markup
|
||||
text = "".join(part for part in temp if not part.startswith(("[color", "[/color]", "[ref=", "[/ref]")))
|
||||
text = "".join(part for part in temp if not part.startswith("["))
|
||||
cmdinput = App.get_running_app().textinput
|
||||
if not cmdinput.text:
|
||||
input_text = get_input_text_from_response(text, App.get_running_app().last_autofillable_command)
|
||||
|
||||
@@ -110,6 +110,16 @@ class AutoLogicRegister(type):
|
||||
elif not item_name.startswith("__"):
|
||||
if hasattr(CollectionState, 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)
|
||||
return new_class
|
||||
|
||||
|
||||
@@ -88,7 +88,6 @@ processes = weakref.WeakSet()
|
||||
|
||||
|
||||
def launch_subprocess(func: Callable, name: str | None = None, args: Tuple[str, ...] = ()) -> None:
|
||||
global processes
|
||||
import multiprocessing
|
||||
process = multiprocessing.Process(target=func, name=name, args=args)
|
||||
process.start()
|
||||
|
||||
@@ -121,6 +121,7 @@ class ALTTPWeb(WebWorld):
|
||||
)
|
||||
|
||||
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):
|
||||
|
||||
@@ -41,6 +41,7 @@ class AquariaWeb(WebWorld):
|
||||
)
|
||||
|
||||
tutorials = [setup, setup_fr]
|
||||
game_info_languages = ["en", "fr"]
|
||||
|
||||
|
||||
class AquariaWorld(World):
|
||||
|
||||
@@ -31,6 +31,7 @@ class CliqueWebWorld(WebWorld):
|
||||
)
|
||||
|
||||
tutorials = [setup_en, setup_de]
|
||||
game_info_languages = ["en", "de"]
|
||||
|
||||
|
||||
class CliqueWorld(World):
|
||||
|
||||
@@ -34,6 +34,7 @@ class DLCqwebworld(WebWorld):
|
||||
["Deoxis"]
|
||||
)
|
||||
tutorials = [setup_en, setup_fr]
|
||||
game_info_languages = ["en", "fr"]
|
||||
|
||||
|
||||
class DLCqworld(World):
|
||||
|
||||
@@ -43,6 +43,7 @@ class FFMQWebWorld(WebWorld):
|
||||
)
|
||||
|
||||
tutorials = [setup_en, setup_fr]
|
||||
game_info_languages = ["en", "fr"]
|
||||
|
||||
|
||||
class FFMQWorld(World):
|
||||
|
||||
@@ -130,6 +130,7 @@ class OOTWeb(WebWorld):
|
||||
|
||||
tutorials = [setup, setup_fr, setup_de]
|
||||
option_groups = oot_option_groups
|
||||
game_info_languages = ["en", "de"]
|
||||
|
||||
|
||||
class OOTWorld(World):
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
# 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
|
||||
|
||||
### Features
|
||||
@@ -14,9 +22,6 @@ _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
|
||||
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
|
||||
|
||||
|
||||
@@ -2414,6 +2414,7 @@ def door_shuffle(world, multiworld, player, badges, badge_locs):
|
||||
loc.place_locked_item(badge)
|
||||
|
||||
state = multiworld.state.copy()
|
||||
state.allow_partial_entrances = True
|
||||
for item, data in item_table.items():
|
||||
if (data.id or item in poke_data.pokemon_data) and data.classification == ItemClassification.progression \
|
||||
and ("Badge" not in item or world.options.badgesanity):
|
||||
|
||||
@@ -41,6 +41,7 @@ class Starcraft2WebWorld(WebWorld):
|
||||
)
|
||||
|
||||
tutorials = [setup_en, setup_fr]
|
||||
game_info_languages = ["en", "fr"]
|
||||
|
||||
|
||||
class SC2World(World):
|
||||
|
||||
@@ -88,6 +88,8 @@ Notes:
|
||||
See the [Archipelago Plando Guide](../../../tutorial/Archipelago/plando/en) for more information on Plando and Connection Plando.
|
||||
|
||||
## 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 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 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 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.
|
||||
|
||||
@@ -330,7 +330,11 @@ def pair_portals(world: "TunicWorld", regions: Dict[str, Region]) -> Dict[Portal
|
||||
else:
|
||||
if not portal2:
|
||||
raise Exception(f"Could not find entrance named {p_exit} for "
|
||||
f"plando connections in {player_name}'s YAML.")
|
||||
f"plando connections in {player_name}'s YAML.\n"
|
||||
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)
|
||||
|
||||
# update the traversal chart to say you can get from portal1's region to portal2's and vice versa
|
||||
|
||||
@@ -617,7 +617,7 @@ if is_easter_time():
|
||||
easter_special_option_group = OptionGroup("EASTER SPECIAL", [
|
||||
EasterEggHunt,
|
||||
])
|
||||
witness_option_groups.insert(2, easter_special_option_group)
|
||||
witness_option_groups = [easter_special_option_group, *witness_option_groups]
|
||||
else:
|
||||
silly_options_group = next(group for group in witness_option_groups if group.name == "Silly Options")
|
||||
silly_options_group.options.append(EasterEggHunt)
|
||||
|
||||
Reference in New Issue
Block a user