Skip to content

Commit

Permalink
Merge branch 'main' into refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
tioui authored Nov 17, 2024
2 parents f253b9b + 66314de commit 8329397
Show file tree
Hide file tree
Showing 52 changed files with 527 additions and 259 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -58,7 +58,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
uses: github/codeql-action/autobuild@v3

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
Expand All @@ -72,4 +72,4 @@ jobs:
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
11 changes: 5 additions & 6 deletions Launcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,15 @@
from shutil import which
from typing import Callable, Optional, Sequence, Tuple, Union

import Utils
import settings
from worlds.LauncherComponents import Component, components, Type, SuffixIdentifier, icon_paths

if __name__ == "__main__":
import ModuleUpdate
ModuleUpdate.update()

from Utils import is_frozen, user_path, local_path, init_logging, open_filename, messagebox, \
is_windows, is_macos, is_linux
import settings
import Utils
from Utils import (init_logging, is_frozen, is_linux, is_macos, is_windows, local_path, messagebox, open_filename,
user_path)
from worlds.LauncherComponents import Component, components, icon_paths, SuffixIdentifier, Type


def open_host_yaml():
Expand Down
4 changes: 2 additions & 2 deletions Options.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from schema import And, Optional, Or, Schema
from typing_extensions import Self

from Utils import get_fuzzy_results, is_iterable_except_str, output_path
from Utils import get_file_safe_name, get_fuzzy_results, is_iterable_except_str, output_path

if typing.TYPE_CHECKING:
from BaseClasses import MultiWorld, PlandoOptions
Expand Down Expand Up @@ -1531,7 +1531,7 @@ def yaml_dump_scalar(scalar) -> str:

del file_data

with open(os.path.join(target_folder, game_name + ".yaml"), "w", encoding="utf-8-sig") as f:
with open(os.path.join(target_folder, get_file_safe_name(game_name) + ".yaml"), "w", encoding="utf-8-sig") as f:
f.write(res)


Expand Down
3 changes: 2 additions & 1 deletion WebHost.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# in case app gets imported by something like gunicorn
import Utils
import settings
from Utils import get_file_safe_name

if typing.TYPE_CHECKING:
from flask import Flask
Expand Down Expand Up @@ -71,7 +72,7 @@ def create_ordered_tutorials_file() -> typing.List[typing.Dict[str, typing.Any]]
shutil.rmtree(base_target_path, ignore_errors=True)
for game, world in worlds.items():
# copy files from world's docs folder to the generated folder
target_path = os.path.join(base_target_path, game)
target_path = os.path.join(base_target_path, get_file_safe_name(game))
os.makedirs(target_path, exist_ok=True)

if world.zip_path:
Expand Down
3 changes: 2 additions & 1 deletion WebHostLib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pony.flask import Pony
from werkzeug.routing import BaseConverter

from Utils import title_sorted
from Utils import title_sorted, get_file_safe_name

UPLOAD_FOLDER = os.path.relpath('uploads')
LOGS_FOLDER = os.path.relpath('logs')
Expand All @@ -20,6 +20,7 @@

app.jinja_env.filters['any'] = any
app.jinja_env.filters['all'] = all
app.jinja_env.filters['get_file_safe_name'] = get_file_safe_name

app.config["SELFHOST"] = True # application process is in charge of running the websites
app.config["GENERATORS"] = 8 # maximum concurrent world gens
Expand Down
2 changes: 1 addition & 1 deletion WebHostLib/templates/gameInfo.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

{% block body %}
{% include 'header/'+theme+'Header.html' %}
<div id="game-info" class="markdown" data-lang="{{ lang }}" data-game="{{ game }}">
<div id="game-info" class="markdown" data-lang="{{ lang }}" data-game="{{ game | get_file_safe_name }}">
<!-- Populated my JS / MD -->
</div>
{% endblock %}
4 changes: 4 additions & 0 deletions WebHostLib/templates/genericTracker.html
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
<td>
{% if hint.finding_player == player %}
<b>{{ player_names_with_alias[(team, hint.finding_player)] }}</b>
{% elif get_slot_info(team, hint.finding_player).type == 2 %}
<i>{{ player_names_with_alias[(team, hint.finding_player)] }}</i>
{% else %}
<a href="{{ url_for("get_player_tracker", tracker=room.tracker, tracked_team=team, tracked_player=hint.finding_player) }}">
{{ player_names_with_alias[(team, hint.finding_player)] }}
Expand All @@ -107,6 +109,8 @@
<td>
{% if hint.receiving_player == player %}
<b>{{ player_names_with_alias[(team, hint.receiving_player)] }}</b>
{% elif get_slot_info(team, hint.receiving_player).type == 2 %}
<i>{{ player_names_with_alias[(team, hint.receiving_player)] }}</i>
{% else %}
<a href="{{ url_for("get_player_tracker", tracker=room.tracker, tracked_team=team, tracked_player=hint.receiving_player) }}">
{{ player_names_with_alias[(team, hint.receiving_player)] }}
Expand Down
16 changes: 14 additions & 2 deletions WebHostLib/templates/multitrackerHintTable.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,20 @@
)
-%}
<tr>
<td>{{ player_names_with_alias[(team, hint.finding_player)] }}</td>
<td>{{ player_names_with_alias[(team, hint.receiving_player)] }}</td>
<td>
{% if get_slot_info(team, hint.finding_player).type == 2 %}
<i>{{ player_names_with_alias[(team, hint.finding_player)] }}</i>
{% else %}
{{ player_names_with_alias[(team, hint.finding_player)] }}
{% endif %}
</td>
<td>
{% if get_slot_info(team, hint.receiving_player).type == 2 %}
<i>{{ player_names_with_alias[(team, hint.receiving_player)] }}</i>
{% else %}
{{ player_names_with_alias[(team, hint.receiving_player)] }}
{% endif %}
</td>
<td>{{ item_id_to_name[games[(team, hint.receiving_player)]][hint.item] }}</td>
<td>{{ location_id_to_name[games[(team, hint.finding_player)]][hint.location] }}</td>
<td>{{ games[(team, hint.finding_player)] }}</td>
Expand Down
2 changes: 1 addition & 1 deletion WebHostLib/templates/playerOptions/playerOptions.html
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ <h1>Player Options</h1>
A list of all games you have generated can be found on the <a href="/user-content">User Content Page</a>.
<br />
You may also download the
<a href="/static/generated/configs/{{ world_name }}.yaml">template file for this game</a>.
<a href="/static/generated/configs/{{ world_name | get_file_safe_name }}.yaml">template file for this game</a>.
</p>

<form id="options-form" method="post" enctype="application/x-www-form-urlencoded" action="generate-yaml">
Expand Down
2 changes: 1 addition & 1 deletion WebHostLib/templates/tutorial.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
{% endblock %}

{% block body %}
<div id="tutorial-wrapper" class="markdown" data-game="{{ game }}" data-file="{{ file }}" data-lang="{{ lang }}">
<div id="tutorial-wrapper" class="markdown" data-game="{{ game | get_file_safe_name }}" data-file="{{ file | get_file_safe_name }}" data-lang="{{ lang }}">
<!-- Content generated by JavaScript -->
</div>
{% endblock %}
6 changes: 5 additions & 1 deletion WebHostLib/tracker.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ def render_generic_tracker(tracker_data: TrackerData, team: int, player: int) ->
template_name_or_list="genericTracker.html",
game_specific_tracker=game in _player_trackers,
room=tracker_data.room,
get_slot_info=tracker_data.get_slot_info,
team=team,
player=player,
player_name=tracker_data.get_room_long_player_names()[team, player],
Expand All @@ -446,6 +447,7 @@ def render_generic_multiworld_tracker(tracker_data: TrackerData, enabled_tracker
enabled_trackers=enabled_trackers,
current_tracker="Generic",
room=tracker_data.room,
get_slot_info=tracker_data.get_slot_info,
all_slots=tracker_data.get_all_slots(),
room_players=tracker_data.get_all_players(),
locations=tracker_data.get_room_locations(),
Expand Down Expand Up @@ -497,7 +499,7 @@ def render_Factorio_multiworld_tracker(tracker_data: TrackerData, enabled_tracke
(team, player): collections.Counter({
tracker_data.item_id_to_name["Factorio"][item_id]: count
for item_id, count in tracker_data.get_player_inventory_counts(team, player).items()
}) for team, players in tracker_data.get_all_slots().items() for player in players
}) for team, players in tracker_data.get_all_players().items() for player in players
if tracker_data.get_player_game(team, player) == "Factorio"
}

Expand All @@ -506,6 +508,7 @@ def render_Factorio_multiworld_tracker(tracker_data: TrackerData, enabled_tracke
enabled_trackers=enabled_trackers,
current_tracker="Factorio",
room=tracker_data.room,
get_slot_info=tracker_data.get_slot_info,
all_slots=tracker_data.get_all_slots(),
room_players=tracker_data.get_all_players(),
locations=tracker_data.get_room_locations(),
Expand Down Expand Up @@ -638,6 +641,7 @@ def render_ALinkToThePast_multiworld_tracker(tracker_data: TrackerData, enabled_
enabled_trackers=enabled_trackers,
current_tracker="A Link to the Past",
room=tracker_data.room,
get_slot_info=tracker_data.get_slot_info,
all_slots=tracker_data.get_all_slots(),
room_players=tracker_data.get_all_players(),
locations=tracker_data.get_room_locations(),
Expand Down
6 changes: 3 additions & 3 deletions data/options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@
name: Player{number}

# Used to describe your yaml. Useful if you have multiple files.
description: Default {{ game }} Template
description: {{ yaml_dump("Default %s Template" % game) }}

game: {{ game }}
game: {{ yaml_dump(game) }}
requires:
version: {{ __version__ }} # Version of Archipelago required for this yaml to work as expected.

Expand All @@ -44,7 +44,7 @@ requires:
{%- endfor -%}
{% endmacro %}

{{ game }}:
{{ yaml_dump(game) }}:
{%- for group_name, group_options in option_groups.items() %}
# {{ group_name }}

Expand Down
2 changes: 1 addition & 1 deletion docs/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@
/worlds/shivers/ @GodlFire

# A Short Hike
/worlds/shorthike/ @chandler05
/worlds/shorthike/ @chandler05 @BrandenEK

# Sonic Adventure 2 Battle
/worlds/sa2b/ @PoryGone @RaspberrySpace
Expand Down
1 change: 1 addition & 0 deletions test/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import settings

warnings.simplefilter("always")
warnings.filterwarnings(action="ignore", category=DeprecationWarning, module="s2clientprotocol")
settings.no_gui = True
settings.skip_autosave = True

Expand Down
55 changes: 55 additions & 0 deletions test/options/test_generate_templates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import unittest

from pathlib import Path
from tempfile import TemporaryDirectory
from typing import TYPE_CHECKING, Dict, Type
from Utils import parse_yaml

if TYPE_CHECKING:
from worlds.AutoWorld import World


class TestGenerateYamlTemplates(unittest.TestCase):
old_world_types: Dict[str, Type["World"]]

def setUp(self) -> None:
import worlds.AutoWorld

self.old_world_types = worlds.AutoWorld.AutoWorldRegister.world_types

def tearDown(self) -> None:
import worlds.AutoWorld

worlds.AutoWorld.AutoWorldRegister.world_types = self.old_world_types

if "World: with colon" in worlds.AutoWorld.AutoWorldRegister.world_types:
del worlds.AutoWorld.AutoWorldRegister.world_types["World: with colon"]

def test_name_with_colon(self) -> None:
from Options import generate_yaml_templates
from worlds.AutoWorld import AutoWorldRegister
from worlds.AutoWorld import World

class WorldWithColon(World):
game = "World: with colon"
item_name_to_id = {}
location_name_to_id = {}

AutoWorldRegister.world_types = {WorldWithColon.game: WorldWithColon}
with TemporaryDirectory(f"archipelago_{__name__}") as temp_dir:
generate_yaml_templates(temp_dir)
path: Path
for path in Path(temp_dir).iterdir():
self.assertTrue(path.is_file())
self.assertTrue(path.suffix == ".yaml")
with path.open(encoding="utf-8") as f:
try:
data = parse_yaml(f)
except:
f.seek(0)
print(f"Error in {path.name}:\n{f.read()}")
raise
self.assertIn("game", data)
self.assertIn(":", data["game"])
self.assertIn(data["game"], data)
self.assertIsInstance(data[data["game"]], dict)
10 changes: 8 additions & 2 deletions test/webhost/test_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,16 @@ def test_has_tutorial(self):
def test_has_game_info(self):
for game_name, world_type in AutoWorldRegister.world_types.items():
if not world_type.hidden:
target_path = Utils.local_path("WebHostLib", "static", "generated", "docs", game_name)
safe_name = Utils.get_file_safe_name(game_name)
target_path = Utils.local_path("WebHostLib", "static", "generated", "docs", safe_name)
for game_info_lang in world_type.web.game_info_languages:
with self.subTest(game_name):
self.assertTrue(
os.path.isfile(Utils.local_path(target_path, f'{game_info_lang}_{game_name}.md')),
safe_name == game_name or
not os.path.isfile(Utils.local_path(target_path, f'{game_info_lang}_{game_name}.md')),
f'Info docs have be named <lang>_{safe_name}.md for {game_name}.'
)
self.assertTrue(
os.path.isfile(Utils.local_path(target_path, f'{game_info_lang}_{safe_name}.md')),
f'{game_name} missing game info file for "{game_info_lang}" language.'
)
12 changes: 9 additions & 3 deletions worlds/LauncherComponents.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,16 @@ def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, path

apworld_path = pathlib.Path(apworld_src)

module_name = pathlib.Path(apworld_path.name).stem
try:
import zipfile
zipfile.ZipFile(apworld_path).open(module_name + "/__init__.py")
zip = zipfile.ZipFile(apworld_path)
directories = [f.filename.strip('/') for f in zip.filelist if f.CRC == 0 and f.file_size == 0 and f.filename.count('/') == 1]
if len(directories) == 1 and directories[0] in apworld_path.stem:
module_name = directories[0]
apworld_name = module_name + ".apworld"
else:
raise Exception("APWorld appears to be invalid or damaged. (expected a single directory)")
zip.open(module_name + "/__init__.py")
except ValueError as e:
raise Exception("Archive appears invalid or damaged.") from e
except KeyError as e:
Expand All @@ -122,7 +128,7 @@ def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, path
# TODO: run generic test suite over the apworld.
# TODO: have some kind of version system to tell from metadata if the apworld should be compatible.

target = pathlib.Path(worlds.user_folder) / apworld_path.name
target = pathlib.Path(worlds.user_folder) / apworld_name
import shutil
shutil.copyfile(apworld_path, target)

Expand Down
17 changes: 14 additions & 3 deletions worlds/cv64/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ def on_package(self, ctx: "BizHawkClientContext", cmd: str, args: dict) -> None:
self.received_deathlinks += 1
if "cause" in args["data"]:
cause = args["data"]["cause"]
if len(cause) > 88:
cause = cause[0x00:0x89]
# Truncate the death cause message at 120 characters.
if len(cause) > 120:
cause = cause[0:120]
else:
cause = f"{args['data']['source']} killed you!"
self.death_causes.append(cause)
Expand Down Expand Up @@ -146,8 +147,18 @@ async def game_watcher(self, ctx: "BizHawkClientContext") -> None:
text_color = bytearray([0xA2, 0x0B])
else:
text_color = bytearray([0xA2, 0x02])

# Get the item's player's name. If it's longer than 40 characters, truncate it at 40.
# 35 should be the max number of characters in a server player name right now (16 for the original
# name + 16 for the alias + 3 for the added parenthesis and space), but if it ever goes higher it
# should be future-proofed now. No need to truncate CV64 items names because its longest item name
# gets nowhere near the limit.
player_name = ctx.player_names[next_item.player]
if len(player_name) > 40:
player_name = player_name[0:40]

received_text, num_lines = cv64_text_wrap(f"{ctx.item_names.lookup_in_game(next_item.item)}\n"
f"from {ctx.player_names[next_item.player]}", 96)
f"from {player_name}", 96)
await bizhawk.guarded_write(ctx.bizhawk_ctx,
[(0x389BE1, [next_item.item & 0xFF], "RDRAM"),
(0x18C0A8, text_color + cv64_string_to_bytearray(received_text, False),
Expand Down
Loading

0 comments on commit 8329397

Please sign in to comment.