Skip to content

Commit c74ccd7

Browse files
committed
# Conflicts:
# worlds/tunic/__init__.py # worlds/tunic/combat_logic.py # worlds/tunic/er_data.py # worlds/tunic/er_rules.py # worlds/tunic/er_scripts.py
1 parent 2e8bce7 commit c74ccd7

File tree

147 files changed

+9789
-2466
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

147 files changed

+9789
-2466
lines changed

.github/workflows/scan-build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ jobs:
4040
run: |
4141
wget https://apt.llvm.org/llvm.sh
4242
chmod +x ./llvm.sh
43-
sudo ./llvm.sh 17
43+
sudo ./llvm.sh 19
4444
- name: Install scan-build command
4545
run: |
46-
sudo apt install clang-tools-17
46+
sudo apt install clang-tools-19
4747
- name: Get a recent python
4848
uses: actions/setup-python@v5
4949
with:
@@ -56,7 +56,7 @@ jobs:
5656
- name: scan-build
5757
run: |
5858
source venv/bin/activate
59-
scan-build-17 --status-bugs -o scan-build-reports -disable-checker deadcode.DeadStores python setup.py build -y
59+
scan-build-19 --status-bugs -o scan-build-reports -disable-checker deadcode.DeadStores python setup.py build -y
6060
- name: Store report
6161
if: failure()
6262
uses: actions/upload-artifact@v4

.github/workflows/unittests.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ jobs:
5353
- name: Install dependencies
5454
run: |
5555
python -m pip install --upgrade pip
56-
pip install pytest "pytest-subtests<0.14.0" pytest-xdist
56+
pip install pytest pytest-subtests pytest-xdist
5757
python ModuleUpdate.py --yes --force --append "WebHostLib/requirements.txt"
5858
python Launcher.py --update_settings # make sure host.yaml exists for tests
5959
- name: Unittests

MultiServer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1914,7 +1914,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
19141914
hint = ctx.get_hint(client.team, player, location)
19151915
if not hint:
19161916
return # Ignored safely
1917-
if hint.receiving_player != client.slot:
1917+
if client.slot not in ctx.slot_set(hint.receiving_player):
19181918
await ctx.send_msgs(client,
19191919
[{'cmd': 'InvalidPacket', "type": "arguments", "text": 'UpdateHint: No Permission',
19201920
"original_cmd": cmd}])

NetUtils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ def _handle_text(self, node: JSONMessagePart):
232232

233233
def _handle_player_id(self, node: JSONMessagePart):
234234
player = int(node["text"])
235-
node["color"] = 'magenta' if player == self.ctx.slot else 'yellow'
235+
node["color"] = 'magenta' if self.ctx.slot_concerns_self(player) else 'yellow'
236236
node["text"] = self.ctx.player_names[player]
237237
return self._handle_color(node)
238238

@@ -410,6 +410,8 @@ def get_checked(self, state: typing.Dict[typing.Tuple[int, int], typing.Set[int]
410410
checked = state[team, slot]
411411
if not checked:
412412
# This optimizes the case where everyone connects to a fresh game at the same time.
413+
if slot not in self:
414+
raise KeyError(slot)
413415
return []
414416
return [location_id for
415417
location_id in self[slot] if

Options.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -754,7 +754,7 @@ def __init__(self, value: int) -> None:
754754
elif value > self.range_end and value not in self.special_range_names.values():
755755
raise Exception(f"{value} is higher than maximum {self.range_end} for option {self.__class__.__name__} " +
756756
f"and is also not one of the supported named special values: {self.special_range_names}")
757-
757+
758758
# See docstring
759759
for key in self.special_range_names:
760760
if key != key.lower():
@@ -1180,7 +1180,7 @@ def __len__(self) -> int:
11801180
class Accessibility(Choice):
11811181
"""
11821182
Set rules for reachability of your items/locations.
1183-
1183+
11841184
**Full:** ensure everything can be reached and acquired.
11851185
11861186
**Minimal:** ensure what is needed to reach your goal can be acquired.
@@ -1198,7 +1198,7 @@ class Accessibility(Choice):
11981198
class ItemsAccessibility(Accessibility):
11991199
"""
12001200
Set rules for reachability of your items/locations.
1201-
1201+
12021202
**Full:** ensure everything can be reached and acquired.
12031203
12041204
**Minimal:** ensure what is needed to reach your goal can be acquired.
@@ -1249,12 +1249,16 @@ class CommonOptions(metaclass=OptionsMetaProperty):
12491249
progression_balancing: ProgressionBalancing
12501250
accessibility: Accessibility
12511251

1252-
def as_dict(self, *option_names: str, casing: str = "snake") -> typing.Dict[str, typing.Any]:
1252+
def as_dict(self,
1253+
*option_names: str,
1254+
casing: typing.Literal["snake", "camel", "pascal", "kebab"] = "snake",
1255+
toggles_as_bools: bool = False) -> typing.Dict[str, typing.Any]:
12531256
"""
12541257
Returns a dictionary of [str, Option.value]
12551258
12561259
:param option_names: names of the options to return
12571260
:param casing: case of the keys to return. Supports `snake`, `camel`, `pascal`, `kebab`
1261+
:param toggles_as_bools: whether toggle options should be output as bools instead of strings
12581262
"""
12591263
assert option_names, "options.as_dict() was used without any option names."
12601264
option_results = {}
@@ -1276,6 +1280,8 @@ def as_dict(self, *option_names: str, casing: str = "snake") -> typing.Dict[str,
12761280
value = getattr(self, option_name).value
12771281
if isinstance(value, set):
12781282
value = sorted(value)
1283+
elif toggles_as_bools and issubclass(type(self).type_hints[option_name], Toggle):
1284+
value = bool(value)
12791285
option_results[display_name] = value
12801286
else:
12811287
raise ValueError(f"{option_name} not found in {tuple(type(self).type_hints)}")

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ Currently, the following games are supported:
7878
* Yacht Dice
7979
* Faxanadu
8080
* Saving Princess
81+
* Castlevania: Circle of the Moon
8182

8283
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
8384
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled

Utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,7 +534,8 @@ def handle_exception(exc_type, exc_value, exc_traceback):
534534
sys.__excepthook__(exc_type, exc_value, exc_traceback)
535535
return
536536
logging.getLogger(exception_logger).exception("Uncaught exception",
537-
exc_info=(exc_type, exc_value, exc_traceback))
537+
exc_info=(exc_type, exc_value, exc_traceback),
538+
extra={"NoStream": exception_logger is None})
538539
return orig_hook(exc_type, exc_value, exc_traceback)
539540

540541
handle_exception._wrapped = True

WebHost.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ def get_app() -> "Flask":
3434
app.config.from_file(configpath, yaml.safe_load)
3535
logging.info(f"Updated config from {configpath}")
3636
# inside get_app() so it's usable in systems like gunicorn, which do not run WebHost.py, but import it.
37-
parser = argparse.ArgumentParser()
37+
parser = argparse.ArgumentParser(allow_abbrev=False)
3838
parser.add_argument('--config_override', default=None,
3939
help="Path to yaml config file that overrules config.yaml.")
4040
args = parser.parse_known_args()[0]

WebHostLib/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
app.config["JOB_THRESHOLD"] = 1
4040
# after what time in seconds should generation be aborted, freeing the queue slot. Can be set to None to disable.
4141
app.config["JOB_TIME"] = 600
42+
# memory limit for generator processes in bytes
43+
app.config["GENERATOR_MEMORY_LIMIT"] = 4294967296
4244
app.config['SESSION_PERMANENT'] = True
4345

4446
# waitress uses one thread for I/O, these are for processing of views that then get sent

WebHostLib/autolauncher.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import typing
77
from datetime import timedelta, datetime
88
from threading import Event, Thread
9+
from typing import Any
910
from uuid import UUID
1011

1112
from pony.orm import db_session, select, commit
@@ -53,7 +54,21 @@ def launch_generator(pool: multiprocessing.pool.Pool, generation: Generation):
5354
generation.state = STATE_STARTED
5455

5556

56-
def init_db(pony_config: dict):
57+
def init_generator(config: dict[str, Any]) -> None:
58+
try:
59+
import resource
60+
except ModuleNotFoundError:
61+
pass # unix only module
62+
else:
63+
# set soft limit for memory to from config (default 4GiB)
64+
soft_limit = config["GENERATOR_MEMORY_LIMIT"]
65+
old_limit, hard_limit = resource.getrlimit(resource.RLIMIT_AS)
66+
if soft_limit != old_limit:
67+
resource.setrlimit(resource.RLIMIT_AS, (soft_limit, hard_limit))
68+
logging.debug(f"Changed AS mem limit {old_limit} -> {soft_limit}")
69+
del resource, soft_limit, hard_limit
70+
71+
pony_config = config["PONY"]
5772
db.bind(**pony_config)
5873
db.generate_mapping()
5974

@@ -105,8 +120,8 @@ def keep_running():
105120
try:
106121
with Locker("autogen"):
107122

108-
with multiprocessing.Pool(config["GENERATORS"], initializer=init_db,
109-
initargs=(config["PONY"],), maxtasksperchild=10) as generator_pool:
123+
with multiprocessing.Pool(config["GENERATORS"], initializer=init_generator,
124+
initargs=(config,), maxtasksperchild=10) as generator_pool:
110125
with db_session:
111126
to_start = select(generation for generation in Generation if generation.state == STATE_STARTED)
112127

0 commit comments

Comments
 (0)