Skip to content

Commit 8ec15ba

Browse files
BootsinSootsBerserker66silasaryduckboycoolRosalie-A
authored
Pull in 0.6.6 (#77)
* Core: Bump version from 0.6.5 to 0.6.6 (ArchipelagoMW#5753) * WebHost: increase form upload limit (ArchipelagoMW#5756) * OptionsCreator: Respect World.hidden flag (ArchipelagoMW#5754) * APQuest: Fix ValueError on typing numbers/backspace ArchipelagoMW#5757 * TLOZ: Add manifest file (ArchipelagoMW#5755) * Added manifest file. * Update archipelago.json --------- Co-authored-by: NewSoupVi <[email protected]> * SC2: New maintainership (ArchipelagoMW#5752) I (Ziktofel) stepped down but will remain as a mentor * Factorio: Craftsanity (ArchipelagoMW#5529) * TUNIC: Make UT care about hex goal amount ArchipelagoMW#5762 * TUNIC: Update world version to 4.2.7 ArchipelagoMW#5761 * TUNIC: Update wording on Mask and Lantern option descriptions ArchipelagoMW#5760 * Satisfactory: Add New Game (ArchipelagoMW#5190) * Added Satisfactory to latest master * Fixed hard drive from containing the mam + incremented default value for harddrive progression * Apply cherry pick of 3076259 * Apply cherry pick of 6114a55 * Clarify Point goal behavior (Jarno458/SatisfactoryArchipelagoMod#98) * Update Setup guide and info page * Add links to Gifting and Energy Link compatible games. Add info on Hard Drive behavior * Fix typos * Update hard drive behavior description * Hopefully fixed the mam from getting placed behind harddrives * Add 1 "Bundle: Solid Biofuel" to default starting items (for later chainsaw usage or early power gen) * Add info/warning about save setup failure bug * Add notes about dedicated server setup * Fixes: `TypeError: 'set' object is not subscriptable` random.choice does not work over set objects, cast to a list to allow 'trap_selection_override' * progrees i think * Fixed some bugs * Progress commmit incase my pc crashes * progress i think as test passed * I guess test pass, game still unbeatable tho * its generating * Some refactorings * Fixed generation with different elevator tiers * Remove debug statement * Fix this link. * Implemented abstract base classes + some fixes * Implemented many many new options * Yay more stuff * Fixed renaming of filters * Added 1.1 stuffs * Added options groups and presets * Fixes after variable renmame * Added recipy groups for easyer hinting * Implemented random Tier 0 * Updated slot_data * Latest update for 1.1 * Applied cheaper building costs of assembler and foundry * Implemented exploration cost in slot_data * Fixed exposing option type * Add goal time estimates * Trap info * Added support for Universal Tracker Put more things in the never exclude pool for a more familiar gameplay * Added iron ore to build hub * Added Dark Matter Crystals * Added Single Dark Matter Crystals * Fixed typo in options preset * Update setup directions and info * Options formatting fixes, lower minimum ExplorationCollectableCount, add new Explorer starting inventory items preset * Fixed incorrect description on the options * Reduce Portable Miner and Reinforced Iron Plate quantities in "Skip Tutorial Inspired" starting preset * Fixed options pickling error * Reworked logic to no longer include Single: items as filler Reworked logic for more performance Reworked logic to always put useful equipment in pool * Fixed Itemlinks Removed space elevator parts from fillers Removed more AWESOME shop purchaseables from minimal item pool Added all equipment to minimal item pool Removed non fissile and fertile uranium from minimal item pool Removed portal from minimal item pool Removed Ionized fuel from minimal item pool Removed recipes for Hoverpack and Turbo Rifle Ammo from minimal item pool Lowered the chance for rolling steel on randomized starter recipes * Fixed hub milestone item leaking to into wrong milestones * Fixed unlock cost of geothermal generator * Fixed itemlinks again * Add troubleshooting note about hoverpacks * Add starting inventory bundle delivery info * Added hint generation at generation time Harddrive locations now go from 1-100 rather then 0-99 * Update __init__.py Fixed mistake * Cleaned docs to be better suited to get verified * Update CODEOWNERS Added Satisfactory * Update README.md Added Satisfactory * Restructure and expand setup page to instruct both players and hosts * Add terms entry for Archipelago mod * Fixed generation of traps * Added Robb as code owner * Restore tests to original state * Apply suggestions from code review Co-authored-by: Copilot <[email protected]> * Fix additional typos from code review * Implemented fix for itterating enum flags on python 3.10 * Update en_Satisfactory.md * Update setup_en.md * Apply suggestions from code review Co-authored-by: Scipio Wright <[email protected]> * more world > multiworld * Clarify universal tracker behavior * Fix typos * Info on smart hinting system * Move list of additional mods to a page on the mod GitHub * Restore revamped setup guide that other commits overwrote Originally from be26511, d8bd1aa * Removed bundle of ficsit coupons from the from the item pool added estimated completion times to space elevator option description * Apply suggestions from code review Co-authored-by: Scipio Wright <[email protected]> * Wording * Fix typo * Update with changes from ToBeVerified branch * Update note about gameplay options * Update note about gameplay options * Improved universal tracker handling * Improved universal tracker + modernized code a bit * Fixed bugs that where re-introduced * Added Recipe: Excited Photonic Matter * Removed python 3.9 workaround * Fixed * Apply suggestions from code review Co-authored-by: Scipio Wright <[email protected]> * Streamlined handle craftable logic by using itterable rather then tuple Removed dict.keys as the dict itzelf already enumerates over keys * Updated option description * Fixed typing * More info on goal completion conditions * More info on goal completion conditions (093fe38) * Apply suggestions from code review Co-authored-by: Silvris <[email protected]> * Implemented review results * PEP8 stuff * More PEP8 * Rename ElevatorTier->ElevatorPhase and related for clarity and consistency. Untested * speedups part1 * speedsups on part rules * Fix formatting * fix `Elevator Tier #` string literals missed in rename * Remove unused/duplicate imports + organize imports, `== None` to `is None` * Fixed after merge * Updated values + removed TODO * PEPed up the code * Small refactorings * Updated name slot data to phase * Fix hint creation * Clarify wording of elevator goal * Review result * Fixed minor typo in option * Update option time estimates --------- Co-authored-by: Rob B <[email protected]> Co-authored-by: ProverbialPennance <[email protected]> Co-authored-by: Joe Amenta <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Scipio Wright <[email protected]> Co-authored-by: Silvris <[email protected]> Co-authored-by: NewSoupVi <[email protected]> * EarthBound: Implement New Game (ArchipelagoMW#5159) * Add the world * doc update * docs * Fix Blast/Missile not clearing Reflect * Update worlds/earthbound/__init__.py Co-authored-by: Duck <[email protected]> * Update worlds/earthbound/__init__.py remove unused import Co-authored-by: Duck <[email protected]> * Update worlds/earthbound/__init__.py Co-authored-by: Duck <[email protected]> * Update worlds/earthbound/modules/dungeon_er.py make bool optional Co-authored-by: Duck <[email protected]> * Update worlds/earthbound/modules/boss_shuffle.py typing update Co-authored-by: Duck <[email protected]> * Update worlds/earthbound/modules/boss_shuffle.py Co-authored-by: Duck <[email protected]> * Filter events out of item name to id * we call it a glorp * Update worlds/earthbound/Regions.py Co-authored-by: Duck <[email protected]> * Update worlds/earthbound/__init__.py Co-authored-by: Duck <[email protected]> * Update worlds/earthbound/Items.py Co-authored-by: Duck <[email protected]> * Update worlds/earthbound/Regions.py Co-authored-by: Duck <[email protected]> * Fix missing optional import * hint stuff * -Fix Apple Kid text being wrong -Fix Slimy Pile text being wrong * -Fix some sprite corruption if PSI was used when an enemy loaded another enemy -Fixed a visible artifact tile during some cutscenes * Update ver * Update docs * Fix some money scripting issues * Add argument to PSI fakeout attack * Updated monkey caves shop description * Remove closing markdown from doc * Add new flavors * Make flavors actually work * Update platforms * Fix common gear getting duplicated * Split region initialization * Condense checks for start inventory + some other junk * Fix some item groups - change receiver phone to warp pad * wow that one was really bad :glorp: * blah * Fix cutoff option text * switch start inventory concatenation to itertools * Fix sky runner scripting bug - added some new comm suggestions * Fix crash when generating with spoiler_only * Fix happy-happy teleport not unlocking after beating carpainter * Hint man hints can now use CreateHint packets to create hints in other games * Adjust some filler rarity * Update world to use CreateHints and deprecate old method * Fix epilogue skip being offset * Rearrange a couple regions * Fix tendapants getting deleted in battle * update doc * i got scared and forgot i had multiple none checks and am worried about this triggering but tested and it works * Fix mostly typing errors from silvris * More type checks * More typing * Typema * Type * Fix enemy levels overwriting music * Fix gihugic blunder * Fix Lumine Hall enabling OSS * del world * Rel 4.2.7 * Remove some debug logs * Fix vanilla bug with weird ambush detection * Fix Starman Junior having an unscaled Freeze * Change shop scaling * Fix shops using the wrong thankful script * Update some bosses in boss shuffle * Loc group adjustment * Update some boss shuffle stuff | Fix Enemizer attacks getting overwritten by Shuffle data | Fix flunkies not updating and still being used with enemizer * Get rid of some debug stuff * Get boss shuffle running, dont merge * Fix json and get boss shuffle no plando back up * Fix Magicant Boost not initializing to Ness if party count = 4 * Fix belch shop using wrong logic * Don't re-send goal status * EBitem * remove : * idk if this is whatvi wanted * All client messagesnow only send when relevant instead of constantly * Patch up the rest of boss plando * Fix Giygas being not excluded from enemizer * Fix epilogue again * adjust the sphere scaling name * add the things * Fix Ness being placed onto monotoli when monotoli was in sea of eden * Fix prefill properly * Fix boss shuffle on vanilla slots. * rename this, apparently * Update archipelago.json --------- Co-authored-by: Duck <[email protected]> Co-authored-by: NewSoupVi <[email protected]> * Yugioh: Add space in concatenated string (ArchipelagoMW#5695) * Add spaces * Revert wrong one * Add right one * Core: Add datapackage exports to gitignore (ArchipelagoMW#5719) * Gitignore and description * Update description * Celeste Open World: speedup module load (ArchipelagoMW#5448) * speedup world load * those 3 weren't in-fact needed * MultiServer: Safe DataStorage .pop (ArchipelagoMW#5060) * Make datastorage .pop not throw on missing key or index * Reworked to use logic rather than exception catching * Satisfactory/Timespinner: Added Manifesto (ArchipelagoMW#5764) * Added Manifesto * Update archipelago.json * Update archipelago.json * Update archipelago.json --------- Co-authored-by: Jarno <[email protected]> Co-authored-by: NewSoupVi <[email protected]> * Satisfactory: Fix nondeterministic creation of trap filler items (ArchipelagoMW#5766) The `trap_selection_override` option is an `OptionSet` subclass, so its `.value` is a `set`. Sets have nondeterministic iteration order (the iteration order depends on the hashes of the objects within the set, which can change depending on the random hashseed of the Python process). This `.enabled_traps` is used in `Items.get_filler_item_name()` with `random.choice(self.enabled_traps)`, which is called as part of creating the item pool in `Items.build_item_pool()` (for clarity, this `random` is the world's `Random` instance passed as an argument, so no problems there). So, with `self.enabled_traps` being in a nondeterministic order, the picked trap to add to the item pool through `random.choice(self.enabled_traps)` would be nondeterministic. Sorting the `trap_selection_override.value` before converting to a `tuple` ensures that the names in `.enabled_traps` are always in a deterministic order. This issue was identified by merging the main branch into the PR branch for ArchipelagoMW#4410 and seeing Satisfactory fail the tests for hash-determinism. With this fix applied, the tests in that PR pass. * Yoshi's Island - Fix some small logic issues that were reported, add json file (ArchipelagoMW#5742) * Fix Piece of Luigi not goaling until reset * Update .gitignore * fix logic thing that one guy said * fix platform being missing from chomp rock zone rules * add json file * added the wrong one * remove extraneous lnk * Update archipelago.json --------- Co-authored-by: NewSoupVi <[email protected]> * KH2: Fix placing single items onto multiple locations in pre_fill (ArchipelagoMW#5619) `goofy_pre_fill` and `donald_pre_fill` would pick a random `Item` from a `list[Item]` and then use `list.remove()` to remove the picked `Item`, but the lists (at least `donald_weapon_abilities`) could contain multiple items with the same name, so `list.remove()` could remove a different `Item` to the picked `Item`, allowing an `Item` in the list to be picked and placed more than once. This happens because `Item.__eq__` only compares the item's `.name` and `.player`, and `list.remove()` compares by equality, meaning it can remove a different, but equal, instance from the list. This results in `old_location.item` not being cleared, so `old_location.item` and `new_location.item` would refer to the same item. * Core: Process all player files before reporting errors (ArchipelagoMW#4039) * Process all player files before reporting errors Full tracebacks will still be in the console and in the logs, but this creates a relatively compact summary at the bottom. * Include full typename in output * Update module access and address style comments * Annotate variables * multi-errors: Revert to while loop * Core: Handle each roll in its own try-catch * multi-errors: Updated style and comments * Undo accidental index change * multi-errors: fix last remaining ref to erargs * Docs: explicitly document why 2^53-1 is the max size, not ^31 or ^63 (ArchipelagoMW#5717) * explicitly document why 2^53-1 is the max size, not ^31 or ^63 * explicitly recommend 32-bit ids * make description correct by explicitly mentioning and linking to a description of 'safe' * Paint: Add manifest (ArchipelagoMW#5778) * Paint: Implement New Game * Add docstring * Remove unnecessary self.multiworld references * Implement start_inventory_from_pool * Convert logic to use LogicMixin * Add location_exists_with_options function to deduplicate code * Simplify starting tool creation * Add Paint to supported games list * Increment version to 0.4.1 * Update docs to include color selection features * Fix world attribute definitions * Fix linting errors * De-duplicate lists of traps * Move LogicMixin to __init__.py * 0.5.0 features - adjustable canvas size increment, updated similarity metric * Fix OptionError formatting * Create OptionError when generating single-player game with error-prone settings * Increment version to 0.5.1 * Update CODEOWNERS * Update documentation for 0.5.2 client changes * Simplify region creation * Add comments describing logic * Remove unnecessary f-strings * Remove unused import * Refactor rules to location class * Remove unnecessary self.multiworld references * Update logic to correctly match client-side item caps * Paint: Add manifest --------- Co-authored-by: Fabian Dill <[email protected]> * APQuest: Fix import shadowing issue (ArchipelagoMW#5769) * Fix import shadowing issue * another comment * Core: allow abstract world classes (ArchipelagoMW#5468) * Docs: Make image path in contributing absolute (ArchipelagoMW#5790) * Core: Make .apworlds importable using importlib (without force-importing them first) (ArchipelagoMW#5734) * Make apworlds importable in general * move it to a probably more appropriate place? * oops * PyCharm: Fix name of apworld builder run config (ArchipelagoMW#5824) * rename the apworld builder run config * Update Build APWorlds.run.xml --------- Co-authored-by: NewSoupVi <[email protected]> * Multiserver: remove dead code (ArchipelagoMW#5831) * Core: replace the eval in OptionsCreator.py (ArchipelagoMW#5828) * Satisfactory: Fix typo in GoalSelection possible values description comment (ArchipelagoMW#5826) * WebHost: Fix world sorting in /tutorial/ (ArchipelagoMW#5785) * customserver: don't set last_activity that will be overwritten later (ArchipelagoMW#5844) * Factorio: fix inverted condition in victory requirements (ArchipelagoMW#5647) * The Messenger: Fix lambda capture issue in add_closed_portal_reqs (ArchipelagoMW#5816) * Super Mario Land 2: Fix Goal Logic (ArchipelagoMW#5781) * TUNIC: Fix region for the grass by the West Garden portal (ArchipelagoMW#5784) * Core: Change image link to relative (ArchipelagoMW#5802) * Shapez: Change image links to relative (ArchipelagoMW#5803) * Noita: Fix filling Shop Item locations without updating item.location (ArchipelagoMW#5840) In single-player multiworlds with small item pools, Noita was manually placing some items into Shop Item locations, but was only setting location.item, and not also setting item.location so that the item and location refer to one another. This has been fixed by using the MultiWorld.push_item() helper method to place the items instead of manually placing the items. * Core: Add `.apignore` format to not include files in APWorld Builder (ArchipelagoMW#5779) * Timespinner: Align Lantern Logic (ArchipelagoMW#5562) * FFMQ: Update link to upstream rando (ArchipelagoMW#5838) * SC2: fix supreme logic hole (ArchipelagoMW#5768) * sc2: Fixing a discrepancy between slot data and logic where story tech would not be granted for supreme if zerg was not a selected race. * sc2: Fixed an issue where Kinetic Blast was not listed as a vanilla Kerrigan ability * sc2: Fixing some functions that could force Kerrigan items into the pool when playing Kerriganless * sc2: excluding zerg excludes hots for vanilla-like mission order * Preprocessing options * Moving general empty selection handling to option preprocessing * Adding a unit test for empty race/campaign selection * sc2: Properly handling non-raceswapped campaigns when excluding campaigns based on race exclusions * sc2: Adding an explicit error message if a user excludes all missions in a way with no obvious resolution * Core: fix bug with missing help text (ArchipelagoMW#5632) Co-authored-by: Duck <[email protected]> * APQuest: Explain game_name and supports_uri more in components.py (ArchipelagoMW#5759) * APQuest: Explain game_name and supports_uri more in components.py Hopefully this can lead to more games implementing support for the "click on slot name -> everything launches automatically" functionality. * Update components.py * Update components.py * LADX: no pickle (ArchipelagoMW#5849) * Core: Bump version from 0.6.6 to 0.6.7 (ArchipelagoMW#5851) * Docs: add dev FAQ for 'should I start with the APWorld or the client?' (ArchipelagoMW#5716) * Docs: add dev FAQ for 'should I start with the APWorld or the client?' * fix indentation of bullet point wrapped lines * use %20 for spaces in links * link to adding games.md and add #ap-modding-help to adding games.md * make APQuest a link * also linkify 'run a local server' * reword the 'judging client is easier' point to reflect a broader range of first-timers * move the 'not 100%' point into the introductory sentences, and tweak related wording * correct link * Docs: define and explain the trade-off of "local" vs "remote" items (ArchipelagoMW#5718) * first draft * second draft * fix indentation of bullet point wrapped lines * move quote * explicitly discuss all three item handling flags, since the start inventory one is easily forgotten * rewrite to avoid a 'debate between two camps' framing * tweak the wording to allow for the possibility that some games can 'just' do both local and remote items without exposing this detail to the player * relative links * Docs: explicitly document why get_filler_item_name may return non-IC.filler items, despite its name (ArchipelagoMW#5747) * Docs: explicitly document why get_filler_item_name may return non-IC.filler items, despite its name * reword * apply Scipio's rewordings * Update worlds/AutoWorld.py Co-authored-by: qwint <[email protected]> * any --------- Co-authored-by: qwint <[email protected]> * Docs: Show that Data is optional for bounces ArchipelagoMW#5794 * Core: Add Pymem to requirements.txt (ArchipelagoMW#5855) As to not break custom worlds when Jak & Daxter moves from PyMem to PyMemoryEditor * Super Mario 64: Add painting passability as items (ArchipelagoMW#5294) * LADX: fix improved additional warps (ArchipelagoMW#5858) --------- Co-authored-by: Fabian Dill <[email protected]> Co-authored-by: Katelyn Gigante <[email protected]> Co-authored-by: Duck <[email protected]> Co-authored-by: Rosalie <[email protected]> Co-authored-by: NewSoupVi <[email protected]> Co-authored-by: Ziktofel <[email protected]> Co-authored-by: Alchav <[email protected]> Co-authored-by: Scipio Wright <[email protected]> Co-authored-by: Silent <[email protected]> Co-authored-by: Jarno <[email protected]> Co-authored-by: Rob B <[email protected]> Co-authored-by: ProverbialPennance <[email protected]> Co-authored-by: Joe Amenta <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Silvris <[email protected]> Co-authored-by: PinkSwitch <[email protected]> Co-authored-by: Jarno <[email protected]> Co-authored-by: Mysteryem <[email protected]> Co-authored-by: Benjamin S Wolf <[email protected]> Co-authored-by: Ixrec <[email protected]> Co-authored-by: MarioManTAW <[email protected]> Co-authored-by: Ian Robinson <[email protected]> Co-authored-by: Benny D <[email protected]> Co-authored-by: James White <[email protected]> Co-authored-by: Remy Jette <[email protected]> Co-authored-by: black-sliver <[email protected]> Co-authored-by: lepideble <[email protected]> Co-authored-by: Scrungip <[email protected]> Co-authored-by: Colin <[email protected]> Co-authored-by: wildham <[email protected]> Co-authored-by: Phaneros <[email protected]> Co-authored-by: Doug Hoskisson <[email protected]> Co-authored-by: qwint <[email protected]> Co-authored-by: Nicholas Saylor <[email protected]> Co-authored-by: Will Morrow <[email protected]> Co-authored-by: threeandthreee <[email protected]>
1 parent 59d4ae1 commit 8ec15ba

File tree

145 files changed

+55889
-3194
lines changed

Some content is hidden

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

145 files changed

+55889
-3194
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ Output Logs/
6565
/installdelete.iss
6666
/data/user.kv
6767
/datapackage
68+
/datapackage_export.json
6869
/custom_worlds
6970
/fuzz_output/
7071

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<component name="ProjectRunConfigurationManager">
2-
<configuration default="false" name="Build APWorld" type="PythonConfigurationType" factoryName="Python">
2+
<configuration default="false" name="Build APWorlds" type="PythonConfigurationType" factoryName="Python">
33
<module name="Archipelago" />
44
<option name="ENV_FILES" value="" />
55
<option name="INTERPRETER_OPTIONS" value="" />

Generate.py

Lines changed: 86 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
119119
else:
120120
meta_weights = None
121121

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

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

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

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

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

174-
settings_cache: dict[str, tuple[argparse.Namespace, ...]] = \
175-
{fname: (tuple(roll_settings(yaml, args.plando) for yaml in yamls) if args.sameoptions else None)
176-
for fname, yamls in weights_cache.items()}
177-
178186
if meta_weights:
179187
for category_name, category_dict in meta_weights.items():
180188
for key in category_dict:
@@ -197,7 +205,24 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
197205
else:
198206
yaml[category_name][key] = option
199207

200-
player_path_cache = {}
208+
settings_cache: dict[str, tuple[argparse.Namespace, ...]] = {fname: None for fname in weights_cache}
209+
if args.sameoptions:
210+
for fname, yamls in weights_cache.items():
211+
try:
212+
settings_cache[fname] = tuple(roll_settings(yaml, args.plando) for yaml in yamls)
213+
except Exception as e:
214+
logging.exception(f"Exception reading settings in file {fname}")
215+
player_errors.append(
216+
f"{len(player_errors) + 1}. "
217+
f"File {fname} is invalid. Please fix your yaml.\n{Utils.get_all_causes(e)}"
218+
)
219+
# Exit early here to avoid throwing the same errors again later
220+
if player_errors:
221+
errors = "\n\n".join(player_errors)
222+
raise ValueError(f"Encountered {len(player_errors)} error(s) in player files. "
223+
f"See logs for full tracebacks.\n\n{errors}")
224+
225+
player_path_cache: dict[int, str] = {}
201226
for player in range(1, args.multi + 1):
202227
player_path_cache[player] = player_files.get(player, args.weights_file_path)
203228
name_counter = Counter()
@@ -206,38 +231,62 @@ def main(args=None) -> tuple[argparse.Namespace, int]:
206231
player = 1
207232
while player <= args.multi:
208233
path = player_path_cache[player]
209-
if path:
234+
if not path:
235+
player_errors.append(f'No weights specified for player {player}')
236+
player += 1
237+
continue
238+
239+
for doc_index, yaml in enumerate(weights_cache[path]):
240+
name = yaml.get("name")
210241
try:
211-
settings: tuple[argparse.Namespace, ...] = settings_cache[path] if settings_cache[path] else \
212-
tuple(roll_settings(yaml, args.plando) for yaml in weights_cache[path])
213-
for settingsObject in settings:
214-
for k, v in vars(settingsObject).items():
215-
if v is not None:
216-
try:
217-
getattr(args, k)[player] = v
218-
except AttributeError:
219-
setattr(args, k, {player: v})
220-
except Exception as e:
221-
raise Exception(f"Error setting {k} to {v} for player {player}") from e
222-
223-
# name was not specified
224-
if player not in args.name:
225-
if path == args.weights_file_path:
226-
# weights file, so we need to make the name unique
227-
args.name[player] = f"Player{player}"
228-
else:
229-
# use the filename
230-
args.name[player] = os.path.splitext(os.path.split(path)[-1])[0]
231-
args.name[player] = handle_name(args.name[player], player, name_counter)
232-
233-
player += 1
242+
# Use the cached settings object if it exists, otherwise roll settings within the try-catch
243+
# Invariant: settings_cache[path] and weights_cache[path] have the same length
244+
settingsObject: argparse.Namespace = (
245+
settings_cache[path][doc_index]
246+
if settings_cache[path]
247+
else roll_settings(yaml, args.plando)
248+
)
249+
250+
for k, v in vars(settingsObject).items():
251+
if v is not None:
252+
try:
253+
getattr(args, k)[player] = v
254+
except AttributeError:
255+
setattr(args, k, {player: v})
256+
except Exception as e:
257+
raise Exception(f"Error setting {k} to {v} for player {player}") from e
258+
259+
# name was not specified
260+
if player not in args.name:
261+
if path == args.weights_file_path:
262+
# weights file, so we need to make the name unique
263+
args.name[player] = f"Player{player}"
264+
else:
265+
# use the filename
266+
args.name[player] = os.path.splitext(os.path.split(path)[-1])[0]
267+
args.name[player] = handle_name(args.name[player], player, name_counter)
268+
234269
except Exception as e:
235-
raise ValueError(f"File {path} is invalid. Please fix your yaml.") from e
236-
else:
237-
raise RuntimeError(f'No weights specified for player {player}')
270+
logging.exception(f"Exception reading settings in file {path} document #{doc_index + 1} "
271+
f"(name: {args.name.get(player, name)})")
272+
player_errors.append(
273+
f"{len(player_errors) + 1}. "
274+
f"File {path} document #{doc_index + 1} (name: {args.name.get(player, name)}) is invalid. "
275+
f"Please fix your yaml.\n{Utils.get_all_causes(e)}")
276+
277+
# increment for each yaml document in the file
278+
player += 1
238279

239280
if len(set(name.lower() for name in args.name.values())) != len(args.name):
240-
raise Exception(f"Names have to be unique. Names: {Counter(name.lower() for name in args.name.values())}")
281+
player_errors.append(
282+
f"{len(player_errors) + 1}. "
283+
f"Names have to be unique. Names: {Counter(name.lower() for name in args.name.values())}"
284+
)
285+
286+
if player_errors:
287+
errors = "\n\n".join(player_errors)
288+
raise ValueError(f"Encountered {len(player_errors)} error(s) in player files. "
289+
f"See logs for full tracebacks.\n\n{errors}")
241290

242291
return args, seed
243292

MultiServer.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ def remove_from_list(container, value):
6969

7070

7171
def pop_from_container(container, value):
72+
if isinstance(container, list) and isinstance(value, int) and len(container) <= value:
73+
return container
74+
75+
if isinstance(container, dict) and value not in container:
76+
return container
77+
7278
try:
7379
container.pop(value)
7480
except ValueError:
@@ -911,12 +917,6 @@ async def server(websocket: "ServerConnection", path: str = "/", ctx: Context =
911917

912918

913919
async def on_client_connected(ctx: Context, client: Client):
914-
players = []
915-
for team, clients in ctx.clients.items():
916-
for slot, connected_clients in clients.items():
917-
if connected_clients:
918-
name = ctx.player_names[team, slot]
919-
players.append(NetworkPlayer(team, slot, ctx.name_aliases.get((team, slot), name), name))
920920
games = {ctx.games[x] for x in range(1, len(ctx.games) + 1)}
921921
games.add("Archipelago")
922922
await ctx.send_msgs(client, [{
@@ -1364,7 +1364,10 @@ def get_help_text(self) -> str:
13641364
argname += "=" + parameter.default
13651365
argtext += argname
13661366
argtext += " "
1367-
doctext = '\n '.join(inspect.getdoc(method).split('\n'))
1367+
method_doc = inspect.getdoc(method)
1368+
if method_doc is None:
1369+
method_doc = "(missing help text)"
1370+
doctext = "\n ".join(method_doc.split("\n"))
13681371
s += f"{self.marker}{command} {argtext}\n {doctext}\n"
13691372
return s
13701373

OptionsCreator.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -509,8 +509,10 @@ def randomize_option(instance: Widget, value: str):
509509
self.options[name] = "random-" + str(self.options[name])
510510
else:
511511
self.options[name] = self.options[name].replace("random-", "")
512-
if self.options[name].isnumeric() or self.options[name] in ("True", "False"):
513-
self.options[name] = eval(self.options[name])
512+
if self.options[name].isnumeric():
513+
self.options[name] = int(self.options[name])
514+
elif self.options[name] in ("True", "False"):
515+
self.options[name] = self.options[name] == "True"
514516

515517
base_object = instance.parent.parent
516518
label_object = instance.parent
@@ -632,7 +634,7 @@ def world_button_action(world_btn: WorldButton):
632634
self.create_options_panel(world_btn)
633635

634636
for world, cls in sorted(AutoWorldRegister.world_types.items(), key=lambda x: x[0]):
635-
if world == "Archipelago":
637+
if cls.hidden:
636638
continue
637639
world_text = MDButtonText(text=world, size_hint_y=None, width=dp(150),
638640
pos_hint={"x": 0.03, "center_y": 0.5})

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ Currently, the following games are supported:
8383
* Celeste (Open World)
8484
* Choo-Choo Charles
8585
* APQuest
86+
* Satisfactory
87+
* EarthBound
8688

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

Utils.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from time import sleep
2323
from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union, TypeGuard
2424
from yaml import load, load_all, dump
25+
from pathspec import PathSpec, GitIgnoreSpec
2526

2627
try:
2728
from yaml import CLoader as UnsafeLoader, CSafeLoader as SafeLoader, CDumper as Dumper
@@ -48,7 +49,7 @@ def as_simple_string(self) -> str:
4849
return ".".join(str(item) for item in self)
4950

5051

51-
__version__ = "0.6.5"
52+
__version__ = "0.6.7"
5253
version_tuple = tuplize_version(__version__)
5354

5455
is_linux = sys.platform.startswith("linux")
@@ -387,6 +388,14 @@ def store_data_package_for_checksum(game: str, data: typing.Dict[str, Any]) -> N
387388
logging.debug(f"Could not store data package: {e}")
388389

389390

391+
def read_apignore(filename: str | pathlib.Path) -> PathSpec | None:
392+
try:
393+
with open(filename) as ignore_file:
394+
return GitIgnoreSpec.from_lines(ignore_file)
395+
except FileNotFoundError:
396+
return None
397+
398+
390399
def get_default_adjuster_settings(game_name: str) -> Namespace:
391400
import LttPAdjuster
392401
adjuster_settings = Namespace()
@@ -1222,3 +1231,35 @@ def weakref_cb(_, q=self._work_queue):
12221231
t.start()
12231232
self._threads.add(t)
12241233
# NOTE: don't add to _threads_queues so we don't block on shutdown
1234+
1235+
1236+
def get_full_typename(t: type) -> str:
1237+
"""Returns the full qualified name of a type, including its module (if not builtins)."""
1238+
module = t.__module__
1239+
if module and module != "builtins":
1240+
return f"{module}.{t.__qualname__}"
1241+
return t.__qualname__
1242+
1243+
1244+
def get_all_causes(ex: Exception) -> str:
1245+
"""Return a string describing the recursive causes of this exception.
1246+
1247+
:param ex: The exception to be described.
1248+
:return A multiline string starting with the initial exception on the first line and each resulting exception
1249+
on subsequent lines with progressive indentation.
1250+
1251+
For example:
1252+
1253+
```
1254+
Exception: Invalid value 'bad'.
1255+
Which caused: Options.OptionError: Error generating option
1256+
Which caused: ValueError: File bad.yaml is invalid.
1257+
```
1258+
"""
1259+
cause = ex
1260+
causes = [f"{get_full_typename(type(ex))}: {ex}"]
1261+
while cause := cause.__cause__:
1262+
causes.append(f"{get_full_typename(type(cause))}: {cause}")
1263+
top = causes[-1]
1264+
others = "".join(f"\n{' ' * (i + 1)}Which caused: {c}" for i, c in enumerate(reversed(causes[:-1])))
1265+
return f"{top}{others}"

WebHostLib/__init__.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,30 @@
2323
app.jinja_env.filters['all'] = all
2424
app.jinja_env.filters['get_file_safe_name'] = get_file_safe_name
2525

26+
# overwrites of flask default config
27+
app.config["DEBUG"] = False
28+
app.config["PORT"] = 80
29+
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER
30+
app.config["MAX_CONTENT_LENGTH"] = 64 * 1024 * 1024 # 64 megabyte limit
31+
# if you want to deploy, make sure you have a non-guessable secret key
32+
app.config["SECRET_KEY"] = bytes(socket.gethostname(), encoding="utf-8")
33+
app.config["SESSION_PERMANENT"] = True
34+
app.config["MAX_FORM_MEMORY_SIZE"] = 2 * 1024 * 1024 # 2 MB, needed for large option pages such as SC2
35+
36+
# custom config
2637
app.config["SELFHOST"] = True # application process is in charge of running the websites
2738
app.config["GENERATORS"] = 8 # maximum concurrent world gens
2839
app.config["HOSTERS"] = 8 # maximum concurrent room hosters
2940
app.config["SELFLAUNCH"] = True # application process is in charge of launching Rooms.
3041
app.config["SELFLAUNCHCERT"] = None # can point to a SSL Certificate to encrypt Room websocket connections
3142
app.config["SELFLAUNCHKEY"] = None # can point to a SSL Certificate Key to encrypt Room websocket connections
3243
app.config["SELFGEN"] = True # application process is in charge of scheduling Generations.
33-
app.config["DEBUG"] = False
34-
app.config["PORT"] = 80
35-
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
36-
app.config['MAX_CONTENT_LENGTH'] = 64 * 1024 * 1024 # 64 megabyte limit
37-
# if you want to deploy, make sure you have a non-guessable secret key
38-
app.config["SECRET_KEY"] = bytes(socket.gethostname(), encoding="utf-8")
3944
# at what amount of worlds should scheduling be used, instead of rolling in the web-thread
4045
app.config["JOB_THRESHOLD"] = 1
4146
# after what time in seconds should generation be aborted, freeing the queue slot. Can be set to None to disable.
4247
app.config["JOB_TIME"] = 600
4348
# memory limit for generator processes in bytes
4449
app.config["GENERATOR_MEMORY_LIMIT"] = 4294967296
45-
app.config['SESSION_PERMANENT'] = True
4650

4751
# waitress uses one thread for I/O, these are for processing of views that then get sent
4852
# archipelago.gg uses gunicorn + nginx; ignoring this option

WebHostLib/customserver.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ async def start_room(room_id):
325325

326326
except (KeyboardInterrupt, SystemExit):
327327
if ctx.saving:
328-
ctx._save()
328+
ctx._save(True)
329329
setattr(asyncio.current_task(), "save", None)
330330
except Exception as e:
331331
with db_session:
@@ -336,7 +336,7 @@ async def start_room(room_id):
336336
raise
337337
else:
338338
if ctx.saving:
339-
ctx._save()
339+
ctx._save(True)
340340
setattr(asyncio.current_task(), "save", None)
341341
finally:
342342
try:

WebHostLib/misc.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,8 +128,13 @@ def tutorial_landing():
128128
"authors": tutorial.authors,
129129
"language": tutorial.language
130130
}
131-
tutorials = {world_name: tutorials for world_name, tutorials in title_sorted(
132-
tutorials.items(), key=lambda element: "\x00" if element[0] == "Archipelago" else worlds[element[0]].game)}
131+
132+
worlds = dict(
133+
title_sorted(
134+
worlds.items(), key=lambda element: "\x00" if element[0] == "Archipelago" else worlds[element[0]].game
135+
)
136+
)
137+
133138
return render_template("tutorialLanding.html", worlds=worlds, tutorials=tutorials)
134139

135140

0 commit comments

Comments
 (0)