Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
139 commits
Select commit Hold shift + click to select a range
4847be9
AHIT: Fix death link timestamps being incorrect (#5404)
CookieCat45 Oct 23, 2025
1983939
LADX: stealing logic option (#3965)
Oct 23, 2025
df3c6b7
KH1: Add specified encoding to file output from Client to avoid crash…
gaithern Oct 23, 2025
4f7f092
setup: check if the sign host is on a local network (#5501)
black-sliver Oct 23, 2025
6b91ffe
WebHost: add missing docutils requirement ... (#5583)
black-sliver Oct 23, 2025
643f61e
Core: Add a ruff.toml to the root directory (#5259)
NewSoupVi Oct 24, 2025
04fe43d
kvui: Fix audio being completely non-functional on Linux (#5588)
NewSoupVi Oct 25, 2025
2bf410f
CI: update appimagetool to 2025-10-19 (#5578)
black-sliver Oct 25, 2025
8837e61
WebHost, Multiple Worlds: fix images not showing in guides (#5576)
black-sliver Oct 25, 2025
41a62a1
SC2: added MindHawk to credits (#5549)
Subsourian Oct 26, 2025
3f139f2
CV64: Fix Explosive DeathLink not working with Increase Shimmy Speed …
LiquidCat64 Oct 26, 2025
4b03061
WebHost: Pin Flask-Compress to 1.18 for all versions of Python (#5590)
NewSoupVi Oct 26, 2025
7cd7111
CI: use rehosted appimage runtime and appimagetool (#5595)
black-sliver Oct 31, 2025
97c07e9
CVCotM: Fix determinism with Halve DSS Cards Placed (#5601)
LiquidCat64 Nov 3, 2025
5b6714d
chore(documentation): Update deployment example config (#5476)
a-priestley Nov 8, 2025
37b87e3
[Docs] Update docs/network protocol.md/NetworkVersion to include clas…
Quasky Nov 8, 2025
96ae223
CCCharles: Fix editorial issues in documentations (#5611)
lgbarrere Nov 8, 2025
360ad71
CI: downgrade pytest to 8.4.2 (#5613)
black-sliver Nov 8, 2025
ecadb30
Core: Allows Meta.yaml to add triggers to individual yaml's categorie…
Vertraic Nov 8, 2025
b2b0d15
Core: add export_datapackage tool (#5609)
Berserker66 Nov 9, 2025
77808d3
Core: Bump version from 0.6.4 to 0.6.5 (#5607)
Berserker66 Nov 9, 2025
19db589
Game Docs: Fix main setup guide links (#5603)
duckboycool Nov 9, 2025
bec6256
SC2 Tracker: Fix bundled Protoss W/A upgrade display (#5612)
Ziktofel Nov 9, 2025
14f261b
Launcher: add skip_open_folder arg to Generate Template Options (#5302)
gurglemurgle5 Nov 9, 2025
0bf48d7
fix(workflows): Update branch filter in Docker workflow (#5616)
a-priestley Nov 9, 2025
ea40156
Jak 1: Remove PAL-only instructions, no longer needed. (#5598)
massimilianodelliubaldini Nov 11, 2025
088f2cc
SC2: Remove dependency on s2clientprotocol and update protobuf versio…
GreenestBeen Nov 11, 2025
283badf
SoE: add apworld manifest (#5557)
black-sliver Nov 11, 2025
4b2298e
SC2: make worlds._sc2common.bot.proto a regular package (#5626)
black-sliver Nov 11, 2025
dba03e3
Choo Choo Charles: Raise InvalidItemError instead of bare Exception (…
NewSoupVi Nov 12, 2025
e4fd064
Core: don't use union type just to reuse a name (#5246)
beauxq Nov 12, 2025
4a41550
CI: update pytest to 9.0.1 (#5637)
black-sliver Nov 14, 2025
d597bc4
Docs: Add troubleshooting section to kh1_en.md, typo fix in kh1/Optio…
Omnises Nov 14, 2025
5779dda
Core: Deprecate Utils.get_options by July 31st, 2025 (#4811)
NewSoupVi Nov 14, 2025
a2f8877
Core: Fix #5605 - Trigger values being shared by weights.yaml slots (…
Mysteryem Nov 14, 2025
d7eb95a
SC2: Allowing unexcluded_items to affect items excluded by vanilla_it…
MatthewMarinets Nov 15, 2025
cde73c5
SC2: Move race_swap pick_one functionality to mission picking (#5538)
Salzkorn Nov 15, 2025
c408c53
LADX: create manifest (#5563)
Nov 15, 2025
98273dd
SC2: Add Manifest (#5559)
Snarkie Nov 15, 2025
93d3d8b
SC2: Fixing a gap in the ascendant upgrades in the tracker (#5570)
MatthewMarinets Nov 15, 2025
7e8746c
Pokémon R/B: Specify encounter types for Dexsanity hint data (#5574)
snowflav-goob Nov 15, 2025
d098372
Wargroove 1: added archipelago.json (#5591)
FlySniper Nov 15, 2025
34e13c5
SC2: Adjusting and slightly simplifying mission difficulty pool adjus…
MatthewMarinets Nov 15, 2025
75eb266
CV64: Fix not having Clocktower Key3 when placed in a start_inventory…
LiquidCat64 Nov 15, 2025
557a284
SC2: Fix custom mission order if used in weights.yaml (#5604)
Ziktofel Nov 15, 2025
e4b5591
CV64: Fix not having Clocktower Key3 when placed in a start_inventory…
Nov 15, 2025
7422b10
SC2: Fix the goal mission tooltip depending on goal missions' status …
Ziktofel Nov 15, 2025
494381b
Factorio: Add no-enemies mode to worldgen schema (#5542)
silasary Nov 15, 2025
8fbd356
Core: add a local yaml creator GUI (#4900)
Silvris Nov 15, 2025
b828781
Core: add random range and additional random descriptions to template…
Silvris Nov 15, 2025
c2094a9
Muse Dash: Update Song list to Medium5 Echoes (#5597)
DeamonHunter Nov 15, 2025
3bb43b2
Landstalker: Add manifest file (#5629)
Dinopony Nov 15, 2025
5055f87
The Witness: Add archipelago.json (#5481)
NewSoupVi Nov 15, 2025
3ec1e91
Core: Only error in playthrough generation if game is not beatable (#…
NewSoupVi Nov 15, 2025
b3c323e
The Witness: Fix CreateHints spoiling vague hints (#5359)
NewSoupVi Nov 15, 2025
24aa4af
WebHost: Validation for webworld themes (#5083)
josephwhite Nov 15, 2025
5e08c8b
Celeste (Open World): fix tutorial link on game page (#5627)
black-sliver Nov 15, 2025
01e64a2
Doc: Update Mac instructions to instruct the user to make a frozen bu…
silasary Nov 16, 2025
3c819ec
LttP: logic fixes and missing bombs (#5645)
Silvris Nov 16, 2025
32a0210
Factorio: Add connection change filtering functionality (#4997)
af-chacon Nov 21, 2025
fd968d7
Yacht Dice: Add archipelago.json manifest #5658
spinerak Nov 21, 2025
8b737ca
Core: Better error message for invalid range values (#4038)
Zannick Nov 22, 2025
c2f76d8
TWW: Fix client sending duplicate magic meter (#5664)
tanjo3 Nov 23, 2025
2d15c23
SC2: Fix missing brackets in Zerg The Host logic (#5657)
Ziktofel Nov 23, 2025
e60ea17
SC2: Migrate external resources from user repos to sc2 organization (…
Ziktofel Nov 24, 2025
447f8fb
LADX: switch to asyncio.get_running_loop() (#5666)
Nov 24, 2025
e0cbf77
APQuest: Implement New Game (#5393)
NewSoupVi Nov 24, 2025
f9630fa
Core: Add a bunch of validation to AutoPatchRegister (#5431)
NewSoupVi Nov 24, 2025
aa2774a
Tests: Move world dependencies in tests to APQuest #5668
qwint Nov 25, 2025
f3000a8
LADX: Give better feedback during patching (#5401)
Nov 25, 2025
d834ece
SC2: Fix bugs and issues around excluded/unexcluded (#5644)
MatthewMarinets Nov 25, 2025
8ea49e7
Core: updates of requirements (#5672)
Berserker66 Nov 25, 2025
c2c4884
Core: Fix typo in docstring for hint_points in commonclient (#5673)
Berserker66 Nov 25, 2025
23d3192
APQuest: Fix import of Protocol from bokeh instead of typing (#5674)
NewSoupVi Nov 25, 2025
ec2c39e
Docs: Improve the documentation for priority locations to mention de-…
Emerassi Nov 26, 2025
3e16c20
PyCharm: fix the apworld builder run config (#5678)
benny-dreamly Nov 26, 2025
3b721e0
Tests: Move hosting test to APQuest #5671
NewSoupVi Nov 26, 2025
60a192b
ALttP/Factorio: Add spaces in concatenated strings (#5564)
duckboycool Nov 27, 2025
8a1a715
SC2: logic fixes minor bugs (#5660)
MatthewMarinets Nov 29, 2025
a07faca
LADX: catch exception after closing magpie #5687
Nov 29, 2025
b75cce5
TLOZ: Add space in concatenated string #5690
duckboycool Nov 29, 2025
34d362a
CV64/CVCotM: Add spaces in concatenated strings (#5691)
duckboycool Nov 29, 2025
9a755e6
Jak and Daxter: Add space in concatenated string #5692
duckboycool Nov 29, 2025
df48c3e
KH1: Add space in concatenated string #5693
duckboycool Nov 29, 2025
7631242
MLSS: Add space in concatenated string #5694
duckboycool Nov 29, 2025
c05a2ad
Wargroove: Add space in concatenated string #5696
duckboycool Nov 29, 2025
d089b00
Core: Add spaces in concatenated strings #5697
duckboycool Nov 29, 2025
360a138
Civ6: Fix issue with names including civ-breaking characters (#5204)
hesto2 Nov 29, 2025
d88fe99
DS3: Update/Fix Excluded Locations Logging (#5220)
Exempt-Medic Nov 29, 2025
f1aca0f
CVCotM: Add a client safeguard in case the player doesn't have Dash B…
LiquidCat64 Nov 29, 2025
b81be6b
Jak and Daxter: Second attempt at fixing trade tests. #5599
massimilianodelliubaldini Nov 29, 2025
08ea3fe
ALTTP: Fix setting `Beat Agahnim 1` event twice (#5617)
Mysteryem Nov 29, 2025
8ae1a7d
TUNIC: Fix fuse rule in lower zig #5621
ScipioWright Nov 29, 2025
9f9765b
shapez: Fix logic bug with vanilla shapes and floating layers #5623
BlastSlimey Nov 29, 2025
f7e3f4e
[FFMQ] Bugfix: Fix missing logic rule for Frozen Fields > Aquaria access
wildham0 Nov 29, 2025
18cf1bc
sc2: Item group fixes and new item groups (#5679)
MatthewMarinets Nov 29, 2025
146a314
SC2: Update Infested Banshee description to be more clear when the B…
Ziktofel Nov 29, 2025
886cc68
Timespinner: Exclude Removed Location from Web Tracker (#5701)
TriumphantBass Nov 29, 2025
4e608b1
Docs: fix name of "Build APWorlds" component (#5703)
beauxq Nov 30, 2025
7750657
SNIClient: new `SnesReader` interface (#5155)
beauxq Nov 30, 2025
3fa2745
OptionCreator: pre-RC1 fixes (#5680)
Silvris Nov 30, 2025
e8a63ab
weights: Fixing negatives and zeroes disappearing from option dicts u…
MatthewMarinets Nov 30, 2025
ac84b27
CI: update appimage runtime to fix problems with sleep (#5706)
black-sliver Dec 1, 2025
0905e3c
WebHost/Game Guides: Change links to stay on current instance (#5699)
duckboycool Dec 1, 2025
d25abfc
Docs: update apsudoku docs / add links to web build (#5720)
EmilyV99 Dec 5, 2025
4a0a65d
WebHost: add played game to static tracker (#5731)
Berserker66 Dec 8, 2025
5a6a0b3
sc2: Fixing typos in item descriptions (#5739)
MatthewMarinets Dec 11, 2025
d65fcf2
Launcher: Add workaround for kivy bug for linux touchpad devices (#5737)
BeeFox-sys Dec 12, 2025
ce38d8c
Docs: Add 'silasary' to Mac tutorial contributors (#5745)
benny-dreamly Dec 14, 2025
577b958
SC2: Fix Kerrigan logic for active spells (#5746)
Ziktofel Dec 14, 2025
51d5e1a
Launcher: fix shortcuts on the AppImage (#5726)
Silvris Dec 15, 2025
45994e3
Tests: test that every option in a preset is visible in either simple…
Silvris Dec 16, 2025
4477dc7
Core: Bump version from 0.6.5 to 0.6.6 (#5753)
Berserker66 Dec 17, 2025
01e1e1f
WebHost: increase form upload limit (#5756)
Berserker66 Dec 17, 2025
56363ea
OptionsCreator: Respect World.hidden flag (#5754)
silasary Dec 17, 2025
a906f13
APQuest: Fix ValueError on typing numbers/backspace #5757
duckboycool Dec 17, 2025
5fa7191
TLOZ: Add manifest file (#5755)
Rosalie-A Dec 17, 2025
5a8e166
SC2: New maintainership (#5752)
Ziktofel Dec 17, 2025
b42fb77
Factorio: Craftsanity (#5529)
Alchav Dec 18, 2025
0002bb8
TUNIC: Make UT care about hex goal amount #5762
ScipioWright Dec 19, 2025
9305ecb
TUNIC: Update world version to 4.2.7 #5761
silent-destroyer Dec 19, 2025
863f161
TUNIC: Update wording on Mask and Lantern option descriptions #5760
silent-destroyer Dec 19, 2025
ebbdd7b
Satisfactory: Add New Game (#5190)
Jarno458 Dec 19, 2025
55c70a5
EarthBound: Implement New Game (#5159)
PinkSwitch Dec 19, 2025
53e8130
Yugioh: Add space in concatenated string (#5695)
duckboycool Dec 19, 2025
d78b9de
Core: Add datapackage exports to gitignore (#5719)
duckboycool Dec 19, 2025
e54a159
Celeste Open World: speedup module load (#5448)
Silvris Dec 19, 2025
efd8528
MultiServer: Safe DataStorage .pop (#5060)
Jarno458 Dec 19, 2025
ad1b41e
Satisfactory/Timespinner: Added Manifesto (#5764)
Jarno458 Dec 19, 2025
8178ee4
Satisfactory: Fix nondeterministic creation of trap filler items (#5766)
Mysteryem Dec 19, 2025
dd5b253
Yoshi's Island - Fix some small logic issues that were reported, add …
PinkSwitch Dec 20, 2025
dbf2325
KH2: Fix placing single items onto multiple locations in pre_fill (#5…
Mysteryem Dec 20, 2025
c6400b6
Core: Process all player files before reporting errors (#4039)
Zannick Dec 20, 2025
1df38cb
Docs: explicitly document why 2^53-1 is the max size, not ^31 or ^63 …
Ixrec Dec 20, 2025
e950a2f
Paint: Add manifest (#5778)
MarioManTAW Dec 22, 2025
d594d5d
APQuest: Fix import shadowing issue (#5769)
NewSoupVi Dec 22, 2025
16559e7
Core: allow abstract world classes (#5468)
drtchops Dec 24, 2025
5a88641
Docs: Make image path in contributing absolute (#5790)
duckboycool Dec 25, 2025
db56e26
Core: Make .apworlds importable using importlib (without force-import…
NewSoupVi Jan 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
13 changes: 7 additions & 6 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@ env:
ENEMIZER_VERSION: 7.1
# NOTE: since appimage/appimagetool and appimage/type2-runtime does not have tags anymore,
# we check the sha256 and require manual intervention if it was updated.
APPIMAGETOOL_VERSION: continuous
APPIMAGETOOL_X86_64_HASH: '29348a20b80827cd261c28e95172ff828b69d43d4e4e18e3fd069e2c8693c94e'
APPIMAGE_RUNTIME_VERSION: continuous
APPIMAGE_RUNTIME_X86_64_HASH: 'e70ffa9b69b211574d0917adc482dd66f25a0083427b5945783965d55b0b0a8b'
APPIMAGE_FORK: 'PopTracker'
APPIMAGETOOL_VERSION: 'r-2025-11-18'
APPIMAGETOOL_X86_64_HASH: '4577a452b30af2337123fbb383aea154b618e51ad5448c3b62085cbbbfbfd9a2'
APPIMAGE_RUNTIME_VERSION: 'r-2025-11-07'
APPIMAGE_RUNTIME_X86_64_HASH: '27ddd3f78e483fc5f7856e413d7c17092917f8c35bfe3318a0d378aa9435ad17'

permissions: # permissions required for attestation
id-token: 'write'
Expand Down Expand Up @@ -141,9 +142,9 @@ jobs:
- name: Install build-time dependencies
run: |
echo "PYTHON=python3.12" >> $GITHUB_ENV
wget -nv https://github.com/AppImage/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-x86_64.AppImage
wget -nv https://github.com/$APPIMAGE_FORK/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-x86_64.AppImage
echo "$APPIMAGETOOL_X86_64_HASH appimagetool-x86_64.AppImage" | sha256sum -c
wget -nv https://github.com/AppImage/type2-runtime/releases/download/$APPIMAGE_RUNTIME_VERSION/runtime-x86_64
wget -nv https://github.com/$APPIMAGE_FORK/type2-runtime/releases/download/$APPIMAGE_RUNTIME_VERSION/runtime-x86_64
echo "$APPIMAGE_RUNTIME_X86_64_HASH runtime-x86_64" | sha256sum -c
chmod a+rx appimagetool-x86_64.AppImage
./appimagetool-x86_64.AppImage --appimage-extract
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
- "!.github/workflows/**"
- ".github/workflows/docker.yml"
branches:
- "*"
- "main"
tags:
- "v?[0-9]+.[0-9]+.[0-9]*"
workflow_dispatch:
Expand Down
13 changes: 7 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ env:
ENEMIZER_VERSION: 7.1
# NOTE: since appimage/appimagetool and appimage/type2-runtime does not have tags anymore,
# we check the sha256 and require manual intervention if it was updated.
APPIMAGETOOL_VERSION: continuous
APPIMAGETOOL_X86_64_HASH: '29348a20b80827cd261c28e95172ff828b69d43d4e4e18e3fd069e2c8693c94e'
APPIMAGE_RUNTIME_VERSION: continuous
APPIMAGE_RUNTIME_X86_64_HASH: 'e70ffa9b69b211574d0917adc482dd66f25a0083427b5945783965d55b0b0a8b'
APPIMAGE_FORK: 'PopTracker'
APPIMAGETOOL_VERSION: 'r-2025-11-18'
APPIMAGETOOL_X86_64_HASH: '4577a452b30af2337123fbb383aea154b618e51ad5448c3b62085cbbbfbfd9a2'
APPIMAGE_RUNTIME_VERSION: 'r-2025-11-07'
APPIMAGE_RUNTIME_X86_64_HASH: '27ddd3f78e483fc5f7856e413d7c17092917f8c35bfe3318a0d378aa9435ad17'

permissions: # permissions required for attestation
id-token: 'write'
Expand Down Expand Up @@ -127,9 +128,9 @@ jobs:
- name: Install build-time dependencies
run: |
echo "PYTHON=python3.12" >> $GITHUB_ENV
wget -nv https://github.com/AppImage/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-x86_64.AppImage
wget -nv https://github.com/$APPIMAGE_FORK/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-x86_64.AppImage
echo "$APPIMAGETOOL_X86_64_HASH appimagetool-x86_64.AppImage" | sha256sum -c
wget -nv https://github.com/AppImage/type2-runtime/releases/download/$APPIMAGE_RUNTIME_VERSION/runtime-x86_64
wget -nv https://github.com/$APPIMAGE_FORK/type2-runtime/releases/download/$APPIMAGE_RUNTIME_VERSION/runtime-x86_64
echo "$APPIMAGE_RUNTIME_X86_64_HASH runtime-x86_64" | sha256sum -c
chmod a+rx appimagetool-x86_64.AppImage
./appimagetool-x86_64.AppImage --appimage-extract
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/unittests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install pytest pytest-subtests pytest-xdist
pip install -r ci-requirements.txt
python ModuleUpdate.py --yes --force --append "WebHostLib/requirements.txt"
python Launcher.py --update_settings # make sure host.yaml exists for tests
- name: Unittests
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Output Logs/
/installdelete.iss
/data/user.kv
/datapackage
/datapackage_export.json
/custom_worlds

# Byte-compiled / optimized / DLL files
Expand Down
4 changes: 2 additions & 2 deletions .run/Build APWorld.run.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$ContentRoot$/Launcher.py" />
<option name="PARAMETERS" value="\&quot;Build APWorlds\&quot;" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/Launcher.py" />
<option name="PARAMETERS" value="&quot;Build APWorlds&quot;" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
Expand Down
7 changes: 4 additions & 3 deletions BaseClasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -1721,9 +1721,10 @@ def create_playthrough(self, create_paths: bool = True) -> None:
logging.debug('The following items could not be reached: %s', ['%s (Player %d) at %s (Player %d)' % (
location.item.name, location.item.player, location.name, location.player) for location in
sphere_candidates])
if any([multiworld.worlds[location.item.player].options.accessibility != 'minimal' for location in sphere_candidates]):
raise RuntimeError(f'Not all progression items reachable ({sphere_candidates}). '
f'Something went terribly wrong here.')
if not multiworld.has_beaten_game(state):
raise RuntimeError("During playthrough generation, the game was determined to be unbeatable. "
"Something went terribly wrong here. "
f"Unreachable progression items: {sphere_candidates}")
else:
self.unreachables = sphere_candidates
break
Expand Down
6 changes: 5 additions & 1 deletion CommonClient.py
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ def update_game(self, game: str, name_to_id_lookup_table: typing.Dict[str, int])
hint_cost: int | None
"""Current Hint Cost per Hint from the server"""
hint_points: int | None
"""Current avaliable Hint Points from the server"""
"""Current available Hint Points from the server"""
player_names: dict[int, str]
"""Current lookup of slot number to player display name from server (includes aliases)"""

Expand Down Expand Up @@ -572,6 +572,10 @@ def is_uninteresting_item_send(self, print_json_packet: dict) -> bool:
return print_json_packet.get("type", "") == "ItemSend" \
and not self.slot_concerns_self(print_json_packet["receiving"]) \
and not self.slot_concerns_self(print_json_packet["item"].player)

def is_connection_change(self, print_json_packet: dict) -> bool:
"""Helper function for filtering out connection changes."""
return print_json_packet.get("type", "") in ["Join","Part"]

def on_print(self, args: dict):
logger.info(args["text"])
Expand Down
143 changes: 103 additions & 40 deletions Generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,9 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
else:
meta_weights = None


player_id = 1
player_files = {}
player_id: int = 1
player_files: dict[int, str] = {}
player_errors: list[str] = []
for file in os.scandir(args.player_files_path):
fname = file.name
if file.is_file() and not fname.startswith(".") and not fname.lower().endswith(".ini") and \
Expand All @@ -137,7 +137,11 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
weights_cache[fname] = tuple(weights_for_file)

except Exception as e:
raise ValueError(f"File {fname} is invalid. Please fix your yaml.") from e
logging.exception(f"Exception reading weights in file {fname}")
player_errors.append(
f"{len(player_errors) + 1}. "
f"File {fname} is invalid. Please fix your yaml.\n{Utils.get_all_causes(e)}"
)

# sort dict for consistent results across platforms:
weights_cache = {key: value for key, value in sorted(weights_cache.items(), key=lambda k: k[0].casefold())}
Expand All @@ -152,6 +156,10 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
args.multi = max(player_id - 1, args.multi)

if args.multi == 0:
if player_errors:
errors = "\n\n".join(player_errors)
raise ValueError(f"Encountered {len(player_errors)} error(s) in player files. "
f"See logs for full tracebacks.\n\n{errors}")
raise ValueError(
"No individual player files found and number of players is 0. "
"Provide individual player files or specify the number of players via host.yaml or --multi."
Expand All @@ -161,6 +169,10 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
f"{seed_name} Seed {seed} with plando: {args.plando}")

if not weights_cache:
if player_errors:
errors = "\n\n".join(player_errors)
raise ValueError(f"Encountered {len(player_errors)} error(s) in player files. "
f"See logs for full tracebacks.\n\n{errors}")
raise Exception(f"No weights found. "
f"Provide a general weights file ({args.weights_file_path}) or individual player files. "
f"A mix is also permitted.")
Expand All @@ -171,10 +183,6 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
args.sprite_pool = dict.fromkeys(range(1, args.multi+1), None)
args.name = {}

settings_cache: dict[str, tuple[argparse.Namespace, ...]] = \
{fname: (tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.sameoptions else None)
for fname, yamls in weights_cache.items()}

if meta_weights:
for category_name, category_dict in meta_weights.items():
for key in category_dict:
Expand All @@ -189,10 +197,32 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
yaml[category][key] = option
elif category_name not in yaml:
logging.warning(f"Meta: Category {category_name} is not present in {path}.")
elif key == "triggers":
if "triggers" not in yaml[category_name]:
yaml[category_name][key] = []
for trigger in option:
yaml[category_name][key].append(trigger)
else:
yaml[category_name][key] = option

player_path_cache = {}
settings_cache: dict[str, tuple[argparse.Namespace, ...]] = {fname: None for fname in weights_cache}
if args.sameoptions:
for fname, yamls in weights_cache.items():
try:
settings_cache[fname] = tuple(roll_settings(yaml, args.plando) for yaml in yamls)
except Exception as e:
logging.exception(f"Exception reading settings in file {fname}")
player_errors.append(
f"{len(player_errors) + 1}. "
f"File {fname} is invalid. Please fix your yaml.\n{Utils.get_all_causes(e)}"
)
# Exit early here to avoid throwing the same errors again later
if player_errors:
errors = "\n\n".join(player_errors)
raise ValueError(f"Encountered {len(player_errors)} error(s) in player files. "
f"See logs for full tracebacks.\n\n{errors}")

player_path_cache: dict[int, str] = {}
for player in range(1, args.multi + 1):
player_path_cache[player] = player_files.get(player, args.weights_file_path)
name_counter = Counter()
Expand All @@ -201,38 +231,62 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
player = 1
while player <= args.multi:
path = player_path_cache[player]
if path:
if not path:
player_errors.append(f'No weights specified for player {player}')
player += 1
continue

for doc_index, yaml in enumerate(weights_cache[path]):
name = yaml.get("name")
try:
settings: tuple[argparse.Namespace, ...] = settings_cache[path] if settings_cache[path] else \
tuple(roll_settings(yaml, args.plando) for yaml in weights_cache[path])
for settingsObject in settings:
for k, v in vars(settingsObject).items():
if v is not None:
try:
getattr(args, k)[player] = v
except AttributeError:
setattr(args, k, {player: v})
except Exception as e:
raise Exception(f"Error setting {k} to {v} for player {player}") from e

# name was not specified
if player not in args.name:
if path == args.weights_file_path:
# weights file, so we need to make the name unique
args.name[player] = f"Player{player}"
else:
# use the filename
args.name[player] = os.path.splitext(os.path.split(path)[-1])[0]
args.name[player] = handle_name(args.name[player], player, name_counter)

player += 1
# Use the cached settings object if it exists, otherwise roll settings within the try-catch
# Invariant: settings_cache[path] and weights_cache[path] have the same length
settingsObject: argparse.Namespace = (
settings_cache[path][doc_index]
if settings_cache[path]
else roll_settings(yaml, args.plando)
)

for k, v in vars(settingsObject).items():
if v is not None:
try:
getattr(args, k)[player] = v
except AttributeError:
setattr(args, k, {player: v})
except Exception as e:
raise Exception(f"Error setting {k} to {v} for player {player}") from e

# name was not specified
if player not in args.name:
if path == args.weights_file_path:
# weights file, so we need to make the name unique
args.name[player] = f"Player{player}"
else:
# use the filename
args.name[player] = os.path.splitext(os.path.split(path)[-1])[0]
args.name[player] = handle_name(args.name[player], player, name_counter)

except Exception as e:
raise ValueError(f"File {path} is invalid. Please fix your yaml.") from e
else:
raise RuntimeError(f'No weights specified for player {player}')
logging.exception(f"Exception reading settings in file {path} document #{doc_index + 1} "
f"(name: {args.name.get(player, name)})")
player_errors.append(
f"{len(player_errors) + 1}. "
f"File {path} document #{doc_index + 1} (name: {args.name.get(player, name)}) is invalid. "
f"Please fix your yaml.\n{Utils.get_all_causes(e)}")

# increment for each yaml document in the file
player += 1

if len(set(name.lower() for name in args.name.values())) != len(args.name):
raise Exception(f"Names have to be unique. Names: {Counter(name.lower() for name in args.name.values())}")
player_errors.append(
f"{len(player_errors) + 1}. "
f"Names have to be unique. Names: {Counter(name.lower() for name in args.name.values())}"
)

if player_errors:
errors = "\n\n".join(player_errors)
raise ValueError(f"Encountered {len(player_errors)} error(s) in player files. "
f"See logs for full tracebacks.\n\n{errors}")

return args, seed

Expand Down Expand Up @@ -342,7 +396,9 @@ def update_weights(weights: dict, new_weights: dict, update_type: str, name: str
elif isinstance(new_value, list):
cleaned_value.extend(new_value)
elif isinstance(new_value, dict):
cleaned_value = dict(Counter(cleaned_value) + Counter(new_value))
counter_value = Counter(cleaned_value)
counter_value.update(new_value)
cleaned_value = dict(counter_value)
else:
raise Exception(f"Cannot apply merge to non-dict, set, or list type {option_name},"
f" received {type(new_value).__name__}.")
Expand All @@ -356,13 +412,18 @@ def update_weights(weights: dict, new_weights: dict, update_type: str, name: str
for element in new_value:
cleaned_value.remove(element)
elif isinstance(new_value, dict):
cleaned_value = dict(Counter(cleaned_value) - Counter(new_value))
counter_value = Counter(cleaned_value)
counter_value.subtract(new_value)
cleaned_value = dict(counter_value)
else:
raise Exception(f"Cannot apply remove to non-dict, set, or list type {option_name},"
f" received {type(new_value).__name__}.")
cleaned_weights[option_name] = cleaned_value
else:
cleaned_weights[option_name] = new_weights[option]
# Options starting with + and - may modify values in-place, and new_weights may be shared by multiple slots
# using the same .yaml, so ensure that the new value is a copy.
cleaned_value = copy.deepcopy(new_weights[option])
cleaned_weights[option_name] = cleaned_value
new_options = set(cleaned_weights) - set(weights)
weights.update(cleaned_weights)
if new_options:
Expand All @@ -385,6 +446,8 @@ def roll_meta_option(option_key, game: str, category_dict: dict) -> Any:
if options[option_key].supports_weighting:
return get_choice(option_key, category_dict)
return category_dict[option_key]
if option_key == "triggers":
return category_dict[option_key]
raise Options.OptionError(f"Error generating meta option {option_key} for {game}.")


Expand Down
Loading
Loading